runline 0.13.0 → 0.14.1
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/dist/plugins/shiftLabs/src/index.js +4 -0
- package/dist/plugins/shiftLabs/src/issue-views.js +200 -0
- package/dist/plugins/shiftLabs/src/issues.js +166 -38
- package/dist/plugins/shiftLabs/src/pages.js +60 -34
- package/dist/plugins/shiftLabs/src/projects.js +102 -0
- package/dist/plugins/shiftLabs/src/shared.js +42 -11
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
+
import { registerIssueViewActions } from "./issue-views.js";
|
|
2
3
|
import { registerIssueActions } from "./issues.js";
|
|
3
4
|
import { registerPageActions } from "./pages.js";
|
|
5
|
+
import { registerProjectActions } from "./projects.js";
|
|
4
6
|
import { registerTranscriptionActions } from "./transcription.js";
|
|
5
7
|
export default function shiftLabs(rl) {
|
|
6
8
|
rl.setName("shiftLabs");
|
|
@@ -15,7 +17,9 @@ export default function shiftLabs(rl) {
|
|
|
15
17
|
env: "SHIFT_LABS_ORG_ID",
|
|
16
18
|
})),
|
|
17
19
|
}));
|
|
20
|
+
registerProjectActions(rl);
|
|
18
21
|
registerIssueActions(rl);
|
|
22
|
+
registerIssueViewActions(rl);
|
|
19
23
|
registerPageActions(rl);
|
|
20
24
|
registerTranscriptionActions(rl);
|
|
21
25
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as t from "typebox";
|
|
2
|
+
import { cursorSchema, enumSchema, ISSUE_PRIORITY, ISSUE_STATUS, ISSUE_VIEW_GROUP, ISSUE_VIEW_LAYOUT, ISSUE_VIEW_SORT, ISSUE_VIEW_VISIBILITY, idSchema, pathSegment, request, STRICT_OBJECT, STRICT_UPDATE_OBJECT, timestampSchema, } from "./shared.js";
|
|
3
|
+
const viewFields = {
|
|
4
|
+
name: t.String({
|
|
5
|
+
minLength: 1,
|
|
6
|
+
maxLength: 120,
|
|
7
|
+
pattern: "\\S",
|
|
8
|
+
description: "View name",
|
|
9
|
+
}),
|
|
10
|
+
visibility: t.Optional(t.Literal("organization")),
|
|
11
|
+
layout: t.Optional(enumSchema("View layout", ISSUE_VIEW_LAYOUT)),
|
|
12
|
+
projectId: t.Optional(idSchema("Project ID")),
|
|
13
|
+
rootIssueId: t.Optional(idSchema("Root Issue ID")),
|
|
14
|
+
statuses: t.Optional(t.Array(enumSchema("Issue status", ISSUE_STATUS), { maxItems: 5 })),
|
|
15
|
+
priorities: t.Optional(t.Array(enumSchema("Issue priority", ISSUE_PRIORITY), { maxItems: 5 })),
|
|
16
|
+
assigneeUserIds: t.Optional(t.Array(idSchema("Assignee user ID"), { maxItems: 20 })),
|
|
17
|
+
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }), {
|
|
18
|
+
maxItems: 20,
|
|
19
|
+
})),
|
|
20
|
+
startAfter: t.Optional(timestampSchema("ISO-8601 filter start timestamp")),
|
|
21
|
+
dueBefore: t.Optional(timestampSchema("ISO-8601 filter due timestamp")),
|
|
22
|
+
blocked: t.Optional(t.Boolean()),
|
|
23
|
+
groupBy: t.Optional(enumSchema("View grouping", ISSUE_VIEW_GROUP)),
|
|
24
|
+
sortBy: t.Optional(t.Array(enumSchema("View sort", ISSUE_VIEW_SORT), {
|
|
25
|
+
minItems: 1,
|
|
26
|
+
maxItems: 3,
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
export function registerIssueViewActions(rl) {
|
|
30
|
+
const listFields = {
|
|
31
|
+
visibility: t.Optional(enumSchema("View visibility", ISSUE_VIEW_VISIBILITY)),
|
|
32
|
+
createdById: t.Optional(idSchema("Creator ID")),
|
|
33
|
+
cursor: t.Optional(cursorSchema()),
|
|
34
|
+
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
35
|
+
};
|
|
36
|
+
rl.registerAction("issueView.list", {
|
|
37
|
+
access: "read",
|
|
38
|
+
description: "List the first page of personal and organization Issue Views.",
|
|
39
|
+
inputSchema: t.Object(listFields, STRICT_OBJECT),
|
|
40
|
+
async execute(input, ctx) {
|
|
41
|
+
const body = await request(ctx, `/v1/issue-views?${listParams(input)}`);
|
|
42
|
+
return body.views;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
rl.registerAction("issueView.listPage", {
|
|
46
|
+
access: "read",
|
|
47
|
+
description: "List a cursor-paginated page of Issue Views.",
|
|
48
|
+
inputSchema: t.Object(listFields, STRICT_OBJECT),
|
|
49
|
+
async execute(input, ctx) {
|
|
50
|
+
return request(ctx, `/v1/issue-views?${listParams(input)}`);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
rl.registerAction("issueView.get", {
|
|
54
|
+
access: "read",
|
|
55
|
+
description: "Get an Issue View by ID.",
|
|
56
|
+
inputSchema: t.Object({ id: idSchema("Issue View ID") }, STRICT_OBJECT),
|
|
57
|
+
async execute(input, ctx) {
|
|
58
|
+
const { id } = input;
|
|
59
|
+
const body = await request(ctx, `/v1/issue-views/${pathSegment(id)}`);
|
|
60
|
+
return body.view;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
rl.registerAction("issueView.issues", {
|
|
64
|
+
access: "read",
|
|
65
|
+
description: "Execute the first page of a saved Issue View.",
|
|
66
|
+
inputSchema: t.Object({
|
|
67
|
+
id: idSchema("Issue View ID"),
|
|
68
|
+
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
69
|
+
}, STRICT_OBJECT),
|
|
70
|
+
async execute(input, ctx) {
|
|
71
|
+
const { id, ...pagination } = input;
|
|
72
|
+
const body = await request(ctx, withQuery(`/v1/issue-views/${pathSegment(id)}/issues`, listParams(pagination)));
|
|
73
|
+
return body.issues;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
rl.registerAction("issueView.issuesPage", {
|
|
77
|
+
access: "read",
|
|
78
|
+
description: "Execute a cursor-paginated page of a saved Issue View.",
|
|
79
|
+
inputSchema: t.Object({
|
|
80
|
+
id: idSchema("Issue View ID"),
|
|
81
|
+
cursor: t.Optional(cursorSchema()),
|
|
82
|
+
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
83
|
+
}, STRICT_OBJECT),
|
|
84
|
+
async execute(input, ctx) {
|
|
85
|
+
const { id, ...pagination } = input;
|
|
86
|
+
return request(ctx, withQuery(`/v1/issue-views/${pathSegment(id)}/issues`, listParams(pagination)));
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
rl.registerAction("issueView.create", {
|
|
90
|
+
access: "write",
|
|
91
|
+
description: "Create a bounded saved Issue query. Views never own Issue membership.",
|
|
92
|
+
inputSchema: t.Object(viewFields, STRICT_OBJECT),
|
|
93
|
+
async execute(input, ctx) {
|
|
94
|
+
const fields = input;
|
|
95
|
+
assertDateOrder(fields.startAfter, fields.dueBefore);
|
|
96
|
+
const body = await request(ctx, "/v1/issue-views", {
|
|
97
|
+
method: "POST",
|
|
98
|
+
body: JSON.stringify(toViewBody(fields)),
|
|
99
|
+
});
|
|
100
|
+
return body.view;
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
rl.registerAction("issueView.update", {
|
|
104
|
+
access: "write",
|
|
105
|
+
description: "Patch a saved Issue View. Null filter values remove that filter without replacing unrelated filters.",
|
|
106
|
+
inputSchema: t.Object({
|
|
107
|
+
id: idSchema("Issue View ID"),
|
|
108
|
+
name: t.Optional(t.String({ minLength: 1, maxLength: 120, pattern: "\\S" })),
|
|
109
|
+
visibility: t.Optional(t.Literal("organization")),
|
|
110
|
+
layout: t.Optional(enumSchema("View layout", ISSUE_VIEW_LAYOUT)),
|
|
111
|
+
projectId: optionalNullableId("Project ID"),
|
|
112
|
+
rootIssueId: optionalNullableId("Root Issue ID"),
|
|
113
|
+
statuses: optionalNullableArray(enumSchema("Issue status", ISSUE_STATUS), 5),
|
|
114
|
+
priorities: optionalNullableArray(enumSchema("Issue priority", ISSUE_PRIORITY), 5),
|
|
115
|
+
assigneeUserIds: optionalNullableArray(idSchema("Assignee user ID"), 20),
|
|
116
|
+
labels: optionalNullableArray(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }), 20),
|
|
117
|
+
startAfter: optionalNullableTimestamp("ISO-8601 filter start timestamp"),
|
|
118
|
+
dueBefore: optionalNullableTimestamp("ISO-8601 filter due timestamp"),
|
|
119
|
+
blocked: t.Optional(t.Union([t.Boolean(), t.Null()])),
|
|
120
|
+
groupBy: t.Optional(t.Union([enumSchema("View grouping", ISSUE_VIEW_GROUP), t.Null()])),
|
|
121
|
+
sortBy: t.Optional(t.Array(enumSchema("View sort", ISSUE_VIEW_SORT), {
|
|
122
|
+
minItems: 1,
|
|
123
|
+
maxItems: 3,
|
|
124
|
+
})),
|
|
125
|
+
}, STRICT_UPDATE_OBJECT),
|
|
126
|
+
async execute(input, ctx) {
|
|
127
|
+
const { id, ...fields } = input;
|
|
128
|
+
assertDateOrder(fields.startAfter, fields.dueBefore);
|
|
129
|
+
const body = await request(ctx, `/v1/issue-views/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(toViewBody(fields, false)) });
|
|
130
|
+
return body.view;
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
rl.registerAction("issueView.delete", {
|
|
134
|
+
access: "write",
|
|
135
|
+
description: "Delete a saved Issue View without changing any Issues.",
|
|
136
|
+
inputSchema: t.Object({ id: idSchema("Issue View ID") }, STRICT_OBJECT),
|
|
137
|
+
async execute(input, ctx) {
|
|
138
|
+
const { id } = input;
|
|
139
|
+
await request(ctx, `/v1/issue-views/${pathSegment(id)}`, {
|
|
140
|
+
method: "DELETE",
|
|
141
|
+
});
|
|
142
|
+
return { deleted: true };
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function listParams(input) {
|
|
147
|
+
const params = new URLSearchParams();
|
|
148
|
+
for (const [key, value] of Object.entries((input ?? {}))) {
|
|
149
|
+
if (value !== undefined)
|
|
150
|
+
params.set(key, String(value));
|
|
151
|
+
}
|
|
152
|
+
return params;
|
|
153
|
+
}
|
|
154
|
+
function withQuery(path, params) {
|
|
155
|
+
const query = params.toString();
|
|
156
|
+
return query ? `${path}?${query}` : path;
|
|
157
|
+
}
|
|
158
|
+
function assertDateOrder(startAfter, dueBefore) {
|
|
159
|
+
if (typeof startAfter === "string" &&
|
|
160
|
+
typeof dueBefore === "string" &&
|
|
161
|
+
startAfter > dueBefore) {
|
|
162
|
+
throw new Error("View dueBefore must not precede startAfter");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function optionalNullableId(description) {
|
|
166
|
+
return t.Optional(t.Union([idSchema(description), t.Null()]));
|
|
167
|
+
}
|
|
168
|
+
function optionalNullableTimestamp(description) {
|
|
169
|
+
return t.Optional(t.Union([timestampSchema(description), t.Null()]));
|
|
170
|
+
}
|
|
171
|
+
function optionalNullableArray(item, maxItems) {
|
|
172
|
+
return t.Optional(t.Union([t.Array(item, { maxItems }), t.Null()]));
|
|
173
|
+
}
|
|
174
|
+
function toViewBody(input, applyDefaults = true) {
|
|
175
|
+
const { projectId, rootIssueId, statuses, priorities, assigneeUserIds, labels, startAfter, dueBefore, blocked, ...view } = input;
|
|
176
|
+
const filters = Object.fromEntries(Object.entries({
|
|
177
|
+
projectId,
|
|
178
|
+
rootIssueId,
|
|
179
|
+
statuses,
|
|
180
|
+
priorities,
|
|
181
|
+
assigneeUserIds,
|
|
182
|
+
labels,
|
|
183
|
+
startAfter,
|
|
184
|
+
dueBefore,
|
|
185
|
+
blocked,
|
|
186
|
+
}).filter(([, value]) => value !== undefined));
|
|
187
|
+
return {
|
|
188
|
+
...view,
|
|
189
|
+
...(applyDefaults
|
|
190
|
+
? {
|
|
191
|
+
visibility: view.visibility ?? "organization",
|
|
192
|
+
layout: view.layout ?? "list",
|
|
193
|
+
}
|
|
194
|
+
: {}),
|
|
195
|
+
...(Object.keys(filters).length > 0 ? { filters } : {}),
|
|
196
|
+
...(applyDefaults && view.sortBy === undefined
|
|
197
|
+
? { sortBy: ["updatedAt"] }
|
|
198
|
+
: {}),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -1,74 +1,202 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import {
|
|
2
|
+
import { cursorSchema, enumSchema, ISSUE_PRIORITY, ISSUE_SOURCE, ISSUE_STATUS, idSchema, pathSegment, request, STRICT_OBJECT, STRICT_UPDATE_OBJECT, timestampSchema, } from "./shared.js";
|
|
3
|
+
const issueListFields = {
|
|
4
|
+
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
5
|
+
projectId: t.Optional(idSchema("Project ID")),
|
|
6
|
+
parentIssueId: t.Optional(idSchema("Parent Issue ID")),
|
|
7
|
+
assigneeUserId: t.Optional(idSchema("Assignee user ID")),
|
|
8
|
+
source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
|
|
9
|
+
includeArchived: t.Optional(t.Boolean()),
|
|
10
|
+
cursor: t.Optional(cursorSchema()),
|
|
11
|
+
limit: t.Optional(t.Integer({
|
|
12
|
+
minimum: 1,
|
|
13
|
+
maximum: 100,
|
|
14
|
+
description: "Max results, default 50",
|
|
15
|
+
})),
|
|
16
|
+
};
|
|
17
|
+
function issueListParams(input) {
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
for (const [key, value] of Object.entries((input ?? {}))) {
|
|
20
|
+
if (value !== undefined)
|
|
21
|
+
params.set(key, String(value));
|
|
22
|
+
}
|
|
23
|
+
return params;
|
|
24
|
+
}
|
|
25
|
+
function withQuery(path, params) {
|
|
26
|
+
const query = params.toString();
|
|
27
|
+
return query ? `${path}?${query}` : path;
|
|
28
|
+
}
|
|
3
29
|
export function registerIssueActions(rl) {
|
|
4
30
|
rl.registerAction("issue.list", {
|
|
5
31
|
access: "read",
|
|
6
|
-
description: "List Shift Labs
|
|
7
|
-
inputSchema: t.Object(
|
|
8
|
-
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
9
|
-
assigneeUserId: t.Optional(t.String()),
|
|
10
|
-
source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
|
|
11
|
-
limit: t.Optional(t.Number({ description: "Max results, default 50" })),
|
|
12
|
-
}),
|
|
32
|
+
description: "List the first page of Shift Labs Issues for the API key's organization.",
|
|
33
|
+
inputSchema: t.Object(issueListFields, STRICT_OBJECT),
|
|
13
34
|
async execute(input, ctx) {
|
|
14
|
-
const
|
|
15
|
-
const params = new URLSearchParams();
|
|
16
|
-
for (const key of ["status", "assigneeUserId", "source", "limit"]) {
|
|
17
|
-
const value = fields[key];
|
|
18
|
-
if (value !== undefined)
|
|
19
|
-
params.set(key, String(value));
|
|
20
|
-
}
|
|
21
|
-
const body = await request(ctx, `/v1/issues?${params}`);
|
|
35
|
+
const body = await request(ctx, `/v1/issues?${issueListParams(input)}`);
|
|
22
36
|
return body.issues;
|
|
23
37
|
},
|
|
24
38
|
});
|
|
39
|
+
rl.registerAction("issue.listPage", {
|
|
40
|
+
access: "read",
|
|
41
|
+
description: "List a cursor-paginated page of Shift Labs Issues.",
|
|
42
|
+
inputSchema: t.Object(issueListFields, STRICT_OBJECT),
|
|
43
|
+
async execute(input, ctx) {
|
|
44
|
+
return request(ctx, `/v1/issues?${issueListParams(input)}`);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
25
47
|
rl.registerAction("issue.get", {
|
|
26
48
|
access: "read",
|
|
27
|
-
description: "Get a Shift Labs
|
|
28
|
-
inputSchema: t.Object({ id:
|
|
49
|
+
description: "Get a Shift Labs Issue by ID.",
|
|
50
|
+
inputSchema: t.Object({ id: idSchema("Issue ID") }, STRICT_OBJECT),
|
|
29
51
|
async execute(input, ctx) {
|
|
30
52
|
const { id } = input;
|
|
31
|
-
const body = await request(ctx, `/v1/issues/${
|
|
53
|
+
const body = await request(ctx, `/v1/issues/${pathSegment(id)}`);
|
|
32
54
|
return body.issue;
|
|
33
55
|
},
|
|
34
56
|
});
|
|
35
57
|
rl.registerAction("issue.create", {
|
|
36
58
|
access: "write",
|
|
37
|
-
description: "Create
|
|
59
|
+
description: "Create an Issue. Leave projectId empty for inbox work; parentIssueId requires a Project.",
|
|
38
60
|
inputSchema: t.Object({
|
|
39
|
-
title: t.String({
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
title: t.String({
|
|
62
|
+
minLength: 1,
|
|
63
|
+
maxLength: 160,
|
|
64
|
+
pattern: "\\S",
|
|
65
|
+
description: "Issue title",
|
|
66
|
+
}),
|
|
67
|
+
description: t.Optional(t.String({ maxLength: 20_000, description: "Issue description" })),
|
|
68
|
+
projectId: t.Optional(idSchema("Project ID")),
|
|
69
|
+
parentIssueId: t.Optional(idSchema("Parent Issue ID")),
|
|
70
|
+
sortOrder: t.Optional(t.Integer({ minimum: 0 })),
|
|
71
|
+
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
42
72
|
priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
|
|
43
|
-
severity: t.Optional(enumSchema("Issue severity", ISSUE_SEVERITY)),
|
|
44
73
|
source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
assigneeUserId: t.Optional(idSchema("Assignee user ID")),
|
|
75
|
+
startAt: t.Optional(timestampSchema("ISO-8601 start timestamp")),
|
|
76
|
+
dueAt: t.Optional(timestampSchema("ISO-8601 due timestamp")),
|
|
77
|
+
deploymentId: t.Optional(idSchema("Deployment ID")),
|
|
78
|
+
workspaceId: t.Optional(idSchema("Workspace ID")),
|
|
79
|
+
sessionId: t.Optional(idSchema("Session ID")),
|
|
80
|
+
traceId: t.Optional(idSchema("Trace ID")),
|
|
81
|
+
fingerprint: t.Optional(t.String({ minLength: 1, maxLength: 512, pattern: "\\S" })),
|
|
82
|
+
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }))),
|
|
83
|
+
metadata: t.Optional(t.Record(t.String(), t.Unknown(), { description: "Issue metadata" })),
|
|
84
|
+
}, STRICT_OBJECT),
|
|
53
85
|
async execute(input, ctx) {
|
|
86
|
+
const fields = input;
|
|
87
|
+
if (fields.parentIssueId !== undefined &&
|
|
88
|
+
fields.projectId === undefined) {
|
|
89
|
+
throw new Error("projectId is required when parentIssueId is set");
|
|
90
|
+
}
|
|
91
|
+
assertDateOrder(fields.startAt, fields.dueAt, "Issue dueAt must not precede startAt");
|
|
54
92
|
const body = await request(ctx, "/v1/issues", {
|
|
55
93
|
method: "POST",
|
|
56
|
-
body: JSON.stringify(
|
|
94
|
+
body: JSON.stringify(fields),
|
|
57
95
|
});
|
|
58
96
|
return body.issue;
|
|
59
97
|
},
|
|
60
98
|
});
|
|
99
|
+
rl.registerAction("issue.update", {
|
|
100
|
+
access: "write",
|
|
101
|
+
description: "Update an Issue, including Project assignment, one-level parentage, ordering, status, dates, or assignee.",
|
|
102
|
+
inputSchema: t.Object({
|
|
103
|
+
id: idSchema("Issue ID"),
|
|
104
|
+
title: t.Optional(t.String({ minLength: 1, maxLength: 160, pattern: "\\S" })),
|
|
105
|
+
description: t.Optional(t.String({ maxLength: 20_000 })),
|
|
106
|
+
projectId: t.Optional(t.Union([idSchema("Project ID"), t.Null()])),
|
|
107
|
+
parentIssueId: t.Optional(t.Union([idSchema("Parent Issue ID"), t.Null()])),
|
|
108
|
+
sortOrder: t.Optional(t.Integer({ minimum: 0 })),
|
|
109
|
+
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
110
|
+
priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
|
|
111
|
+
assigneeUserId: t.Optional(t.Union([idSchema("Assignee user ID"), t.Null()])),
|
|
112
|
+
startAt: t.Optional(t.Union([timestampSchema("ISO-8601 start timestamp"), t.Null()])),
|
|
113
|
+
dueAt: t.Optional(t.Union([timestampSchema("ISO-8601 due timestamp"), t.Null()])),
|
|
114
|
+
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }))),
|
|
115
|
+
archived: t.Optional(t.Boolean()),
|
|
116
|
+
}, STRICT_UPDATE_OBJECT),
|
|
117
|
+
async execute(input, ctx) {
|
|
118
|
+
const { id, ...patch } = input;
|
|
119
|
+
assertDateOrder(patch.startAt, patch.dueAt, "Issue dueAt must not precede startAt");
|
|
120
|
+
const body = await request(ctx, `/v1/issues/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(patch) });
|
|
121
|
+
return body.issue;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
61
124
|
rl.registerAction("issue.comment", {
|
|
62
125
|
access: "write",
|
|
63
|
-
description: "Add a comment to a Shift Labs
|
|
126
|
+
description: "Add a comment to a Shift Labs Issue.",
|
|
64
127
|
inputSchema: t.Object({
|
|
65
|
-
id:
|
|
66
|
-
body: t.String({
|
|
67
|
-
|
|
128
|
+
id: idSchema("Issue ID"),
|
|
129
|
+
body: t.String({
|
|
130
|
+
minLength: 1,
|
|
131
|
+
maxLength: 20_000,
|
|
132
|
+
pattern: "\\S",
|
|
133
|
+
description: "Comment body",
|
|
134
|
+
}),
|
|
135
|
+
}, STRICT_OBJECT),
|
|
68
136
|
async execute(input, ctx) {
|
|
69
137
|
const { id, body } = input;
|
|
70
|
-
const response = await request(ctx, `/v1/issues/${
|
|
138
|
+
const response = await request(ctx, `/v1/issues/${pathSegment(id)}/comments`, { method: "POST", body: JSON.stringify({ body }) });
|
|
71
139
|
return response.event;
|
|
72
140
|
},
|
|
73
141
|
});
|
|
142
|
+
rl.registerAction("issue.dependency.list", {
|
|
143
|
+
access: "read",
|
|
144
|
+
description: "List the first page of blocking and blocked-by links.",
|
|
145
|
+
inputSchema: t.Object({
|
|
146
|
+
id: idSchema("Issue ID"),
|
|
147
|
+
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
148
|
+
}, STRICT_OBJECT),
|
|
149
|
+
async execute(input, ctx) {
|
|
150
|
+
const { id, ...pagination } = input;
|
|
151
|
+
const body = await request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/dependencies`, issueListParams(pagination)));
|
|
152
|
+
return body.dependencies;
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
rl.registerAction("issue.dependency.listPage", {
|
|
156
|
+
access: "read",
|
|
157
|
+
description: "List a cursor-paginated page of Issue dependencies.",
|
|
158
|
+
inputSchema: t.Object({
|
|
159
|
+
id: idSchema("Issue ID"),
|
|
160
|
+
cursor: t.Optional(cursorSchema()),
|
|
161
|
+
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
162
|
+
}, STRICT_OBJECT),
|
|
163
|
+
async execute(input, ctx) {
|
|
164
|
+
const { id, ...pagination } = input;
|
|
165
|
+
return request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/dependencies`, issueListParams(pagination)));
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
rl.registerAction("issue.dependency.add", {
|
|
169
|
+
access: "write",
|
|
170
|
+
description: "Declare that one Issue blocks another Issue.",
|
|
171
|
+
inputSchema: t.Object({
|
|
172
|
+
blockedIssueId: idSchema("Issue that is blocked"),
|
|
173
|
+
blockingIssueId: idSchema("Issue that blocks it"),
|
|
174
|
+
}, STRICT_OBJECT),
|
|
175
|
+
async execute(input, ctx) {
|
|
176
|
+
const { blockedIssueId, blockingIssueId } = input;
|
|
177
|
+
if (blockedIssueId === blockingIssueId) {
|
|
178
|
+
throw new Error("An Issue cannot block itself");
|
|
179
|
+
}
|
|
180
|
+
const body = await request(ctx, `/v1/issues/${pathSegment(blockedIssueId)}/dependencies`, { method: "POST", body: JSON.stringify({ blockingIssueId }) });
|
|
181
|
+
return body.dependency;
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
rl.registerAction("issue.dependency.remove", {
|
|
185
|
+
access: "write",
|
|
186
|
+
description: "Remove an Issue dependency link.",
|
|
187
|
+
inputSchema: t.Object({
|
|
188
|
+
issueId: idSchema("Issue ID"),
|
|
189
|
+
dependencyId: idSchema("Dependency ID"),
|
|
190
|
+
}, STRICT_OBJECT),
|
|
191
|
+
async execute(input, ctx) {
|
|
192
|
+
const { issueId, dependencyId } = input;
|
|
193
|
+
await request(ctx, `/v1/issues/${pathSegment(issueId)}/dependencies/${pathSegment(dependencyId)}`, { method: "DELETE" });
|
|
194
|
+
return { removed: true };
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function assertDateOrder(start, end, message) {
|
|
199
|
+
if (typeof start === "string" && typeof end === "string" && start > end) {
|
|
200
|
+
throw new Error(message);
|
|
201
|
+
}
|
|
74
202
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { enumSchema, PAGE_STATUS, PAGE_VISIBILITY, pageRenderUrl, request, } from "./shared.js";
|
|
2
|
+
import { enumSchema, idSchema, PAGE_STATUS, PAGE_VISIBILITY, pageRenderUrl, pathSegment, request, STRICT_OBJECT, timestampSchema, } from "./shared.js";
|
|
3
|
+
const PageSlug = t.String({
|
|
4
|
+
minLength: 1,
|
|
5
|
+
maxLength: 120,
|
|
6
|
+
pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
7
|
+
description: "Lowercase kebab-case page slug",
|
|
8
|
+
});
|
|
3
9
|
export function registerPageActions(rl) {
|
|
4
10
|
rl.registerAction("page.list", {
|
|
5
11
|
access: "read",
|
|
6
12
|
description: "List Shift Labs pages for the API key's organization.",
|
|
7
13
|
inputSchema: t.Object({
|
|
8
14
|
status: t.Optional(enumSchema("Page status", PAGE_STATUS)),
|
|
9
|
-
limit: t.Optional(t.
|
|
10
|
-
|
|
15
|
+
limit: t.Optional(t.Integer({
|
|
16
|
+
minimum: 1,
|
|
17
|
+
maximum: 100,
|
|
18
|
+
description: "Max results, default 50",
|
|
19
|
+
})),
|
|
20
|
+
}, STRICT_OBJECT),
|
|
11
21
|
async execute(input, ctx) {
|
|
12
22
|
const fields = (input ?? {});
|
|
13
23
|
const params = new URLSearchParams();
|
|
@@ -22,10 +32,10 @@ export function registerPageActions(rl) {
|
|
|
22
32
|
rl.registerAction("page.get", {
|
|
23
33
|
access: "read",
|
|
24
34
|
description: "Get a Shift Labs page by ID.",
|
|
25
|
-
inputSchema: t.Object({ id:
|
|
35
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
26
36
|
async execute(input, ctx) {
|
|
27
37
|
const { id } = input;
|
|
28
|
-
const body = await request(ctx, `/v1/pages/${
|
|
38
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}`);
|
|
29
39
|
return body.page;
|
|
30
40
|
},
|
|
31
41
|
});
|
|
@@ -33,11 +43,19 @@ export function registerPageActions(rl) {
|
|
|
33
43
|
access: "write",
|
|
34
44
|
description: "Create a draft hosted HTML page. Agents can publish it with page.publish.",
|
|
35
45
|
inputSchema: t.Object({
|
|
36
|
-
slug:
|
|
37
|
-
title: t.String({
|
|
38
|
-
|
|
46
|
+
slug: PageSlug,
|
|
47
|
+
title: t.String({
|
|
48
|
+
minLength: 1,
|
|
49
|
+
maxLength: 160,
|
|
50
|
+
pattern: "\\S",
|
|
51
|
+
description: "Page title",
|
|
52
|
+
}),
|
|
53
|
+
html: t.Optional(t.String({
|
|
54
|
+
maxLength: 1_000_000,
|
|
55
|
+
description: "Hosted HTML content",
|
|
56
|
+
})),
|
|
39
57
|
visibility: t.Optional(enumSchema("Page visibility", PAGE_VISIBILITY)),
|
|
40
|
-
}),
|
|
58
|
+
}, STRICT_OBJECT),
|
|
41
59
|
async execute(input, ctx) {
|
|
42
60
|
const fields = input;
|
|
43
61
|
const body = await request(ctx, "/v1/pages", {
|
|
@@ -55,15 +73,23 @@ export function registerPageActions(rl) {
|
|
|
55
73
|
access: "write",
|
|
56
74
|
description: "Update a hosted HTML page's slug, title, visibility, or HTML.",
|
|
57
75
|
inputSchema: t.Object({
|
|
58
|
-
id:
|
|
59
|
-
slug: t.Optional(
|
|
60
|
-
title: t.Optional(t.String({
|
|
76
|
+
id: idSchema("Page ID"),
|
|
77
|
+
slug: t.Optional(PageSlug),
|
|
78
|
+
title: t.Optional(t.String({
|
|
79
|
+
minLength: 1,
|
|
80
|
+
maxLength: 160,
|
|
81
|
+
pattern: "\\S",
|
|
82
|
+
description: "Page title",
|
|
83
|
+
})),
|
|
61
84
|
visibility: t.Optional(enumSchema("Page visibility", PAGE_VISIBILITY)),
|
|
62
|
-
html: t.Optional(t.String({
|
|
63
|
-
|
|
85
|
+
html: t.Optional(t.String({
|
|
86
|
+
maxLength: 1_000_000,
|
|
87
|
+
description: "Hosted HTML content",
|
|
88
|
+
})),
|
|
89
|
+
}, STRICT_OBJECT),
|
|
64
90
|
async execute(input, ctx) {
|
|
65
91
|
const { id, ...fields } = input;
|
|
66
|
-
const body = await request(ctx, `/v1/pages/${
|
|
92
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(String(id))}`, {
|
|
67
93
|
method: "PATCH",
|
|
68
94
|
body: JSON.stringify(fields),
|
|
69
95
|
});
|
|
@@ -73,30 +99,30 @@ export function registerPageActions(rl) {
|
|
|
73
99
|
rl.registerAction("page.publish", {
|
|
74
100
|
access: "write",
|
|
75
101
|
description: "Publish a Shift Labs hosted HTML page.",
|
|
76
|
-
inputSchema: t.Object({ id:
|
|
102
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
77
103
|
async execute(input, ctx) {
|
|
78
104
|
const { id } = input;
|
|
79
|
-
const body = await request(ctx, `/v1/pages/${
|
|
105
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}/publish`, { method: "POST" });
|
|
80
106
|
return body.page;
|
|
81
107
|
},
|
|
82
108
|
});
|
|
83
109
|
rl.registerAction("page.archive", {
|
|
84
110
|
access: "write",
|
|
85
111
|
description: "Archive a Shift Labs page.",
|
|
86
|
-
inputSchema: t.Object({ id:
|
|
112
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
87
113
|
async execute(input, ctx) {
|
|
88
114
|
const { id } = input;
|
|
89
|
-
const body = await request(ctx, `/v1/pages/${
|
|
115
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}/archive`, { method: "POST" });
|
|
90
116
|
return body.page;
|
|
91
117
|
},
|
|
92
118
|
});
|
|
93
119
|
rl.registerAction("page.shares", {
|
|
94
120
|
access: "read",
|
|
95
121
|
description: "List shares for a Shift Labs page.",
|
|
96
|
-
inputSchema: t.Object({ pageId:
|
|
122
|
+
inputSchema: t.Object({ pageId: idSchema("Page ID") }, STRICT_OBJECT),
|
|
97
123
|
async execute(input, ctx) {
|
|
98
124
|
const { pageId } = input;
|
|
99
|
-
const body = await request(ctx, `/v1/pages/${
|
|
125
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(pageId)}/shares`);
|
|
100
126
|
return body.shares;
|
|
101
127
|
},
|
|
102
128
|
});
|
|
@@ -104,13 +130,17 @@ export function registerPageActions(rl) {
|
|
|
104
130
|
access: "write",
|
|
105
131
|
description: "Create a viewer share for a Shift Labs page.",
|
|
106
132
|
inputSchema: t.Object({
|
|
107
|
-
pageId:
|
|
108
|
-
email: t.String({
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
pageId: idSchema("Page ID"),
|
|
134
|
+
email: t.String({
|
|
135
|
+
minLength: 1,
|
|
136
|
+
format: "email",
|
|
137
|
+
description: "Viewer email address",
|
|
138
|
+
}),
|
|
139
|
+
expiresAt: t.Optional(timestampSchema("Optional ISO-8601 expiration")),
|
|
140
|
+
}, STRICT_OBJECT),
|
|
111
141
|
async execute(input, ctx) {
|
|
112
142
|
const { pageId, ...fields } = input;
|
|
113
|
-
const body = await request(ctx, `/v1/pages/${
|
|
143
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(String(pageId))}/shares`, {
|
|
114
144
|
method: "POST",
|
|
115
145
|
body: JSON.stringify(fields),
|
|
116
146
|
});
|
|
@@ -120,12 +150,10 @@ export function registerPageActions(rl) {
|
|
|
120
150
|
rl.registerAction("page.revokeShare", {
|
|
121
151
|
access: "write",
|
|
122
152
|
description: "Revoke a Shift Labs page share.",
|
|
123
|
-
inputSchema: t.Object({
|
|
124
|
-
shareId: t.String({ description: "Page share ID" }),
|
|
125
|
-
}),
|
|
153
|
+
inputSchema: t.Object({ shareId: idSchema("Page share ID") }, STRICT_OBJECT),
|
|
126
154
|
async execute(input, ctx) {
|
|
127
155
|
const { shareId } = input;
|
|
128
|
-
await request(ctx, `/v1/pages/shares/${
|
|
156
|
+
await request(ctx, `/v1/pages/shares/${pathSegment(shareId)}`, {
|
|
129
157
|
method: "DELETE",
|
|
130
158
|
});
|
|
131
159
|
return { success: true };
|
|
@@ -134,12 +162,10 @@ export function registerPageActions(rl) {
|
|
|
134
162
|
rl.registerAction("page.renderUrl", {
|
|
135
163
|
access: "read",
|
|
136
164
|
description: "Return the authenticated render URL for a page.",
|
|
137
|
-
inputSchema: t.Object({
|
|
138
|
-
pageId: t.String({ description: "Page ID" }),
|
|
139
|
-
}),
|
|
165
|
+
inputSchema: t.Object({ pageId: idSchema("Page ID") }, STRICT_OBJECT),
|
|
140
166
|
async execute(input, ctx) {
|
|
141
167
|
const { pageId } = input;
|
|
142
|
-
const body = await request(ctx, `/v1/pages/${
|
|
168
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(pageId)}`);
|
|
143
169
|
return {
|
|
144
170
|
url: pageRenderUrl(body.page.organizationId, body.page.slug),
|
|
145
171
|
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as t from "typebox";
|
|
2
|
+
import { cursorSchema, enumSchema, idSchema, PROJECT_STATUS, pathSegment, request, STRICT_OBJECT, STRICT_UPDATE_OBJECT, timestampSchema, } from "./shared.js";
|
|
3
|
+
const projectListFields = {
|
|
4
|
+
status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
|
|
5
|
+
leadUserId: t.Optional(idSchema("Lead user ID")),
|
|
6
|
+
includeArchived: t.Optional(t.Boolean()),
|
|
7
|
+
cursor: t.Optional(cursorSchema()),
|
|
8
|
+
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
9
|
+
};
|
|
10
|
+
function listParams(input) {
|
|
11
|
+
const params = new URLSearchParams();
|
|
12
|
+
for (const [key, value] of Object.entries((input ?? {}))) {
|
|
13
|
+
if (value !== undefined)
|
|
14
|
+
params.set(key, String(value));
|
|
15
|
+
}
|
|
16
|
+
return params;
|
|
17
|
+
}
|
|
18
|
+
export function registerProjectActions(rl) {
|
|
19
|
+
rl.registerAction("project.list", {
|
|
20
|
+
access: "read",
|
|
21
|
+
description: "List the first page of concrete Shift Labs Projects.",
|
|
22
|
+
inputSchema: t.Object(projectListFields, STRICT_OBJECT),
|
|
23
|
+
async execute(input, ctx) {
|
|
24
|
+
const body = await request(ctx, `/v1/projects?${listParams(input)}`);
|
|
25
|
+
return body.projects;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
rl.registerAction("project.listPage", {
|
|
29
|
+
access: "read",
|
|
30
|
+
description: "List a cursor-paginated page of concrete Projects.",
|
|
31
|
+
inputSchema: t.Object(projectListFields, STRICT_OBJECT),
|
|
32
|
+
async execute(input, ctx) {
|
|
33
|
+
return request(ctx, `/v1/projects?${listParams(input)}`);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
rl.registerAction("project.get", {
|
|
37
|
+
access: "read",
|
|
38
|
+
description: "Get a Project and its derived Issue progress.",
|
|
39
|
+
inputSchema: t.Object({ id: idSchema("Project ID") }, STRICT_OBJECT),
|
|
40
|
+
async execute(input, ctx) {
|
|
41
|
+
const { id } = input;
|
|
42
|
+
const [project, progress] = await Promise.all([
|
|
43
|
+
request(ctx, `/v1/projects/${pathSegment(id)}`),
|
|
44
|
+
request(ctx, `/v1/projects/${pathSegment(id)}/progress`),
|
|
45
|
+
]);
|
|
46
|
+
return { project: project.project, progress: progress.progress };
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
rl.registerAction("project.create", {
|
|
50
|
+
access: "write",
|
|
51
|
+
description: "Create a concrete Project container.",
|
|
52
|
+
inputSchema: t.Object({
|
|
53
|
+
name: t.String({
|
|
54
|
+
minLength: 1,
|
|
55
|
+
maxLength: 160,
|
|
56
|
+
pattern: "\\S",
|
|
57
|
+
description: "Project name",
|
|
58
|
+
}),
|
|
59
|
+
description: t.Optional(t.String({ maxLength: 20_000 })),
|
|
60
|
+
status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
|
|
61
|
+
leadUserId: t.Optional(idSchema("Lead user ID")),
|
|
62
|
+
startAt: t.Optional(timestampSchema("ISO-8601 start timestamp")),
|
|
63
|
+
targetAt: t.Optional(timestampSchema("ISO-8601 target timestamp")),
|
|
64
|
+
}, STRICT_OBJECT),
|
|
65
|
+
async execute(input, ctx) {
|
|
66
|
+
const fields = input;
|
|
67
|
+
assertDateOrder(fields.startAt, fields.targetAt);
|
|
68
|
+
const body = await request(ctx, "/v1/projects", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
body: JSON.stringify(fields),
|
|
71
|
+
});
|
|
72
|
+
return body.project;
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
rl.registerAction("project.update", {
|
|
76
|
+
access: "write",
|
|
77
|
+
description: "Update Project lifecycle, ownership, dates, or content.",
|
|
78
|
+
inputSchema: t.Object({
|
|
79
|
+
id: idSchema("Project ID"),
|
|
80
|
+
name: t.Optional(t.String({ minLength: 1, maxLength: 160, pattern: "\\S" })),
|
|
81
|
+
description: t.Optional(t.String({ maxLength: 20_000 })),
|
|
82
|
+
status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
|
|
83
|
+
leadUserId: t.Optional(t.Union([idSchema("Lead user ID"), t.Null()])),
|
|
84
|
+
startAt: t.Optional(t.Union([timestampSchema("ISO-8601 start timestamp"), t.Null()])),
|
|
85
|
+
targetAt: t.Optional(t.Union([timestampSchema("ISO-8601 target timestamp"), t.Null()])),
|
|
86
|
+
archived: t.Optional(t.Boolean()),
|
|
87
|
+
}, STRICT_UPDATE_OBJECT),
|
|
88
|
+
async execute(input, ctx) {
|
|
89
|
+
const { id, ...patch } = input;
|
|
90
|
+
assertDateOrder(patch.startAt, patch.targetAt);
|
|
91
|
+
const body = await request(ctx, `/v1/projects/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(patch) });
|
|
92
|
+
return body.project;
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function assertDateOrder(startAt, targetAt) {
|
|
97
|
+
if (typeof startAt === "string" &&
|
|
98
|
+
typeof targetAt === "string" &&
|
|
99
|
+
startAt > targetAt) {
|
|
100
|
+
throw new Error("Project targetAt must not precede startAt");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
+
export const STRICT_OBJECT = { additionalProperties: false };
|
|
3
|
+
export const STRICT_UPDATE_OBJECT = {
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
minProperties: 2,
|
|
6
|
+
};
|
|
7
|
+
export function idSchema(description) {
|
|
8
|
+
return t.String({ minLength: 1, pattern: "\\S", description });
|
|
9
|
+
}
|
|
10
|
+
export function cursorSchema(description = "Next-page cursor") {
|
|
11
|
+
return t.String({ minLength: 1, maxLength: 512, description });
|
|
12
|
+
}
|
|
13
|
+
export function timestampSchema(description) {
|
|
14
|
+
return t.String({
|
|
15
|
+
format: "date-time",
|
|
16
|
+
pattern: "Z$",
|
|
17
|
+
description,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
2
20
|
const SHIFT_LABS_API_URL = "https://d1ood6y5zobtne.cloudfront.net";
|
|
3
21
|
export function baseUrl() {
|
|
4
22
|
return `${SHIFT_LABS_API_URL}/`;
|
|
@@ -31,18 +49,17 @@ export function pageRenderUrl(organizationId, slug) {
|
|
|
31
49
|
return new URL(`/pages/${pathSegment(organizationId)}/${pathSegment(slug)}`, baseUrl()).toString();
|
|
32
50
|
}
|
|
33
51
|
export const ISSUE_STATUS = [
|
|
34
|
-
"
|
|
35
|
-
"
|
|
52
|
+
"backlog",
|
|
53
|
+
"todo",
|
|
36
54
|
"in_progress",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
55
|
+
"done",
|
|
56
|
+
"canceled",
|
|
39
57
|
];
|
|
40
|
-
export const
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"task",
|
|
58
|
+
export const PROJECT_STATUS = [
|
|
59
|
+
"planned",
|
|
60
|
+
"active",
|
|
61
|
+
"completed",
|
|
62
|
+
"canceled",
|
|
46
63
|
];
|
|
47
64
|
export const ISSUE_PRIORITY = [
|
|
48
65
|
"none",
|
|
@@ -51,7 +68,6 @@ export const ISSUE_PRIORITY = [
|
|
|
51
68
|
"high",
|
|
52
69
|
"urgent",
|
|
53
70
|
];
|
|
54
|
-
export const ISSUE_SEVERITY = ["info", "warning", "error", "critical"];
|
|
55
71
|
export const ISSUE_SOURCE = [
|
|
56
72
|
"user",
|
|
57
73
|
"agent",
|
|
@@ -59,6 +75,21 @@ export const ISSUE_SOURCE = [
|
|
|
59
75
|
"api",
|
|
60
76
|
"integration",
|
|
61
77
|
];
|
|
78
|
+
export const ISSUE_VIEW_VISIBILITY = ["personal", "organization"];
|
|
79
|
+
export const ISSUE_VIEW_LAYOUT = ["list", "board", "timeline"];
|
|
80
|
+
export const ISSUE_VIEW_GROUP = [
|
|
81
|
+
"status",
|
|
82
|
+
"assignee",
|
|
83
|
+
"priority",
|
|
84
|
+
"project",
|
|
85
|
+
];
|
|
86
|
+
export const ISSUE_VIEW_SORT = [
|
|
87
|
+
"manual",
|
|
88
|
+
"priority",
|
|
89
|
+
"startAt",
|
|
90
|
+
"dueAt",
|
|
91
|
+
"updatedAt",
|
|
92
|
+
];
|
|
62
93
|
export const PAGE_STATUS = ["draft", "published", "archived"];
|
|
63
94
|
export const PAGE_VISIBILITY = ["org", "invited"];
|
|
64
95
|
export const TRANSCRIPTION_LANGUAGE = ["auto", "en", "he"];
|