Skip to content

Commit 90dc253

Browse files
authored
Merge branch 'main' into copilot/fix-8038056b-6260-4367-a757-556e9e0d7ff4
2 parents 2b016d0 + 6d16b4a commit 90dc253

File tree

84 files changed

+1150
-393
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1150
-393
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Zero Shot Compass Issue Creator
2+
description: Creates an issue for Compass based on user input
3+
model: openai/gpt-5-nano
4+
modelParameters:
5+
response_format: text
6+
reasoning_effort: high
7+
max_tokens: 131072
8+
messages:
9+
- role: system
10+
content: >-
11+
You are a helpful assistant that doubles as a product owner and product
12+
manager for a software engineering team building a calendar app for
13+
scheduling events and organizing tasks called Compass.
14+
15+
You're tasked with creating issues for software engineering tasks, bugs
16+
and features applicable to the application development.
17+
18+
The following context about Compass and the product are available to
19+
deduce answers to questions you might have while creating these issues:
20+
21+
- Compass documentation: https://docs.compasscalendar.com/docs
22+
- Repository: https://github.com/SwitchbackTech/compass
23+
- Roadmap: https://github.com/orgs/SwitchbackTech/projects/4
24+
- role: user
25+
content: >-
26+
<issue>{{issue}}</issue>
27+
28+
Create this issue with these specific sections alone in the order they
29+
appear:
30+
31+
- Title
32+
- Description
33+
- Scope
34+
- Acceptance criteria
35+
- Business use case
36+
- Additional context
37+
testData:
38+
- issue: |
39+
As a user, I want to be able to create recurring events in the calendar app so that I can easily schedule events that happen on a regular basis without having to manually enter each occurrence.
40+
evaluators:
41+
- name: Outputs a title
42+
string:
43+
startsWith: "Title"

