@useairfoil/producer-linear 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -2
- package/dist/index.d.mts +4027 -0
- package/dist/index.mjs +3 -0
- package/dist/main.d.mts +1 -0
- package/dist/main.mjs +1303 -0
- package/dist/main.mjs.map +1 -0
- package/dist/manifest.d.mts +114 -0
- package/dist/manifest.mjs +161 -0
- package/dist/manifest.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/src-Ci3_ltan.mjs +1691 -0
- package/dist/src-Ci3_ltan.mjs.map +1 -0
- package/package.json +48 -4
- package/src/client/client.ts +200 -0
- package/src/connector.ts +151 -0
- package/src/create-table.ts +5 -0
- package/src/demo/index.ts +137 -0
- package/src/demo/initiatives.ts +116 -0
- package/src/demo/issues.ts +320 -0
- package/src/demo/projects.ts +215 -0
- package/src/demo/setup.ts +151 -0
- package/src/demo/shared.ts +122 -0
- package/src/demo/teams.ts +173 -0
- package/src/index.ts +83 -0
- package/src/main.ts +22 -0
- package/src/manifest.ts +58 -0
- package/src/resources/comments.ts +32 -0
- package/src/resources/customers.ts +105 -0
- package/src/resources/documents.ts +31 -0
- package/src/resources/initiatives.ts +136 -0
- package/src/resources/issue-history.ts +119 -0
- package/src/resources/issues.ts +131 -0
- package/src/resources/pages.ts +264 -0
- package/src/resources/projects.ts +161 -0
- package/src/resources/releases.ts +114 -0
- package/src/resources/teams.ts +140 -0
- package/src/sandbox.ts +35 -0
- package/src/schemas/comments.ts +32 -0
- package/src/schemas/customers.ts +80 -0
- package/src/schemas/documents.ts +32 -0
- package/src/schemas/initiatives.ts +119 -0
- package/src/schemas/issues.ts +180 -0
- package/src/schemas/projects.ts +154 -0
- package/src/schemas/releases.ts +119 -0
- package/src/schemas/shared.ts +66 -0
- package/src/schemas/teams.ts +144 -0
- package/src/start.ts +47 -0
- package/src/tables.ts +83 -0
- package/src/webhook/route.ts +50 -0
- package/src/webhook/signature.ts +28 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import type { Demo } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import type { Comment } from "../schemas/comments";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
clock,
|
|
7
|
+
customerCount,
|
|
8
|
+
customerId,
|
|
9
|
+
cycleId,
|
|
10
|
+
cyclesBack,
|
|
11
|
+
cycleWeeks,
|
|
12
|
+
dateOnly,
|
|
13
|
+
day,
|
|
14
|
+
hour,
|
|
15
|
+
issueId,
|
|
16
|
+
issueLabels,
|
|
17
|
+
labelId,
|
|
18
|
+
milestoneId,
|
|
19
|
+
months,
|
|
20
|
+
priorities,
|
|
21
|
+
projectId,
|
|
22
|
+
projects,
|
|
23
|
+
releaseId,
|
|
24
|
+
type Rows,
|
|
25
|
+
stamp,
|
|
26
|
+
stateId,
|
|
27
|
+
states,
|
|
28
|
+
teamId,
|
|
29
|
+
teams,
|
|
30
|
+
userCount,
|
|
31
|
+
userId,
|
|
32
|
+
uuid,
|
|
33
|
+
verbs,
|
|
34
|
+
workspace,
|
|
35
|
+
} from "./shared";
|
|
36
|
+
|
|
37
|
+
/** One group is one issue, with its history, comments, and links. */
|
|
38
|
+
export const issueRows = (index: number, context: Demo.DemoContext): Rows => {
|
|
39
|
+
const { faker, now } = context;
|
|
40
|
+
const { at, later } = clock(context);
|
|
41
|
+
|
|
42
|
+
const rows: Rows = {
|
|
43
|
+
issues: [],
|
|
44
|
+
issue_history: [],
|
|
45
|
+
comments: [],
|
|
46
|
+
issue_relations: [],
|
|
47
|
+
attachments: [],
|
|
48
|
+
customer_needs: [],
|
|
49
|
+
issue_to_releases: [],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const t = index % teams.length;
|
|
53
|
+
const team = teams[t] ?? teams[0];
|
|
54
|
+
const number = Math.floor(index / teams.length) + 1;
|
|
55
|
+
const identifier = `${team.key}-${number}`;
|
|
56
|
+
const id = issueId(index);
|
|
57
|
+
const title = `${faker.helpers.arrayElement(verbs)} ${faker.hacker.adjective()} ${faker.hacker.noun()}`;
|
|
58
|
+
const slug = title.toLowerCase().replaceAll(" ", "-");
|
|
59
|
+
const url = `${workspace}/issue/${identifier}/${slug}`;
|
|
60
|
+
|
|
61
|
+
const creator = faker.number.int({ max: userCount - 1 });
|
|
62
|
+
|
|
63
|
+
const assignee = faker.helpers.maybe(() => faker.number.int({ max: userCount - 1 }), {
|
|
64
|
+
probability: 0.85,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const created = at(now.getTime() - faker.number.int({ min: day, max: 360 * day }));
|
|
68
|
+
|
|
69
|
+
const outcome = faker.helpers.weightedArrayElement([
|
|
70
|
+
{ weight: 55, value: "Done" },
|
|
71
|
+
{ weight: 8, value: "Canceled" },
|
|
72
|
+
{ weight: 12, value: "In Progress" },
|
|
73
|
+
{ weight: 5, value: "In Review" },
|
|
74
|
+
{ weight: 10, value: "Todo" },
|
|
75
|
+
{ weight: 10, value: "Backlog" },
|
|
76
|
+
] as const);
|
|
77
|
+
|
|
78
|
+
const started =
|
|
79
|
+
outcome === "Done" || outcome === "In Progress" || outcome === "In Review"
|
|
80
|
+
? later(created, hour, 20 * day)
|
|
81
|
+
: null;
|
|
82
|
+
|
|
83
|
+
const completed = outcome === "Done" && started ? later(started, hour, 14 * day) : null;
|
|
84
|
+
const canceled = outcome === "Canceled" ? later(created, day, 30 * day) : null;
|
|
85
|
+
const triaged = team.triage ? later(created, 60_000, day) : null;
|
|
86
|
+
|
|
87
|
+
const project = faker.helpers.maybe(() => faker.number.int({ max: projects.length - 1 }), {
|
|
88
|
+
probability: 0.6,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const cycleNumber =
|
|
92
|
+
team.cycles && started
|
|
93
|
+
? Math.max(
|
|
94
|
+
0,
|
|
95
|
+
Math.min(
|
|
96
|
+
cyclesBack,
|
|
97
|
+
cyclesBack - Math.ceil((now.getTime() - started.getTime()) / (cycleWeeks * 7 * day)),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
: null;
|
|
101
|
+
|
|
102
|
+
const label = faker.number.int({ max: issueLabels.length - 1 });
|
|
103
|
+
|
|
104
|
+
const priority = faker.helpers.weightedArrayElement([
|
|
105
|
+
{ weight: 10, value: 0 },
|
|
106
|
+
{ weight: 5, value: 1 },
|
|
107
|
+
{ weight: 25, value: 2 },
|
|
108
|
+
{ weight: 40, value: 3 },
|
|
109
|
+
{ weight: 20, value: 4 },
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
const estimate = team.key === "ENG" ? faker.helpers.arrayElement([1, 2, 3, 5, 8]) : null;
|
|
113
|
+
const stateIndex = states.findIndex((state) => state.name === outcome);
|
|
114
|
+
|
|
115
|
+
// History: created, assigned, then each state change.
|
|
116
|
+
const changes: Array<{ readonly at: Date; readonly from: number | null; readonly to: number }> = [
|
|
117
|
+
{ at: created, from: null, to: team.triage ? 0 : 1 },
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
if (triaged) changes.push({ at: triaged, from: 0, to: 1 });
|
|
121
|
+
if (started) changes.push({ at: started, from: 1, to: 3 });
|
|
122
|
+
|
|
123
|
+
if (outcome === "In Review" && started)
|
|
124
|
+
changes.push({ at: later(started, day, 5 * day), from: 3, to: 4 });
|
|
125
|
+
|
|
126
|
+
if (completed) changes.push({ at: completed, from: 3, to: 5 });
|
|
127
|
+
if (canceled) changes.push({ at: canceled, from: 1, to: 6 });
|
|
128
|
+
if (outcome === "Todo") changes.push({ at: later(created, 60_000, 3 * day), from: 1, to: 2 });
|
|
129
|
+
|
|
130
|
+
changes.forEach((change, h) => {
|
|
131
|
+
rows.issue_history.push({
|
|
132
|
+
id: uuid(30, index, h),
|
|
133
|
+
...stamp(change.at, change.at),
|
|
134
|
+
issue_id: id,
|
|
135
|
+
actor_id: userId(h === 0 ? creator : (assignee ?? creator)),
|
|
136
|
+
from_state_id: change.from === null ? null : stateId(t, change.from),
|
|
137
|
+
to_state_id: stateId(t, change.to),
|
|
138
|
+
from_assignee_id: null,
|
|
139
|
+
to_assignee_id: h === 0 && assignee !== undefined ? userId(assignee) : null,
|
|
140
|
+
from_cycle_id: null,
|
|
141
|
+
to_cycle_id: change.to === 3 && cycleNumber !== null ? cycleId(t, cycleNumber) : null,
|
|
142
|
+
from_project_id: null,
|
|
143
|
+
to_project_id: h === 0 && project !== undefined ? projectId(project) : null,
|
|
144
|
+
from_parent_id: null,
|
|
145
|
+
to_parent_id: null,
|
|
146
|
+
from_team_id: null,
|
|
147
|
+
to_team_id: null,
|
|
148
|
+
from_priority: null,
|
|
149
|
+
to_priority: h === 0 ? priority : null,
|
|
150
|
+
from_estimate: null,
|
|
151
|
+
to_estimate: h === 0 ? estimate : null,
|
|
152
|
+
from_due_date: null,
|
|
153
|
+
to_due_date: null,
|
|
154
|
+
from_title: null,
|
|
155
|
+
to_title: null,
|
|
156
|
+
added_label_ids: h === 0 ? [labelId(label)] : null,
|
|
157
|
+
removed_label_ids: null,
|
|
158
|
+
attachment_id: null,
|
|
159
|
+
customer_need_id: null,
|
|
160
|
+
to_converted_project_id: null,
|
|
161
|
+
updated_description: null,
|
|
162
|
+
archived: null,
|
|
163
|
+
trashed: null,
|
|
164
|
+
auto_archived: null,
|
|
165
|
+
auto_closed: null,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const comments = Array.from({ length: faker.number.int({ max: 3 }) }, (_, c) => {
|
|
170
|
+
const posted = later(created, 60_000, 10 * day);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
id: uuid(31, index, c),
|
|
174
|
+
...stamp(posted, posted),
|
|
175
|
+
_deleted: false,
|
|
176
|
+
body: faker.helpers.arrayElement([
|
|
177
|
+
"I can reproduce this on staging.",
|
|
178
|
+
"Picking this up now.",
|
|
179
|
+
"Added a fix behind a feature flag.",
|
|
180
|
+
"Can we pair on this tomorrow?",
|
|
181
|
+
"Customer confirmed it works now.",
|
|
182
|
+
]),
|
|
183
|
+
user_id: userId(faker.number.int({ max: userCount - 1 })),
|
|
184
|
+
external_user_id: null,
|
|
185
|
+
issue_id: id,
|
|
186
|
+
project_id: null,
|
|
187
|
+
project_update_id: null,
|
|
188
|
+
initiative_id: null,
|
|
189
|
+
initiative_update_id: null,
|
|
190
|
+
document_content_id: null,
|
|
191
|
+
parent_id: null,
|
|
192
|
+
quoted_text: null,
|
|
193
|
+
edited_at: null,
|
|
194
|
+
resolved_at: null,
|
|
195
|
+
resolving_user_id: null,
|
|
196
|
+
url: `${url}#comment-${uuid(31, index, c).slice(-8)}`,
|
|
197
|
+
} satisfies Comment;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
rows.comments.push(...comments);
|
|
201
|
+
|
|
202
|
+
if (index >= teams.length && faker.datatype.boolean({ probability: 0.1 })) {
|
|
203
|
+
rows.issue_relations.push({
|
|
204
|
+
id: uuid(32, index),
|
|
205
|
+
...stamp(created, created),
|
|
206
|
+
issue_id: id,
|
|
207
|
+
// The issue before this one in the same team.
|
|
208
|
+
related_issue_id: issueId(index - teams.length),
|
|
209
|
+
type: faker.helpers.arrayElement(["blocks", "related", "duplicate"]),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (team.key === "ENG" && started) {
|
|
214
|
+
const prNumber = 1000 + index;
|
|
215
|
+
|
|
216
|
+
rows.attachments.push({
|
|
217
|
+
id: uuid(33, index),
|
|
218
|
+
...stamp(started, completed ?? started),
|
|
219
|
+
_deleted: false,
|
|
220
|
+
issue_id: id,
|
|
221
|
+
creator_id: assignee === undefined ? null : userId(assignee),
|
|
222
|
+
external_user_creator_id: null,
|
|
223
|
+
title: `${title} #${prNumber}`,
|
|
224
|
+
subtitle: completed ? "Merged" : "Open",
|
|
225
|
+
url: `https://github.com/acme-labs/web/pull/${prNumber}`,
|
|
226
|
+
source_type: "github",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (faker.datatype.boolean({ probability: 0.15 })) {
|
|
231
|
+
const asked = later(created, 0, 5 * day);
|
|
232
|
+
|
|
233
|
+
rows.customer_needs.push({
|
|
234
|
+
id: uuid(34, index),
|
|
235
|
+
...stamp(asked, asked),
|
|
236
|
+
_deleted: false,
|
|
237
|
+
customer_id: customerId(faker.number.int({ max: customerCount - 1 })),
|
|
238
|
+
issue_id: id,
|
|
239
|
+
project_id: null,
|
|
240
|
+
original_issue_id: id,
|
|
241
|
+
comment_id: null,
|
|
242
|
+
attachment_id: null,
|
|
243
|
+
creator_id: userId(creator),
|
|
244
|
+
body: "Their team asked for this on the last call.",
|
|
245
|
+
priority: faker.helpers.arrayElement([0, 0, 1]),
|
|
246
|
+
url: null,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Engineering work ships in the monthly release after it's done.
|
|
251
|
+
if (team.key === "ENG" && completed) {
|
|
252
|
+
const month = Math.floor((now.getTime() - completed.getTime()) / (30 * day));
|
|
253
|
+
|
|
254
|
+
if (month < months) {
|
|
255
|
+
rows.issue_to_releases.push({
|
|
256
|
+
id: uuid(35, index),
|
|
257
|
+
...stamp(completed, completed),
|
|
258
|
+
issue_id: id,
|
|
259
|
+
release_id: releaseId(month),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const updated = new Date(
|
|
265
|
+
Math.max(
|
|
266
|
+
...changes.map((change) => change.at.getTime()),
|
|
267
|
+
...comments.map((row) => row.created_at.getTime()),
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
rows.issues.push({
|
|
272
|
+
id,
|
|
273
|
+
...stamp(created, updated),
|
|
274
|
+
_deleted: false,
|
|
275
|
+
identifier,
|
|
276
|
+
number,
|
|
277
|
+
title,
|
|
278
|
+
description: `${faker.hacker.phrase()}\n\n**Steps**\n\n1. Open the page\n2. Try the action\n3. See the error`,
|
|
279
|
+
url,
|
|
280
|
+
branch_name: `${identifier.toLowerCase()}-${slug}`,
|
|
281
|
+
priority,
|
|
282
|
+
priority_label: priorities[priority] ?? "No priority",
|
|
283
|
+
estimate,
|
|
284
|
+
due_date: priority === 1 ? dateOnly(new Date(created.getTime() + 7 * day)) : null,
|
|
285
|
+
creator_id: userId(creator),
|
|
286
|
+
assignee_id: assignee === undefined ? null : userId(assignee),
|
|
287
|
+
delegate_id: null,
|
|
288
|
+
snoozed_by_id: null,
|
|
289
|
+
state_id: stateId(t, stateIndex),
|
|
290
|
+
team_id: teamId(t),
|
|
291
|
+
project_id: project === undefined ? null : projectId(project),
|
|
292
|
+
project_milestone_id:
|
|
293
|
+
project === undefined ? null : milestoneId(project, faker.number.int({ max: 2 })),
|
|
294
|
+
cycle_id: cycleNumber === null ? null : cycleId(t, cycleNumber),
|
|
295
|
+
parent_id: null,
|
|
296
|
+
source_comment_id: null,
|
|
297
|
+
external_user_creator_id: null,
|
|
298
|
+
label_ids: [labelId(label)],
|
|
299
|
+
previous_identifiers: [],
|
|
300
|
+
started_at: started,
|
|
301
|
+
completed_at: completed,
|
|
302
|
+
canceled_at: canceled,
|
|
303
|
+
started_triage_at: team.triage ? created : null,
|
|
304
|
+
triaged_at: triaged,
|
|
305
|
+
auto_closed_at: null,
|
|
306
|
+
auto_archived_at: null,
|
|
307
|
+
added_to_cycle_at: cycleNumber === null ? null : started,
|
|
308
|
+
added_to_project_at: project === undefined ? null : created,
|
|
309
|
+
added_to_team_at: created,
|
|
310
|
+
snoozed_until_at: null,
|
|
311
|
+
sla_type: null,
|
|
312
|
+
sla_started_at: null,
|
|
313
|
+
sla_breaches_at: null,
|
|
314
|
+
customer_ticket_count: BigInt(rows.customer_needs.length),
|
|
315
|
+
integration_source_type: null,
|
|
316
|
+
trashed: false,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return rows;
|
|
320
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { Demo } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import type { Document } from "../schemas/documents";
|
|
4
|
+
import type {
|
|
5
|
+
Project,
|
|
6
|
+
ProjectLabel,
|
|
7
|
+
ProjectMilestone,
|
|
8
|
+
ProjectRelation,
|
|
9
|
+
ProjectStatus,
|
|
10
|
+
ProjectUpdate,
|
|
11
|
+
} from "../schemas/projects";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
dateOnly,
|
|
15
|
+
day,
|
|
16
|
+
milestoneId,
|
|
17
|
+
priorities,
|
|
18
|
+
projectId,
|
|
19
|
+
projects,
|
|
20
|
+
projectStatuses,
|
|
21
|
+
stamp,
|
|
22
|
+
teamId,
|
|
23
|
+
teams,
|
|
24
|
+
userCount,
|
|
25
|
+
userId,
|
|
26
|
+
uuid,
|
|
27
|
+
workspace,
|
|
28
|
+
} from "./shared";
|
|
29
|
+
|
|
30
|
+
/** Projects with their milestones, updates, specs, and relations. */
|
|
31
|
+
export const projectTables = ({ faker, now }: Demo.DemoContext, founded: Date) => {
|
|
32
|
+
const set = stamp(founded, founded);
|
|
33
|
+
|
|
34
|
+
const statusRows: Array<ProjectStatus> = projectStatuses.map((status, s) => ({
|
|
35
|
+
id: uuid(15, s),
|
|
36
|
+
...set,
|
|
37
|
+
name: status.name,
|
|
38
|
+
type: status.type,
|
|
39
|
+
color: status.color,
|
|
40
|
+
description: null,
|
|
41
|
+
position: s,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const projectLabels: Array<ProjectLabel> = ["Q1", "Q2", "Customer request", "Tech debt"].map(
|
|
45
|
+
(name, l) => ({
|
|
46
|
+
id: uuid(16, l),
|
|
47
|
+
...set,
|
|
48
|
+
_deleted: false,
|
|
49
|
+
name,
|
|
50
|
+
description: null,
|
|
51
|
+
color: faker.color.rgb({ casing: "upper" }),
|
|
52
|
+
is_group: false,
|
|
53
|
+
parent_id: null,
|
|
54
|
+
creator_id: userId(1),
|
|
55
|
+
retired_at: null,
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const projectRows: Array<Project> = [];
|
|
60
|
+
const milestones: Array<ProjectMilestone> = [];
|
|
61
|
+
const updates: Array<ProjectUpdate> = [];
|
|
62
|
+
const documents: Array<Document> = [];
|
|
63
|
+
|
|
64
|
+
projects.forEach((name, p) => {
|
|
65
|
+
const started = new Date(now.getTime() - (360 - p * 40) * day);
|
|
66
|
+
const target = new Date(started.getTime() + 120 * day);
|
|
67
|
+
const completed = target < now;
|
|
68
|
+
const status = completed ? 4 : p % 3 === 2 ? 1 : 2;
|
|
69
|
+
const lead = (p * 3) % userCount;
|
|
70
|
+
|
|
71
|
+
const health = completed
|
|
72
|
+
? null
|
|
73
|
+
: faker.helpers.arrayElement(["onTrack", "onTrack", "atRisk", "offTrack"]);
|
|
74
|
+
|
|
75
|
+
const updated = completed ? target : now;
|
|
76
|
+
const slug = name.toLowerCase().replaceAll(" ", "-");
|
|
77
|
+
|
|
78
|
+
projectRows.push({
|
|
79
|
+
id: projectId(p),
|
|
80
|
+
...stamp(started, updated),
|
|
81
|
+
_deleted: false,
|
|
82
|
+
name,
|
|
83
|
+
description: `${name} for the ${teams[p % teams.length]?.name ?? "Engineering"} team`,
|
|
84
|
+
content: `## Goal\n\nShip ${name.toLowerCase()} by ${dateOnly(target)}.`,
|
|
85
|
+
url: `${workspace}/project/${slug}-${p.toString(16).padStart(12, "0")}`,
|
|
86
|
+
icon: null,
|
|
87
|
+
color: faker.color.rgb({ casing: "upper" }),
|
|
88
|
+
status_id: uuid(15, status),
|
|
89
|
+
priority: BigInt(1 + (p % 4)),
|
|
90
|
+
priority_label: priorities[1 + (p % 4)] ?? "Medium",
|
|
91
|
+
health,
|
|
92
|
+
health_updated_at: health ? new Date(now.getTime() - 3 * day) : null,
|
|
93
|
+
progress: completed ? 1 : faker.number.float({ min: 0.1, max: 0.8, fractionDigits: 2 }),
|
|
94
|
+
scope: faker.number.int({ min: 20, max: 80 }),
|
|
95
|
+
lead_id: userId(lead),
|
|
96
|
+
creator_id: userId(lead),
|
|
97
|
+
converted_from_issue_id: null,
|
|
98
|
+
start_date: dateOnly(started),
|
|
99
|
+
target_date: dateOnly(target),
|
|
100
|
+
started_at: status === 1 ? null : started,
|
|
101
|
+
completed_at: completed ? target : null,
|
|
102
|
+
canceled_at: null,
|
|
103
|
+
auto_archived_at: null,
|
|
104
|
+
label_ids: [uuid(16, p % 4)],
|
|
105
|
+
team_ids: [teamId(p % teams.length)],
|
|
106
|
+
member_ids: [userId(lead), userId((lead + 1) % userCount)],
|
|
107
|
+
trashed: false,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
["Alpha", "Beta", "GA"].forEach((stage, m) => {
|
|
111
|
+
const milestoneTarget = new Date(started.getTime() + (m + 1) * 40 * day);
|
|
112
|
+
|
|
113
|
+
milestones.push({
|
|
114
|
+
id: milestoneId(p, m),
|
|
115
|
+
...stamp(started, started),
|
|
116
|
+
name: stage,
|
|
117
|
+
description: null,
|
|
118
|
+
project_id: projectId(p),
|
|
119
|
+
target_date: dateOnly(milestoneTarget),
|
|
120
|
+
status:
|
|
121
|
+
milestoneTarget <= now
|
|
122
|
+
? "done"
|
|
123
|
+
: m === 0 || milestoneTarget.getTime() - now.getTime() < 40 * day
|
|
124
|
+
? "next"
|
|
125
|
+
: "unstarted",
|
|
126
|
+
progress: milestoneTarget <= now ? 1 : 0,
|
|
127
|
+
sort_order: m,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// A weekly update while the project runs.
|
|
132
|
+
for (
|
|
133
|
+
let week = started.getTime() + 7 * day;
|
|
134
|
+
week <= Math.min(updated.getTime(), now.getTime());
|
|
135
|
+
week += 7 * day
|
|
136
|
+
) {
|
|
137
|
+
const posted = new Date(week);
|
|
138
|
+
|
|
139
|
+
updates.push({
|
|
140
|
+
id: uuid(17, p, updates.length),
|
|
141
|
+
...stamp(posted, posted),
|
|
142
|
+
_deleted: false,
|
|
143
|
+
project_id: projectId(p),
|
|
144
|
+
user_id: userId(lead),
|
|
145
|
+
body: faker.helpers.arrayElement([
|
|
146
|
+
"Good week. The main flows are done and we're testing edge cases.",
|
|
147
|
+
"We hit a snag with the migration. It may slip a few days.",
|
|
148
|
+
"On track. Design review is done and we start the rollout next week.",
|
|
149
|
+
]),
|
|
150
|
+
health: faker.helpers.arrayElement(["onTrack", "onTrack", "atRisk"]),
|
|
151
|
+
url: `${workspace}/project/${slug}/updates#${updates.length}`,
|
|
152
|
+
edited_at: null,
|
|
153
|
+
comment_count: 0n,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
documents.push({
|
|
158
|
+
id: uuid(18, p),
|
|
159
|
+
...stamp(started, started),
|
|
160
|
+
_deleted: false,
|
|
161
|
+
title: `${name} spec`,
|
|
162
|
+
content: `# ${name}\n\n## Problem\n\nCustomers asked for this.\n\n## Plan\n\nShip it in three milestones.`,
|
|
163
|
+
url: `${workspace}/document/${slug}-spec-${p.toString(16).padStart(12, "0")}`,
|
|
164
|
+
icon: null,
|
|
165
|
+
color: null,
|
|
166
|
+
creator_id: userId(lead),
|
|
167
|
+
updated_by_id: userId(lead),
|
|
168
|
+
project_id: projectId(p),
|
|
169
|
+
initiative_id: null,
|
|
170
|
+
issue_id: null,
|
|
171
|
+
cycle_id: null,
|
|
172
|
+
team_id: null,
|
|
173
|
+
parent_document_id: null,
|
|
174
|
+
hidden_at: null,
|
|
175
|
+
trashed: false,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const projectRelations: Array<ProjectRelation> = [
|
|
180
|
+
{
|
|
181
|
+
id: uuid(19, 0),
|
|
182
|
+
...set,
|
|
183
|
+
type: "dependency",
|
|
184
|
+
project_id: projectId(4),
|
|
185
|
+
project_milestone_id: null,
|
|
186
|
+
anchor_type: "start",
|
|
187
|
+
related_project_id: projectId(1),
|
|
188
|
+
related_project_milestone_id: null,
|
|
189
|
+
related_anchor_type: "end",
|
|
190
|
+
user_id: userId(0),
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: uuid(19, 1),
|
|
194
|
+
...set,
|
|
195
|
+
type: "dependency",
|
|
196
|
+
project_id: projectId(6),
|
|
197
|
+
project_milestone_id: null,
|
|
198
|
+
anchor_type: "start",
|
|
199
|
+
related_project_id: projectId(3),
|
|
200
|
+
related_project_milestone_id: null,
|
|
201
|
+
related_anchor_type: "end",
|
|
202
|
+
user_id: userId(1),
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
project_statuses: statusRows,
|
|
208
|
+
project_labels: projectLabels,
|
|
209
|
+
projects: projectRows,
|
|
210
|
+
project_milestones: milestones,
|
|
211
|
+
project_updates: updates,
|
|
212
|
+
project_relations: projectRelations,
|
|
213
|
+
documents,
|
|
214
|
+
};
|
|
215
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { Demo } from "@useairfoil/connector-kit";
|
|
2
|
+
|
|
3
|
+
import type { Customer, CustomerStatus, CustomerTier } from "../schemas/customers";
|
|
4
|
+
import type { Release, ReleaseNote, ReleasePipeline, ReleaseStage } from "../schemas/releases";
|
|
5
|
+
|
|
6
|
+
import { initiativeTables } from "./initiatives";
|
|
7
|
+
import { projectTables } from "./projects";
|
|
8
|
+
import {
|
|
9
|
+
customerCount,
|
|
10
|
+
customerId,
|
|
11
|
+
dateOnly,
|
|
12
|
+
day,
|
|
13
|
+
months,
|
|
14
|
+
pipelineId,
|
|
15
|
+
releaseId,
|
|
16
|
+
stamp,
|
|
17
|
+
userCount,
|
|
18
|
+
userId,
|
|
19
|
+
uuid,
|
|
20
|
+
workspace,
|
|
21
|
+
} from "./shared";
|
|
22
|
+
import { teamTables } from "./teams";
|
|
23
|
+
|
|
24
|
+
/** The workspace, written once before any issue. */
|
|
25
|
+
export const setupRows = (context: Demo.DemoContext) => {
|
|
26
|
+
const { faker, now } = context;
|
|
27
|
+
const founded = new Date(now.getTime() - 900 * day);
|
|
28
|
+
const set = stamp(founded, founded);
|
|
29
|
+
|
|
30
|
+
const teamSetup = teamTables(context, founded);
|
|
31
|
+
const projectSetup = projectTables(context, founded);
|
|
32
|
+
const initiativeSetup = initiativeTables(context, founded);
|
|
33
|
+
|
|
34
|
+
const customerStatuses: Array<CustomerStatus> = ["Active", "Trial", "Churned"].map((name, s) => ({
|
|
35
|
+
id: uuid(24, s),
|
|
36
|
+
...set,
|
|
37
|
+
name: name.toLowerCase(),
|
|
38
|
+
display_name: name,
|
|
39
|
+
description: null,
|
|
40
|
+
color: ["#4CB782", "#F2C94C", "#95A2B3"][s] ?? "#95A2B3",
|
|
41
|
+
position: s,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
const customerTiers: Array<CustomerTier> = ["Enterprise", "Growth", "Starter"].map((name, s) => ({
|
|
45
|
+
id: uuid(25, s),
|
|
46
|
+
...set,
|
|
47
|
+
name: name.toLowerCase(),
|
|
48
|
+
display_name: name,
|
|
49
|
+
description: null,
|
|
50
|
+
color: ["#5E6AD2", "#4EA7FC", "#BEC2C8"][s] ?? "#BEC2C8",
|
|
51
|
+
position: s,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const customers: Array<Customer> = Array.from({ length: customerCount }, (_, c) => {
|
|
55
|
+
const name = faker.company.name();
|
|
56
|
+
const domain = `${name.toLowerCase().replaceAll(/[^a-z]+/g, "")}.example.com`;
|
|
57
|
+
const tier = c % 3;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
id: customerId(c),
|
|
61
|
+
...stamp(founded, now),
|
|
62
|
+
_deleted: false,
|
|
63
|
+
name,
|
|
64
|
+
domains: [domain],
|
|
65
|
+
external_ids: [],
|
|
66
|
+
logo_url: null,
|
|
67
|
+
owner_id: userId(c % userCount),
|
|
68
|
+
status_id: uuid(24, c === customerCount - 1 ? 2 : c % 5 === 4 ? 1 : 0),
|
|
69
|
+
tier_id: uuid(25, tier),
|
|
70
|
+
revenue: BigInt([250_000, 60_000, 12_000][tier] ?? 12_000),
|
|
71
|
+
size: [2_000, 300, 40][tier] ?? 40,
|
|
72
|
+
approximate_need_count: 0,
|
|
73
|
+
url: `${workspace}/customer/${name.toLowerCase().replaceAll(/[^a-z]+/g, "-")}`,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const pipeline: Array<ReleasePipeline> = [
|
|
78
|
+
{
|
|
79
|
+
id: pipelineId,
|
|
80
|
+
...set,
|
|
81
|
+
name: "Web app",
|
|
82
|
+
type: "scheduled",
|
|
83
|
+
is_production: true,
|
|
84
|
+
url: `${workspace}/releases/web-app`,
|
|
85
|
+
include_path_patterns: ["apps/web/**"],
|
|
86
|
+
approximate_release_count: BigInt(months),
|
|
87
|
+
trashed: false,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const stages: Array<ReleaseStage> = ["Planned", "In progress", "Released"].map((name, s) => ({
|
|
92
|
+
id: uuid(26, s),
|
|
93
|
+
...set,
|
|
94
|
+
name,
|
|
95
|
+
type: ["planned", "started", "completed"][s] ?? "completed",
|
|
96
|
+
color: ["#BEC2C8", "#F2C94C", "#5E6AD2"][s] ?? "#5E6AD2",
|
|
97
|
+
position: s,
|
|
98
|
+
frozen: false,
|
|
99
|
+
pipeline_id: pipelineId,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
// One release a month. The newest one is still in progress.
|
|
103
|
+
const releases: Array<Release> = Array.from({ length: months }, (_, m) => {
|
|
104
|
+
const released = new Date(now.getTime() - m * 30 * day);
|
|
105
|
+
const current = m === 0;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
id: releaseId(m),
|
|
109
|
+
...stamp(new Date(released.getTime() - 30 * day), current ? now : released),
|
|
110
|
+
name: `Release 2.${months - m}.0`,
|
|
111
|
+
description: null,
|
|
112
|
+
release_version: `2.${months - m}.0`,
|
|
113
|
+
url: `${workspace}/releases/web-app/2.${months - m}.0`,
|
|
114
|
+
commit_sha: current ? null : faker.git.commitSha(),
|
|
115
|
+
pipeline_id: pipelineId,
|
|
116
|
+
stage_id: uuid(26, current ? 1 : 2),
|
|
117
|
+
creator_id: userId(0),
|
|
118
|
+
start_date: dateOnly(new Date(released.getTime() - 30 * day)),
|
|
119
|
+
target_date: dateOnly(released),
|
|
120
|
+
started_at: new Date(released.getTime() - 30 * day),
|
|
121
|
+
completed_at: current ? null : released,
|
|
122
|
+
canceled_at: null,
|
|
123
|
+
trashed: false,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const releaseNotes: Array<ReleaseNote> = releases
|
|
128
|
+
.filter((release) => release.completed_at !== null)
|
|
129
|
+
.map((release, k) => ({
|
|
130
|
+
id: uuid(27, k),
|
|
131
|
+
...stamp(release.completed_at ?? now, release.completed_at ?? now),
|
|
132
|
+
title: `What's new in ${release.release_version}`,
|
|
133
|
+
content: "Faster search, clearer billing pages, and many small fixes.",
|
|
134
|
+
url: `${release.url}/notes`,
|
|
135
|
+
pipeline_id: pipelineId,
|
|
136
|
+
release_ids: [release.id],
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
...teamSetup,
|
|
141
|
+
...projectSetup,
|
|
142
|
+
...initiativeSetup,
|
|
143
|
+
customer_statuses: customerStatuses,
|
|
144
|
+
customer_tiers: customerTiers,
|
|
145
|
+
customers,
|
|
146
|
+
release_pipelines: pipeline,
|
|
147
|
+
release_stages: stages,
|
|
148
|
+
releases,
|
|
149
|
+
release_notes: releaseNotes,
|
|
150
|
+
};
|
|
151
|
+
};
|