jest.config.js

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,42 @@
11
/*
22
* For a detailed explanation regarding each configuration property, visit:
33
* https://jestjs.io/docs/configuration
4+
*
45
*/
6+
7+
/** @type { Exclude<Exclude<import("jest").Config["projects"], undefined>[number], string>} */
8+
const backendProject = {
9+
displayName: "backend",
10+
moduleNameMapper: {
11+
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
12+
"^@backend/auth(/(.*)$)?": "<rootDir>/packages/backend/src/auth/$1",
13+
"^@backend/calendar(/(.*)$)?": "<rootDir>/packages/backend/src/calendar/$1",
14+
"^@backend/common(/(.*)$)?": "<rootDir>/packages/backend/src/common/$1",
15+
"^@backend/dev(/(.*)$)?": "<rootDir>/packages/backend/src/dev/$1",
16+
"^@backend/email(/(.*)$)?": "<rootDir>/packages/backend/src/email/$1",
17+
"^@backend/event(/(.*)$)?": "<rootDir>/packages/backend/src/event/$1",
18+
"^@backend/priority(/(.*)$)?": "<rootDir>/packages/backend/src/priority/$1",
19+
"^@backend/servers(/(.*)$)?": "<rootDir>/packages/backend/src/servers/$1",
20+
"^@backend/sync(/(.*)$)?": "<rootDir>/packages/backend/src/sync/$1",
21+
"^@backend/user(/(.*)$)?": "<rootDir>/packages/backend/src/user/$1",
22+
"^@backend/waitlist(/(.*)$)?": "<rootDir>/packages/backend/src/waitlist/$1",
23+
"^@backend/__tests__(/(.*)$)?":
24+
"<rootDir>/packages/backend/src/__tests__/$1",
25+
},
26+
27+
setupFiles: ["<rootDir>/packages/core/src/__tests__/core.test.init.ts"],
28+
setupFilesAfterEnv: [
29+
// backend init intentionally here to accommodate @shelf/mongodb preset
30+
"<rootDir>/packages/backend/src/__tests__/backend.test.init.ts",
31+
"<rootDir>/packages/backend/src/__tests__/backend.test.start.ts",
32+
],
33+
testMatch: ["<rootDir>/packages/backend/**/?(*.)+(spec|test).[tj]s?(x)"],
34+
// A preset that is used as a base for Jest's configuration
35+
preset: "@shelf/jest-mongodb", // https://jestjs.io/docs/mongodb,
36+
};
37+
538
/** @type { import("jest").Config } */
6-
module.exports = {
39+
const config = {
740
// All imported modules in your tests should be mocked automatically
841
// automock: false,
942

@@ -85,7 +118,7 @@ module.exports = {
85118
// moduleNameMapper: {}
86119

87120
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
88-
// modulePathIgnorePatterns: [],
121+
modulePathIgnorePatterns: ["<rootDir>/build"],
89122

90123
// Activates notifications for test results
91124
// notify: false,
@@ -144,36 +177,24 @@ module.exports = {
144177
"/node_modules/(?!react-dnd|dnd-core|@react-dnd)",
145178
],
146179
},
180+
backendProject,
147181
{
148-
displayName: "backend",
182+
displayName: "scripts",
149183
moduleNameMapper: {
150-
"^@core(/(.*)$)?": "<rootDir>/packages/core/src/$1",
151-
"^@backend/auth(/(.*)$)?": "<rootDir>/packages/backend/src/auth/$1",
152-
"^@backend/calendar(/(.*)$)?":
153-
"<rootDir>/packages/backend/src/calendar/$1",
154-
"^@backend/common(/(.*)$)?": "<rootDir>/packages/backend/src/common/$1",
155-
"^@backend/dev(/(.*)$)?": "<rootDir>/packages/backend/src/dev/$1",
156-
"^@backend/email(/(.*)$)?": "<rootDir>/packages/backend/src/email/$1",
157-
"^@backend/event(/(.*)$)?": "<rootDir>/packages/backend/src/event/$1",
158-
"^@backend/priority(/(.*)$)?":
159-
"<rootDir>/packages/backend/src/priority/$1",
160-
"^@backend/servers(/(.*)$)?":
161-
"<rootDir>/packages/backend/src/servers/$1",
162-
"^@backend/sync(/(.*)$)?": "<rootDir>/packages/backend/src/sync/$1",
163-
"^@backend/user(/(.*)$)?": "<rootDir>/packages/backend/src/user/$1",
164-
"^@backend/waitlist(/(.*)$)?":
165-
"<rootDir>/packages/backend/src/waitlist/$1",
166-
"^@backend/__tests__(/(.*)$)?":
167-
"<rootDir>/packages/backend/src/__tests__/$1",
184+
...backendProject.moduleNameMapper,
185+
"^@scripts(/(.*)$)?": "<rootDir>/packages/scripts/src/$1",
186+
"^@scripts/commands(/(.*)$)?":
187+
"<rootDir>/packages/scripts/src/commands/$1",
188+
"^@scripts/common(/(.*)$)?": "<rootDir>/packages/scripts/src/common/$1",
189+
"^@scripts/migrations(/(.*)$)?":
190+
"<rootDir>/packages/scripts/src/migrations/$1",
191+
"^@scripts/seeders(/(.*)$)?":
192+
"<rootDir>/packages/scripts/src/seeders/$1",
168193
},
169194

170-
setupFiles: ["<rootDir>/packages/core/src/__tests__/core.test.init.ts"],
171-
setupFilesAfterEnv: [
172-
// backend init intentionally here to accommodate @shelf/mongodb preset
173-
"<rootDir>/packages/backend/src/__tests__/backend.test.init.ts",
174-
"<rootDir>/packages/backend/src/__tests__/backend.test.start.ts",
175-
],
176-
testMatch: ["<rootDir>/packages/backend/**/?(*.)+(spec|test).[tj]s?(x)"],
195+
setupFiles: [...backendProject.setupFiles],
196+
setupFilesAfterEnv: [...backendProject.setupFilesAfterEnv],
197+
testMatch: ["<rootDir>/packages/scripts/**/?(*.)+(spec|test).[tj]s?(x)"],
177198
// A preset that is used as a base for Jest's configuration
178199
preset: "@shelf/jest-mongodb", // https://jestjs.io/docs/mongodb,
179200
},
@@ -197,6 +218,7 @@ module.exports = {
197218
// rootDir: 'packages/web/',
198219
// rootDir: 'packages/backend/',
199220
rootDir: "./",
221+
passWithNoTests: true,
200222

201223
// A list of paths to directories that Jest should use to search for files in
202224
// roots: [
@@ -272,3 +294,6 @@ module.exports = {
272294
// Whether to use watchman for file crawling
273295
// watchman: true,
274296
};
297+
298+
// eslint-disable-next-line no-undef
299+
module.exports = config;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"test": "cross-env-shell TZ=Etc/UTC yarn jest",
3131
"test:backend": "yarn test backend",
3232
"test:core": "yarn test core",
33-
"test:web": "yarn test web"
33+
"test:web": "yarn test web",
34+
"test:scripts": "yarn test scripts"
3435
},
3536
"dependencies": {
3637
"@compass/backend": "*",

packages/backend/src/event/classes/gcal.event.rrule.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ describe("GcalEventRRule: ", () => {
4444
});
4545

4646
expect(rrule.toString()).toContain("RRULE:FREQ=DAILY");
47-
expect(rrule.toString()).toContain(`COUNT=${GCAL_MAX_RECURRENCES}`);
4847
expect(rrule.count()).toBe(GCAL_MAX_RECURRENCES);
4948
expect(rrule.all()).toHaveLength(GCAL_MAX_RECURRENCES);
5049
});

packages/backend/src/event/classes/gcal.event.rrule.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
gSchema$EventBase,
1010
gSchema$EventInstance,
1111
} from "@core/types/gcal";
12-
import dayjs from "@core/util/date/dayjs";
12+
import dayjs, { Dayjs } from "@core/util/date/dayjs";
1313
import { diffRRuleOptions } from "@core/util/event/event.util";
1414
import {
1515
getGcalEventDateFormat,
@@ -22,6 +22,9 @@ export class GcalEventRRule extends RRule {
2222
#dateKey: "date" | "dateTime";
2323
#dateFormat: string;
2424
#durationMs!: number;
25+
#startDate!: Dayjs;
26+
#endDate!: Dayjs;
27+
#timezone!: string;
2528

2629
constructor(event: gSchema$EventBase, options: Partial<Options> = {}) {
2730
super(GcalEventRRule.#initOptions(event, options));
@@ -30,17 +33,18 @@ export class GcalEventRRule extends RRule {
3033
this.#isAllDay = "date" in this.#event.start!;
3134
this.#dateKey = this.#isAllDay ? "date" : "dateTime";
3235
this.#dateFormat = getGcalEventDateFormat(this.#event.start);
36+
this.#timezone = this.#event.start?.timeZone ?? dayjs.tz.guess();
3337

3438
const { start, end } = this.#event;
35-
const startDate = parseGCalEventDate(start);
36-
const endDate = parseGCalEventDate(end);
3739

38-
this.#durationMs = endDate.diff(startDate, "milliseconds");
40+
this.#startDate = parseGCalEventDate(start);
41+
this.#endDate = parseGCalEventDate(end);
42+
this.#durationMs = this.#endDate.diff(this.#startDate, "milliseconds");
3943
}
4044

4145
static #initOptions(
4246
event: gSchema$EventBase,
43-
options: Partial<Options> = {},
47+
_options: Partial<Options> = {},
4448
): Partial<Options> {
4549
const startDate = parseGCalEventDate(event.start);
4650
const dtstart = startDate.local().toDate();
@@ -49,11 +53,14 @@ export class GcalEventRRule extends RRule {
4953
const recurrence = event.recurrence?.join("\n").trim();
5054
const valid = recurrence?.length > 0;
5155
const rruleSet = valid ? rrulestr(recurrence!, opts) : { origOptions: {} };
52-
const rruleOptions = { ...rruleSet.origOptions, ...options };
53-
const rawCount = rruleOptions.count ?? GCAL_MAX_RECURRENCES;
54-
const count = Math.min(rawCount, GCAL_MAX_RECURRENCES);
56+
const rruleOptions = { ...rruleSet.origOptions, ..._options };
57+
const options = { ...rruleOptions, dtstart, tzid };
5558

56-
return { ...rruleOptions, count, dtstart, tzid };
59+
if (options.until instanceof Date) {
60+
options.count = undefined as unknown as number;
61+
}
62+
63+
return options;
5764
}
5865

5966
diffOptions(rrule: GcalEventRRule): Array<[keyof ParsedOptions, unknown]> {
@@ -64,6 +71,19 @@ export class GcalEventRRule extends RRule {
6471
return this.toString().split("\n");
6572
}
6673

74+
override all(
75+
iterator: (d: Date, len: number) => boolean = (_, index) =>
76+
index < GCAL_MAX_RECURRENCES,
77+
): Date[] {
78+
const dates = super.all(iterator);
79+
const firstInstance = dates[0];
80+
const firstInstanceStartDate = dayjs(firstInstance).tz(this.#timezone);
81+
const includesDtStart = this.#startDate.isSame(firstInstanceStartDate);
82+
const rDates = includesDtStart ? [] : [this.#startDate.toDate()];
83+
84+
return rDates.concat(dates);
85+
}
86+
6787
/**
6888
* instances
6989
*
@@ -73,9 +93,7 @@ export class GcalEventRRule extends RRule {
7393
*/
7494
instances(): gSchema$EventInstance[] {
7595
return this.all().map((date) => {
76-
const timezone = dayjs.tz.guess();
77-
const tzid = this.#event.start?.timeZone ?? timezone;
78-
const startDate = dayjs(date).tz(tzid);
96+
const startDate = dayjs(date).tz(this.#timezone);
7997
const endDate = startDate.add(this.#durationMs, "milliseconds");
8098

8199
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -85,11 +103,11 @@ export class GcalEventRRule extends RRule {
85103
recurringEventId: this.#event.id!,
86104
start: {
87105
[this.#dateKey]: startDate?.format(this.#dateFormat),
88-
timeZone: this.#event.start?.timeZone ?? timezone,
106+
timeZone: this.#timezone,
89107
},
90108
end: {
91109
[this.#dateKey]: endDate.format(this.#dateFormat),
92-
timeZone: this.#event.end?.timeZone ?? timezone,
110+
timeZone: this.#timezone,
93111
},
94112
};
95113

packages/backend/src/event/services/event.delete.test.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import dayjs from "dayjs";
2-
import { Collection, ObjectId } from "mongodb";
1+
import { ObjectId } from "mongodb";
32
import {
43
mockSomedayRecurrences,
54
newsletterId,
5+
userId,
66
} from "@core/__mocks__/v1/events/events.someday.recur";
7+
import { Origin, Priorities } from "@core/constants/core.constants";
78
import { YEAR_MONTH_DAY_FORMAT } from "@core/constants/date.constants";
8-
import { Schema_Event } from "@core/types/event.types";
9+
import dayjs from "@core/util/date/dayjs";
910
import {
1011
cleanupTestDb,
1112
setupTestDb,
@@ -14,17 +15,16 @@ import mongoService from "@backend/common/services/mongo.service";
1415
import { getDeleteByIdFilter } from "@backend/event/services/event.service.util";
1516

1617
describe("Delete Events", () => {
17-
let eventCollection: Collection<Schema_Event>;
18-
19-
beforeAll(async () => {
20-
await setupTestDb();
21-
22-
eventCollection = mongoService.db.collection("event.delete.test");
23-
});
18+
beforeAll(setupTestDb);
2419

2520
beforeEach(async () => {
26-
await eventCollection.deleteMany({});
27-
await eventCollection.insertMany([...mockSomedayRecurrences]);
21+
await mongoService.event.deleteMany({});
22+
await mongoService.event.insertMany(
23+
mockSomedayRecurrences.map((event) => ({
24+
...event,
25+
_id: new ObjectId(event._id),
26+
})),
27+
);
2828
});
2929

3030
afterAll(cleanupTestDb);
@@ -35,17 +35,19 @@ describe("Delete Events", () => {
3535
const instanceId = new ObjectId();
3636

3737
const filter = getDeleteByIdFilter({
38-
_id: instanceId,
39-
user: "user1",
38+
_id: instanceId.toString(),
39+
user: userId,
4040
recurrence: { rule: ["foo"], eventId: newsletterId },
4141
startDate: today.format(YEAR_MONTH_DAY_FORMAT),
4242
endDate: today.add(6, "days").format(YEAR_MONTH_DAY_FORMAT),
43+
origin: Origin.COMPASS,
44+
priority: Priorities.SELF,
4345
});
4446

45-
const { deletedCount } = await eventCollection.deleteMany(filter);
47+
const { deletedCount } = await mongoService.event.deleteMany(filter);
4648
expect(deletedCount).not.toBe(0);
4749

48-
const _events = await eventCollection.find({ user: "user1" }).toArray();
50+
const _events = await mongoService.event.find({ user: userId }).toArray();
4951
const events = _events.map((e) => e.title);
5052

5153
expect(events.includes("Send Newsletter | Base | Past")).toBe(true);

0 commit comments

Comments
 (0)