@mgsoftwarebv/mcp-server-bridge 3.5.22 → 3.5.23
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/index.js +902 -274
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -106143,6 +106143,20 @@ var TOOLS = [
|
|
|
106143
106143
|
items: { type: "string" },
|
|
106144
106144
|
description: "Filter by tag IDs"
|
|
106145
106145
|
},
|
|
106146
|
+
projectTag: {
|
|
106147
|
+
type: "string",
|
|
106148
|
+
description: "Filter by project label name \u2014 ticket's project must carry this label"
|
|
106149
|
+
},
|
|
106150
|
+
projectTags: {
|
|
106151
|
+
type: "array",
|
|
106152
|
+
items: { type: "string" },
|
|
106153
|
+
description: "Filter by project label names (OR)"
|
|
106154
|
+
},
|
|
106155
|
+
projectTagIds: {
|
|
106156
|
+
type: "array",
|
|
106157
|
+
items: { type: "string" },
|
|
106158
|
+
description: "Filter by project label tag IDs (OR)"
|
|
106159
|
+
},
|
|
106146
106160
|
pageSize: { type: "number", default: 20, maximum: 100 }
|
|
106147
106161
|
},
|
|
106148
106162
|
required: []
|
|
@@ -106156,6 +106170,20 @@ var TOOLS = [
|
|
|
106156
106170
|
properties: {
|
|
106157
106171
|
teamId: teamIdProp,
|
|
106158
106172
|
projectId: { type: "string", description: "Filter to one project UUID." },
|
|
106173
|
+
projectTag: {
|
|
106174
|
+
type: "string",
|
|
106175
|
+
description: "Filter by project label name on the ticket's project"
|
|
106176
|
+
},
|
|
106177
|
+
projectTags: {
|
|
106178
|
+
type: "array",
|
|
106179
|
+
items: { type: "string" },
|
|
106180
|
+
description: "Filter by project label names (OR)"
|
|
106181
|
+
},
|
|
106182
|
+
projectTagIds: {
|
|
106183
|
+
type: "array",
|
|
106184
|
+
items: { type: "string" },
|
|
106185
|
+
description: "Filter by project label tag IDs (OR)"
|
|
106186
|
+
},
|
|
106159
106187
|
priority: {
|
|
106160
106188
|
type: "string",
|
|
106161
106189
|
enum: ["low", "medium", "high", "critical"]
|
|
@@ -106194,6 +106222,9 @@ var TOOLS = [
|
|
|
106194
106222
|
description: "When assigneeId='all', group the items per assignee."
|
|
106195
106223
|
},
|
|
106196
106224
|
projectId: { type: "string" },
|
|
106225
|
+
projectTag: { type: "string" },
|
|
106226
|
+
projectTags: { type: "array", items: { type: "string" } },
|
|
106227
|
+
projectTagIds: { type: "array", items: { type: "string" } },
|
|
106197
106228
|
priority: {
|
|
106198
106229
|
type: "string",
|
|
106199
106230
|
enum: ["low", "medium", "high", "critical"]
|
|
@@ -106232,7 +106263,10 @@ var TOOLS = [
|
|
|
106232
106263
|
type: "boolean",
|
|
106233
106264
|
default: true,
|
|
106234
106265
|
description: "Include the unassigned bucket in the overview."
|
|
106235
|
-
}
|
|
106266
|
+
},
|
|
106267
|
+
projectTag: { type: "string" },
|
|
106268
|
+
projectTags: { type: "array", items: { type: "string" } },
|
|
106269
|
+
projectTagIds: { type: "array", items: { type: "string" } }
|
|
106236
106270
|
},
|
|
106237
106271
|
required: []
|
|
106238
106272
|
}
|
|
@@ -106867,13 +106901,27 @@ var TOOLS = [
|
|
|
106867
106901
|
},
|
|
106868
106902
|
{
|
|
106869
106903
|
name: "get-projects",
|
|
106870
|
-
description: "Get projects with optional filtering. Each project includes its ID
|
|
106904
|
+
description: "Get projects with optional filtering. Each project includes its ID, project labels (tags on the project itself \u2014 not ticket tags), and when archived, its archive timestamp/reason. Archived projects are hidden by default; pass status 'archived' or 'all' to include them.",
|
|
106871
106905
|
inputSchema: {
|
|
106872
106906
|
type: "object",
|
|
106873
106907
|
properties: {
|
|
106874
106908
|
teamId: teamIdProp,
|
|
106875
106909
|
customerId: { type: "string", description: "Filter by customer ID" },
|
|
106876
106910
|
q: { type: "string", description: "Search query for project name" },
|
|
106911
|
+
projectTag: {
|
|
106912
|
+
type: "string",
|
|
106913
|
+
description: "Filter by a single project label name (case-insensitive)"
|
|
106914
|
+
},
|
|
106915
|
+
projectTags: {
|
|
106916
|
+
type: "array",
|
|
106917
|
+
items: { type: "string" },
|
|
106918
|
+
description: "Filter by project label names (OR)"
|
|
106919
|
+
},
|
|
106920
|
+
projectTagIds: {
|
|
106921
|
+
type: "array",
|
|
106922
|
+
items: { type: "string" },
|
|
106923
|
+
description: "Filter by project label tag IDs (OR)"
|
|
106924
|
+
},
|
|
106877
106925
|
status: {
|
|
106878
106926
|
type: "string",
|
|
106879
106927
|
enum: ["active", "archived", "all"],
|
|
@@ -106887,7 +106935,7 @@ var TOOLS = [
|
|
|
106887
106935
|
},
|
|
106888
106936
|
{
|
|
106889
106937
|
name: "create-project",
|
|
106890
|
-
description: "Create a new project",
|
|
106938
|
+
description: "Create a new project. Optionally assign project labels (general team tags for grouping/filtering \u2014 distinct from ticket tags).",
|
|
106891
106939
|
inputSchema: {
|
|
106892
106940
|
type: "object",
|
|
106893
106941
|
properties: {
|
|
@@ -106895,6 +106943,16 @@ var TOOLS = [
|
|
|
106895
106943
|
name: { type: "string", description: "Project name" },
|
|
106896
106944
|
description: { type: "string" },
|
|
106897
106945
|
customerId: { type: "string" },
|
|
106946
|
+
projectTags: {
|
|
106947
|
+
type: "array",
|
|
106948
|
+
items: { type: "string" },
|
|
106949
|
+
description: "Project label names to assign (created if missing)"
|
|
106950
|
+
},
|
|
106951
|
+
projectTagIds: {
|
|
106952
|
+
type: "array",
|
|
106953
|
+
items: { type: "string" },
|
|
106954
|
+
description: "Project label tag IDs to assign"
|
|
106955
|
+
},
|
|
106898
106956
|
status: {
|
|
106899
106957
|
type: "string",
|
|
106900
106958
|
enum: ["active", "on_hold", "completed", "cancelled"],
|
|
@@ -106906,7 +106964,7 @@ var TOOLS = [
|
|
|
106906
106964
|
},
|
|
106907
106965
|
{
|
|
106908
106966
|
name: "update-project",
|
|
106909
|
-
description: "Update an existing project's fields (name, description, customer, rate, currency, billable, estimate, internal). Only provided fields change. Renaming a project renumbers its tickets. To retire a mistakenly-created project use archive-project (reversible) or delete-project (empty projects only). Find the project id via get-projects.",
|
|
106967
|
+
description: "Update an existing project's fields (name, description, customer, rate, currency, billable, estimate, internal, project labels). Only provided fields change. When projectTags/projectTagIds are provided, labels are replaced entirely. Renaming a project renumbers its tickets. To retire a mistakenly-created project use archive-project (reversible) or delete-project (empty projects only). Find the project id via get-projects.",
|
|
106910
106968
|
inputSchema: {
|
|
106911
106969
|
type: "object",
|
|
106912
106970
|
properties: {
|
|
@@ -106928,11 +106986,83 @@ var TOOLS = [
|
|
|
106928
106986
|
internal: {
|
|
106929
106987
|
type: "boolean",
|
|
106930
106988
|
description: "Whether this is an internal (no-customer) project"
|
|
106989
|
+
},
|
|
106990
|
+
projectTags: {
|
|
106991
|
+
type: "array",
|
|
106992
|
+
items: { type: "string" },
|
|
106993
|
+
description: "Replace project labels with these names (creates missing)"
|
|
106994
|
+
},
|
|
106995
|
+
projectTagIds: {
|
|
106996
|
+
type: "array",
|
|
106997
|
+
items: { type: "string" },
|
|
106998
|
+
description: "Replace project labels with these tag IDs"
|
|
106931
106999
|
}
|
|
106932
107000
|
},
|
|
106933
107001
|
required: ["id"]
|
|
106934
107002
|
}
|
|
106935
107003
|
},
|
|
107004
|
+
{
|
|
107005
|
+
name: "get-project-tags",
|
|
107006
|
+
description: "List project labels assigned to a project (metadata for grouping/filtering \u2014 not ticket tags).",
|
|
107007
|
+
inputSchema: {
|
|
107008
|
+
type: "object",
|
|
107009
|
+
properties: {
|
|
107010
|
+
teamId: teamIdProp,
|
|
107011
|
+
projectId: { type: "string", description: "Project ID" }
|
|
107012
|
+
},
|
|
107013
|
+
required: ["projectId"]
|
|
107014
|
+
}
|
|
107015
|
+
},
|
|
107016
|
+
{
|
|
107017
|
+
name: "set-project-tags",
|
|
107018
|
+
description: "Replace all project labels on a project. Pass projectTags (names) and/or projectTagIds. Missing names are created as general team tags.",
|
|
107019
|
+
inputSchema: {
|
|
107020
|
+
type: "object",
|
|
107021
|
+
properties: {
|
|
107022
|
+
teamId: teamIdProp,
|
|
107023
|
+
projectId: { type: "string", description: "Project ID" },
|
|
107024
|
+
projectTags: {
|
|
107025
|
+
type: "array",
|
|
107026
|
+
items: { type: "string" },
|
|
107027
|
+
description: "Project label names"
|
|
107028
|
+
},
|
|
107029
|
+
projectTagIds: {
|
|
107030
|
+
type: "array",
|
|
107031
|
+
items: { type: "string" },
|
|
107032
|
+
description: "Project label tag IDs"
|
|
107033
|
+
}
|
|
107034
|
+
},
|
|
107035
|
+
required: ["projectId"]
|
|
107036
|
+
}
|
|
107037
|
+
},
|
|
107038
|
+
{
|
|
107039
|
+
name: "add-project-tag",
|
|
107040
|
+
description: "Add one project label to a project (merge, does not remove existing labels).",
|
|
107041
|
+
inputSchema: {
|
|
107042
|
+
type: "object",
|
|
107043
|
+
properties: {
|
|
107044
|
+
teamId: teamIdProp,
|
|
107045
|
+
projectId: { type: "string", description: "Project ID" },
|
|
107046
|
+
projectTag: { type: "string", description: "Project label name" },
|
|
107047
|
+
projectTagId: { type: "string", description: "Project label tag ID" }
|
|
107048
|
+
},
|
|
107049
|
+
required: ["projectId"]
|
|
107050
|
+
}
|
|
107051
|
+
},
|
|
107052
|
+
{
|
|
107053
|
+
name: "remove-project-tag",
|
|
107054
|
+
description: "Remove one project label from a project.",
|
|
107055
|
+
inputSchema: {
|
|
107056
|
+
type: "object",
|
|
107057
|
+
properties: {
|
|
107058
|
+
teamId: teamIdProp,
|
|
107059
|
+
projectId: { type: "string", description: "Project ID" },
|
|
107060
|
+
projectTag: { type: "string", description: "Project label name" },
|
|
107061
|
+
projectTagId: { type: "string", description: "Project label tag ID" }
|
|
107062
|
+
},
|
|
107063
|
+
required: ["projectId"]
|
|
107064
|
+
}
|
|
107065
|
+
},
|
|
106936
107066
|
{
|
|
106937
107067
|
name: "archive-project",
|
|
106938
107068
|
description: "Safely archive (soft-retire) a project \u2014 the recommended way to clean up a mistakenly-created project. Reversible and non-destructive: it keeps all tickets, hours, trips, documents and other data, and only hides the project from get-projects by default. Use this instead of delete-project whenever a project has any history. Find the project id via get-projects. Note: the archive flag is stored in projects.settings.archivedAt; the dashboard UI does not yet read it, so the project still appears there.",
|
|
@@ -122886,116 +123016,550 @@ ${JSON.stringify(payload, null, 2)}
|
|
|
122886
123016
|
);
|
|
122887
123017
|
}
|
|
122888
123018
|
|
|
122889
|
-
// src/tools/
|
|
122890
|
-
|
|
122891
|
-
|
|
122892
|
-
"archived",
|
|
122893
|
-
"all"
|
|
122894
|
-
];
|
|
122895
|
-
var DEPENDENCY_LABELS2 = {
|
|
122896
|
-
tickets: "ticket(s)",
|
|
122897
|
-
timesheetEvents: "agenda/time entr(ies)",
|
|
122898
|
-
timesheetTemplates: "timesheet template(s)",
|
|
122899
|
-
trips: "trip(s)",
|
|
122900
|
-
tripTemplates: "trip template(s)"
|
|
122901
|
-
};
|
|
122902
|
-
function getProjectArchiveState(settings) {
|
|
122903
|
-
const obj = settings && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
122904
|
-
const archivedAt = typeof obj.archivedAt === "string" && obj.archivedAt.trim().length > 0 ? obj.archivedAt : null;
|
|
122905
|
-
const archiveReason = typeof obj.archiveReason === "string" && obj.archiveReason.trim().length > 0 ? obj.archiveReason : null;
|
|
122906
|
-
return { archived: archivedAt !== null, archivedAt, archiveReason };
|
|
122907
|
-
}
|
|
122908
|
-
function withArchiveSettings(settings, archivedAt, reason) {
|
|
122909
|
-
const base = settings && typeof settings === "object" && !Array.isArray(settings) ? { ...settings } : {};
|
|
122910
|
-
base.archivedAt = archivedAt;
|
|
122911
|
-
if (reason && reason.trim().length > 0) {
|
|
122912
|
-
base.archiveReason = reason.trim();
|
|
122913
|
-
}
|
|
122914
|
-
return base;
|
|
122915
|
-
}
|
|
122916
|
-
function totalProjectDependencies(counts) {
|
|
122917
|
-
return counts.tickets + counts.timesheetEvents + counts.timesheetTemplates + counts.trips + counts.tripTemplates;
|
|
123019
|
+
// src/tools/ticket-tags.ts
|
|
123020
|
+
function normalizeTagName(name21) {
|
|
123021
|
+
return name21.toLowerCase().trim();
|
|
122918
123022
|
}
|
|
122919
|
-
function
|
|
122920
|
-
|
|
123023
|
+
function formatTagList(tags2) {
|
|
123024
|
+
if (tags2.length === 0) return "";
|
|
123025
|
+
return tags2.map((t9) => t9.name).join(", ");
|
|
122921
123026
|
}
|
|
122922
|
-
function
|
|
122923
|
-
const
|
|
122924
|
-
|
|
123027
|
+
async function getTagsForTickets(ticketIds) {
|
|
123028
|
+
const result = /* @__PURE__ */ new Map();
|
|
123029
|
+
if (ticketIds.length === 0) return result;
|
|
123030
|
+
const rows = await db.select({
|
|
123031
|
+
ticketId: schema_exports.ticketTags.ticketId,
|
|
123032
|
+
id: schema_exports.tags.id,
|
|
123033
|
+
name: schema_exports.tags.name
|
|
123034
|
+
}).from(schema_exports.ticketTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.ticketTags.tagId)).where(inArray(schema_exports.ticketTags.ticketId, ticketIds)).orderBy(schema_exports.tags.name);
|
|
123035
|
+
for (const row of rows) {
|
|
123036
|
+
const existing = result.get(row.ticketId) ?? [];
|
|
123037
|
+
existing.push({ id: row.id, name: row.name });
|
|
123038
|
+
result.set(row.ticketId, existing);
|
|
123039
|
+
}
|
|
123040
|
+
return result;
|
|
122925
123041
|
}
|
|
122926
|
-
|
|
122927
|
-
|
|
122928
|
-
|
|
122929
|
-
|
|
122930
|
-
|
|
122931
|
-
|
|
122932
|
-
if (
|
|
122933
|
-
|
|
122934
|
-
|
|
122935
|
-
|
|
122936
|
-
|
|
122937
|
-
|
|
122938
|
-
}
|
|
122939
|
-
]
|
|
122940
|
-
};
|
|
123042
|
+
async function resolveTagFilterIds(teamId, input) {
|
|
123043
|
+
const ids = new Set(input.tagIds ?? []);
|
|
123044
|
+
const names = [
|
|
123045
|
+
...input.tag ? [input.tag] : [],
|
|
123046
|
+
...input.tags ?? []
|
|
123047
|
+
].map(normalizeTagName).filter(Boolean);
|
|
123048
|
+
if (names.length > 0) {
|
|
123049
|
+
const nameConditions = names.map(
|
|
123050
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123051
|
+
);
|
|
123052
|
+
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
123053
|
+
for (const row of rows) ids.add(row.id);
|
|
122941
123054
|
}
|
|
122942
|
-
|
|
122943
|
-
|
|
122944
|
-
|
|
122945
|
-
|
|
122946
|
-
|
|
122947
|
-
|
|
122948
|
-
|
|
122949
|
-
|
|
122950
|
-
|
|
122951
|
-
|
|
122952
|
-
|
|
122953
|
-
|
|
123055
|
+
return [...ids];
|
|
123056
|
+
}
|
|
123057
|
+
async function resolveTags(teamId, input) {
|
|
123058
|
+
const tags2 = [];
|
|
123059
|
+
const errors = [];
|
|
123060
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
123061
|
+
if (input.tagIds?.length) {
|
|
123062
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
123063
|
+
and(
|
|
123064
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123065
|
+
inArray(schema_exports.tags.id, input.tagIds)
|
|
123066
|
+
)
|
|
123067
|
+
);
|
|
123068
|
+
for (const row of rows) {
|
|
123069
|
+
if (seenIds.has(row.id)) continue;
|
|
123070
|
+
seenIds.add(row.id);
|
|
123071
|
+
tags2.push(row);
|
|
123072
|
+
}
|
|
123073
|
+
for (const id of input.tagIds) {
|
|
123074
|
+
if (!seenIds.has(id)) errors.push(`Unknown tag ID: ${id}`);
|
|
123075
|
+
}
|
|
122954
123076
|
}
|
|
122955
|
-
const
|
|
122956
|
-
if (
|
|
122957
|
-
|
|
122958
|
-
|
|
122959
|
-
|
|
122960
|
-
|
|
122961
|
-
|
|
123077
|
+
const rawNames = input.tagNames ?? [];
|
|
123078
|
+
if (rawNames.length === 0) return { tags: tags2, errors };
|
|
123079
|
+
const normalizedNames = [
|
|
123080
|
+
...new Set(rawNames.map(normalizeTagName).filter(Boolean))
|
|
123081
|
+
];
|
|
123082
|
+
if (normalizedNames.length === 0) return { tags: tags2, errors };
|
|
123083
|
+
const nameConditions = normalizedNames.map(
|
|
123084
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123085
|
+
);
|
|
123086
|
+
const existing = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
123087
|
+
const existingByNorm = /* @__PURE__ */ new Map();
|
|
123088
|
+
for (const tag2 of existing) {
|
|
123089
|
+
existingByNorm.set(normalizeTagName(tag2.name), tag2);
|
|
122962
123090
|
}
|
|
122963
|
-
const
|
|
122964
|
-
|
|
122965
|
-
|
|
122966
|
-
|
|
122967
|
-
|
|
122968
|
-
|
|
122969
|
-
|
|
122970
|
-
|
|
122971
|
-
return {
|
|
122972
|
-
content: [
|
|
122973
|
-
{
|
|
122974
|
-
type: "text",
|
|
122975
|
-
text: `Found ${rows.length} project(s)${status !== "all" ? ` (status: ${status})` : ""}:
|
|
122976
|
-
|
|
122977
|
-
${rows.map((p3) => {
|
|
122978
|
-
const archive = getProjectArchiveState(p3.settings);
|
|
122979
|
-
return `**${p3.name}** (ID: ${p3.id})${archive.archived ? " \u2014 ARCHIVED" : ""}
|
|
122980
|
-
${p3.description ? `Description: ${p3.description}
|
|
122981
|
-
` : ""}Created: ${new Date(p3.createdAt).toLocaleDateString()}
|
|
122982
|
-
${archive.archived ? `Archived: ${archive.archivedAt}${archive.archiveReason ? ` (${archive.archiveReason})` : ""}
|
|
122983
|
-
` : ""}`;
|
|
122984
|
-
}).join("\n") || "No projects found."}`
|
|
123091
|
+
for (const rawName of rawNames) {
|
|
123092
|
+
const norm = normalizeTagName(rawName);
|
|
123093
|
+
if (!norm) continue;
|
|
123094
|
+
const found = existingByNorm.get(norm);
|
|
123095
|
+
if (found) {
|
|
123096
|
+
if (!seenIds.has(found.id)) {
|
|
123097
|
+
seenIds.add(found.id);
|
|
123098
|
+
tags2.push(found);
|
|
122985
123099
|
}
|
|
122986
|
-
|
|
122987
|
-
|
|
122988
|
-
|
|
122989
|
-
|
|
122990
|
-
|
|
122991
|
-
|
|
122992
|
-
|
|
122993
|
-
|
|
122994
|
-
|
|
122995
|
-
|
|
122996
|
-
|
|
122997
|
-
|
|
123100
|
+
continue;
|
|
123101
|
+
}
|
|
123102
|
+
if (!input.createMissing) {
|
|
123103
|
+
errors.push(`Tag not found: ${rawName}`);
|
|
123104
|
+
continue;
|
|
123105
|
+
}
|
|
123106
|
+
try {
|
|
123107
|
+
const [created] = await db.insert(schema_exports.tags).values({
|
|
123108
|
+
teamId,
|
|
123109
|
+
name: norm,
|
|
123110
|
+
projectId: input.projectId ?? null
|
|
123111
|
+
}).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
|
|
123112
|
+
if (created) {
|
|
123113
|
+
seenIds.add(created.id);
|
|
123114
|
+
tags2.push(created);
|
|
123115
|
+
existingByNorm.set(norm, created);
|
|
123116
|
+
}
|
|
123117
|
+
} catch {
|
|
123118
|
+
const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
123119
|
+
and(
|
|
123120
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123121
|
+
sql`lower(${schema_exports.tags.name}) = ${norm}`,
|
|
123122
|
+
input.projectId ? or(
|
|
123123
|
+
eq(schema_exports.tags.projectId, input.projectId),
|
|
123124
|
+
isNull(schema_exports.tags.projectId)
|
|
123125
|
+
) : isNull(schema_exports.tags.projectId)
|
|
123126
|
+
)
|
|
123127
|
+
).limit(1);
|
|
123128
|
+
if (retry2 && !seenIds.has(retry2.id)) {
|
|
123129
|
+
seenIds.add(retry2.id);
|
|
123130
|
+
tags2.push(retry2);
|
|
123131
|
+
existingByNorm.set(norm, retry2);
|
|
123132
|
+
} else {
|
|
123133
|
+
errors.push(`Failed to create tag: ${rawName}`);
|
|
123134
|
+
}
|
|
123135
|
+
}
|
|
123136
|
+
}
|
|
123137
|
+
return { tags: tags2, errors };
|
|
123138
|
+
}
|
|
123139
|
+
async function syncTicketTags(ticketId, teamId, tagIds, mode = "replace") {
|
|
123140
|
+
const current = await db.select({ tagId: schema_exports.ticketTags.tagId }).from(schema_exports.ticketTags).where(eq(schema_exports.ticketTags.ticketId, ticketId));
|
|
123141
|
+
const currentIds = new Set(current.map((row) => row.tagId));
|
|
123142
|
+
let targetIds;
|
|
123143
|
+
if (mode === "remove") {
|
|
123144
|
+
targetIds = new Set(
|
|
123145
|
+
[...currentIds].filter((id) => !tagIds.includes(id))
|
|
123146
|
+
);
|
|
123147
|
+
} else if (mode === "merge") {
|
|
123148
|
+
targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
|
|
123149
|
+
} else {
|
|
123150
|
+
targetIds = new Set(tagIds);
|
|
123151
|
+
}
|
|
123152
|
+
const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
|
|
123153
|
+
const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
|
|
123154
|
+
if (toDelete.length > 0) {
|
|
123155
|
+
await db.delete(schema_exports.ticketTags).where(
|
|
123156
|
+
and(
|
|
123157
|
+
eq(schema_exports.ticketTags.ticketId, ticketId),
|
|
123158
|
+
inArray(schema_exports.ticketTags.tagId, toDelete)
|
|
123159
|
+
)
|
|
123160
|
+
);
|
|
123161
|
+
}
|
|
123162
|
+
if (toInsert.length > 0) {
|
|
123163
|
+
await db.insert(schema_exports.ticketTags).values(
|
|
123164
|
+
toInsert.map((tagId) => ({
|
|
123165
|
+
ticketId,
|
|
123166
|
+
tagId,
|
|
123167
|
+
teamId
|
|
123168
|
+
}))
|
|
123169
|
+
);
|
|
123170
|
+
}
|
|
123171
|
+
const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
|
|
123172
|
+
const tagNamesById = /* @__PURE__ */ new Map();
|
|
123173
|
+
if (tagIdsToDescribe.length > 0) {
|
|
123174
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
|
|
123175
|
+
for (const row of rows) tagNamesById.set(row.id, row.name);
|
|
123176
|
+
}
|
|
123177
|
+
return {
|
|
123178
|
+
added: toInsert.map((id) => tagNamesById.get(id) ?? id),
|
|
123179
|
+
removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
|
|
123180
|
+
};
|
|
123181
|
+
}
|
|
123182
|
+
|
|
123183
|
+
// src/tools/project-tags.ts
|
|
123184
|
+
async function getTagsForProjects(projectIds) {
|
|
123185
|
+
const result = /* @__PURE__ */ new Map();
|
|
123186
|
+
if (projectIds.length === 0) return result;
|
|
123187
|
+
const rows = await db.select({
|
|
123188
|
+
projectId: schema_exports.projectTags.projectId,
|
|
123189
|
+
id: schema_exports.tags.id,
|
|
123190
|
+
name: schema_exports.tags.name
|
|
123191
|
+
}).from(schema_exports.projectTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.projectTags.tagId)).where(inArray(schema_exports.projectTags.projectId, projectIds)).orderBy(schema_exports.tags.name);
|
|
123192
|
+
for (const row of rows) {
|
|
123193
|
+
const existing = result.get(row.projectId) ?? [];
|
|
123194
|
+
existing.push({ id: row.id, name: row.name });
|
|
123195
|
+
result.set(row.projectId, existing);
|
|
123196
|
+
}
|
|
123197
|
+
return result;
|
|
123198
|
+
}
|
|
123199
|
+
async function resolveProjectTagFilterIds(teamId, input) {
|
|
123200
|
+
const ids = new Set(input.projectTagIds ?? []);
|
|
123201
|
+
const names = [
|
|
123202
|
+
...input.projectTag ? [input.projectTag] : [],
|
|
123203
|
+
...input.projectTags ?? []
|
|
123204
|
+
].map(normalizeTagName).filter(Boolean);
|
|
123205
|
+
if (names.length > 0) {
|
|
123206
|
+
const nameConditions = names.map(
|
|
123207
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123208
|
+
);
|
|
123209
|
+
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(
|
|
123210
|
+
and(
|
|
123211
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123212
|
+
isNull(schema_exports.tags.projectId),
|
|
123213
|
+
or(...nameConditions)
|
|
123214
|
+
)
|
|
123215
|
+
);
|
|
123216
|
+
for (const row of rows) ids.add(row.id);
|
|
123217
|
+
}
|
|
123218
|
+
if (input.projectTagIds?.length) {
|
|
123219
|
+
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(
|
|
123220
|
+
and(
|
|
123221
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123222
|
+
isNull(schema_exports.tags.projectId),
|
|
123223
|
+
inArray(schema_exports.tags.id, input.projectTagIds)
|
|
123224
|
+
)
|
|
123225
|
+
);
|
|
123226
|
+
for (const row of rows) ids.add(row.id);
|
|
123227
|
+
}
|
|
123228
|
+
return [...ids];
|
|
123229
|
+
}
|
|
123230
|
+
async function resolveProjectTags(teamId, input) {
|
|
123231
|
+
const tags2 = [];
|
|
123232
|
+
const errors = [];
|
|
123233
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
123234
|
+
if (input.tagIds?.length) {
|
|
123235
|
+
const rows = await db.select({
|
|
123236
|
+
id: schema_exports.tags.id,
|
|
123237
|
+
name: schema_exports.tags.name,
|
|
123238
|
+
projectId: schema_exports.tags.projectId
|
|
123239
|
+
}).from(schema_exports.tags).where(
|
|
123240
|
+
and(
|
|
123241
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123242
|
+
inArray(schema_exports.tags.id, input.tagIds)
|
|
123243
|
+
)
|
|
123244
|
+
);
|
|
123245
|
+
for (const row of rows) {
|
|
123246
|
+
if (row.projectId) {
|
|
123247
|
+
errors.push(
|
|
123248
|
+
`Tag "${row.name}" is project-scoped (ticket tag) and cannot be used as a project label`
|
|
123249
|
+
);
|
|
123250
|
+
continue;
|
|
123251
|
+
}
|
|
123252
|
+
if (seenIds.has(row.id)) continue;
|
|
123253
|
+
seenIds.add(row.id);
|
|
123254
|
+
tags2.push({ id: row.id, name: row.name });
|
|
123255
|
+
}
|
|
123256
|
+
for (const id of input.tagIds) {
|
|
123257
|
+
if (!seenIds.has(id) && !errors.some((e6) => e6.includes(id))) {
|
|
123258
|
+
errors.push(`Unknown tag ID: ${id}`);
|
|
123259
|
+
}
|
|
123260
|
+
}
|
|
123261
|
+
}
|
|
123262
|
+
const rawNames = input.tagNames ?? [];
|
|
123263
|
+
if (rawNames.length === 0) return { tags: tags2, errors };
|
|
123264
|
+
const normalizedNames = [
|
|
123265
|
+
...new Set(rawNames.map(normalizeTagName).filter(Boolean))
|
|
123266
|
+
];
|
|
123267
|
+
if (normalizedNames.length === 0) return { tags: tags2, errors };
|
|
123268
|
+
const nameConditions = normalizedNames.map(
|
|
123269
|
+
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
123270
|
+
);
|
|
123271
|
+
const existing = await db.select({
|
|
123272
|
+
id: schema_exports.tags.id,
|
|
123273
|
+
name: schema_exports.tags.name,
|
|
123274
|
+
projectId: schema_exports.tags.projectId
|
|
123275
|
+
}).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
123276
|
+
const existingByNorm = /* @__PURE__ */ new Map();
|
|
123277
|
+
for (const tag2 of existing) {
|
|
123278
|
+
if (tag2.projectId) {
|
|
123279
|
+
existingByNorm.set(normalizeTagName(tag2.name), {
|
|
123280
|
+
id: tag2.id,
|
|
123281
|
+
name: tag2.name,
|
|
123282
|
+
_scoped: true
|
|
123283
|
+
});
|
|
123284
|
+
} else {
|
|
123285
|
+
existingByNorm.set(normalizeTagName(tag2.name), {
|
|
123286
|
+
id: tag2.id,
|
|
123287
|
+
name: tag2.name
|
|
123288
|
+
});
|
|
123289
|
+
}
|
|
123290
|
+
}
|
|
123291
|
+
for (const rawName of rawNames) {
|
|
123292
|
+
const norm = normalizeTagName(rawName);
|
|
123293
|
+
if (!norm) continue;
|
|
123294
|
+
const found = existingByNorm.get(norm);
|
|
123295
|
+
if (found) {
|
|
123296
|
+
if (found._scoped) {
|
|
123297
|
+
errors.push(
|
|
123298
|
+
`Tag "${rawName}" is project-scoped (ticket tag) and cannot be used as a project label`
|
|
123299
|
+
);
|
|
123300
|
+
continue;
|
|
123301
|
+
}
|
|
123302
|
+
if (!seenIds.has(found.id)) {
|
|
123303
|
+
seenIds.add(found.id);
|
|
123304
|
+
tags2.push({ id: found.id, name: found.name });
|
|
123305
|
+
}
|
|
123306
|
+
continue;
|
|
123307
|
+
}
|
|
123308
|
+
if (!input.createMissing) {
|
|
123309
|
+
errors.push(`Project label not found: ${rawName}`);
|
|
123310
|
+
continue;
|
|
123311
|
+
}
|
|
123312
|
+
try {
|
|
123313
|
+
const [created] = await db.insert(schema_exports.tags).values({
|
|
123314
|
+
teamId,
|
|
123315
|
+
name: norm,
|
|
123316
|
+
projectId: null
|
|
123317
|
+
}).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
|
|
123318
|
+
if (created) {
|
|
123319
|
+
seenIds.add(created.id);
|
|
123320
|
+
tags2.push(created);
|
|
123321
|
+
existingByNorm.set(norm, created);
|
|
123322
|
+
}
|
|
123323
|
+
} catch {
|
|
123324
|
+
const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
123325
|
+
and(
|
|
123326
|
+
eq(schema_exports.tags.teamId, teamId),
|
|
123327
|
+
sql`lower(${schema_exports.tags.name}) = ${norm}`,
|
|
123328
|
+
isNull(schema_exports.tags.projectId)
|
|
123329
|
+
)
|
|
123330
|
+
).limit(1);
|
|
123331
|
+
if (retry2 && !seenIds.has(retry2.id)) {
|
|
123332
|
+
seenIds.add(retry2.id);
|
|
123333
|
+
tags2.push(retry2);
|
|
123334
|
+
existingByNorm.set(norm, retry2);
|
|
123335
|
+
} else {
|
|
123336
|
+
errors.push(`Failed to create project label: ${rawName}`);
|
|
123337
|
+
}
|
|
123338
|
+
}
|
|
123339
|
+
}
|
|
123340
|
+
return { tags: tags2, errors };
|
|
123341
|
+
}
|
|
123342
|
+
async function syncProjectTags(projectId, teamId, tagIds, mode = "replace") {
|
|
123343
|
+
const current = await db.select({ tagId: schema_exports.projectTags.tagId }).from(schema_exports.projectTags).where(eq(schema_exports.projectTags.projectId, projectId));
|
|
123344
|
+
const currentIds = new Set(current.map((row) => row.tagId));
|
|
123345
|
+
let targetIds;
|
|
123346
|
+
if (mode === "remove") {
|
|
123347
|
+
targetIds = new Set(
|
|
123348
|
+
[...currentIds].filter((id) => !tagIds.includes(id))
|
|
123349
|
+
);
|
|
123350
|
+
} else if (mode === "merge") {
|
|
123351
|
+
targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
|
|
123352
|
+
} else {
|
|
123353
|
+
targetIds = new Set(tagIds);
|
|
123354
|
+
}
|
|
123355
|
+
const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
|
|
123356
|
+
const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
|
|
123357
|
+
if (toDelete.length > 0) {
|
|
123358
|
+
await db.delete(schema_exports.projectTags).where(
|
|
123359
|
+
and(
|
|
123360
|
+
eq(schema_exports.projectTags.projectId, projectId),
|
|
123361
|
+
inArray(schema_exports.projectTags.tagId, toDelete)
|
|
123362
|
+
)
|
|
123363
|
+
);
|
|
123364
|
+
}
|
|
123365
|
+
if (toInsert.length > 0) {
|
|
123366
|
+
await db.insert(schema_exports.projectTags).values(
|
|
123367
|
+
toInsert.map((tagId) => ({
|
|
123368
|
+
projectId,
|
|
123369
|
+
tagId,
|
|
123370
|
+
teamId
|
|
123371
|
+
}))
|
|
123372
|
+
);
|
|
123373
|
+
}
|
|
123374
|
+
const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
|
|
123375
|
+
const tagNamesById = /* @__PURE__ */ new Map();
|
|
123376
|
+
if (tagIdsToDescribe.length > 0) {
|
|
123377
|
+
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
|
|
123378
|
+
for (const row of rows) tagNamesById.set(row.id, row.name);
|
|
123379
|
+
}
|
|
123380
|
+
return {
|
|
123381
|
+
added: toInsert.map((id) => tagNamesById.get(id) ?? id),
|
|
123382
|
+
removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
|
|
123383
|
+
};
|
|
123384
|
+
}
|
|
123385
|
+
function projectTagFilterSql(tagIds, ticketsAlias = schema_exports.tickets) {
|
|
123386
|
+
return sql`EXISTS (
|
|
123387
|
+
SELECT 1 FROM ${schema_exports.projectTags} pt
|
|
123388
|
+
WHERE pt.project_id = ${ticketsAlias.projectId}
|
|
123389
|
+
AND pt.tag_id IN (${sql.join(
|
|
123390
|
+
tagIds.map((id) => sql`${id}`),
|
|
123391
|
+
sql`, `
|
|
123392
|
+
)})
|
|
123393
|
+
)`;
|
|
123394
|
+
}
|
|
123395
|
+
|
|
123396
|
+
// src/tools/project-cleanup-util.ts
|
|
123397
|
+
var PROJECT_STATUS_FILTERS = [
|
|
123398
|
+
"active",
|
|
123399
|
+
"archived",
|
|
123400
|
+
"all"
|
|
123401
|
+
];
|
|
123402
|
+
var DEPENDENCY_LABELS2 = {
|
|
123403
|
+
tickets: "ticket(s)",
|
|
123404
|
+
timesheetEvents: "agenda/time entr(ies)",
|
|
123405
|
+
timesheetTemplates: "timesheet template(s)",
|
|
123406
|
+
trips: "trip(s)",
|
|
123407
|
+
tripTemplates: "trip template(s)"
|
|
123408
|
+
};
|
|
123409
|
+
function getProjectArchiveState(settings) {
|
|
123410
|
+
const obj = settings && typeof settings === "object" && !Array.isArray(settings) ? settings : {};
|
|
123411
|
+
const archivedAt = typeof obj.archivedAt === "string" && obj.archivedAt.trim().length > 0 ? obj.archivedAt : null;
|
|
123412
|
+
const archiveReason = typeof obj.archiveReason === "string" && obj.archiveReason.trim().length > 0 ? obj.archiveReason : null;
|
|
123413
|
+
return { archived: archivedAt !== null, archivedAt, archiveReason };
|
|
123414
|
+
}
|
|
123415
|
+
function withArchiveSettings(settings, archivedAt, reason) {
|
|
123416
|
+
const base = settings && typeof settings === "object" && !Array.isArray(settings) ? { ...settings } : {};
|
|
123417
|
+
base.archivedAt = archivedAt;
|
|
123418
|
+
if (reason && reason.trim().length > 0) {
|
|
123419
|
+
base.archiveReason = reason.trim();
|
|
123420
|
+
}
|
|
123421
|
+
return base;
|
|
123422
|
+
}
|
|
123423
|
+
function totalProjectDependencies(counts) {
|
|
123424
|
+
return counts.tickets + counts.timesheetEvents + counts.timesheetTemplates + counts.trips + counts.tripTemplates;
|
|
123425
|
+
}
|
|
123426
|
+
function isProjectEmpty(counts) {
|
|
123427
|
+
return totalProjectDependencies(counts) === 0;
|
|
123428
|
+
}
|
|
123429
|
+
function formatProjectDependencies(counts) {
|
|
123430
|
+
const parts = Object.keys(DEPENDENCY_LABELS2).filter((key) => counts[key] > 0).map((key) => `${counts[key]} ${DEPENDENCY_LABELS2[key]}`);
|
|
123431
|
+
return parts.length > 0 ? parts.join(", ") : "no dependencies";
|
|
123432
|
+
}
|
|
123433
|
+
|
|
123434
|
+
// src/tools/projects.ts
|
|
123435
|
+
async function handleGetProjects(input) {
|
|
123436
|
+
const ctx = getAuthContext();
|
|
123437
|
+
const { customerId, q: q3, pageSize = 20, projectTag, projectTags: projectTags2, projectTagIds } = input;
|
|
123438
|
+
const status = input.status ?? "active";
|
|
123439
|
+
if (!PROJECT_STATUS_FILTERS.includes(status)) {
|
|
123440
|
+
return {
|
|
123441
|
+
content: [
|
|
123442
|
+
{
|
|
123443
|
+
type: "text",
|
|
123444
|
+
text: `Error: invalid status "${status}". Allowed: ${PROJECT_STATUS_FILTERS.join(", ")}.`
|
|
123445
|
+
}
|
|
123446
|
+
]
|
|
123447
|
+
};
|
|
123448
|
+
}
|
|
123449
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
123450
|
+
if (!resolved.ok) return resolved.response;
|
|
123451
|
+
const projectIds = await getAccessibleProjectIds(ctx.userId, resolved.teamId);
|
|
123452
|
+
if (projectIds.length === 0) {
|
|
123453
|
+
return {
|
|
123454
|
+
content: [
|
|
123455
|
+
{
|
|
123456
|
+
type: "text",
|
|
123457
|
+
text: "No projects found or no access to any projects."
|
|
123458
|
+
}
|
|
123459
|
+
]
|
|
123460
|
+
};
|
|
123461
|
+
}
|
|
123462
|
+
const filters = [inArray(schema_exports.projects.id, projectIds)];
|
|
123463
|
+
if (customerId) filters.push(eq(schema_exports.projects.customerId, customerId));
|
|
123464
|
+
if (q3) filters.push(ilike(schema_exports.projects.name, `%${q3}%`));
|
|
123465
|
+
if (status === "active") {
|
|
123466
|
+
filters.push(sql`${schema_exports.projects.settings} ->> 'archivedAt' IS NULL`);
|
|
123467
|
+
} else if (status === "archived") {
|
|
123468
|
+
filters.push(sql`${schema_exports.projects.settings} ->> 'archivedAt' IS NOT NULL`);
|
|
123469
|
+
}
|
|
123470
|
+
const labelFilterIds = await resolveProjectTagFilterIds(resolved.teamId, {
|
|
123471
|
+
projectTag,
|
|
123472
|
+
projectTags: projectTags2,
|
|
123473
|
+
projectTagIds
|
|
122998
123474
|
});
|
|
123475
|
+
if (projectTag || projectTags2?.length || projectTagIds?.length) {
|
|
123476
|
+
if (labelFilterIds.length === 0) {
|
|
123477
|
+
return {
|
|
123478
|
+
content: [
|
|
123479
|
+
{
|
|
123480
|
+
type: "text",
|
|
123481
|
+
text: "No projects found (no matching project labels for the given filter)."
|
|
123482
|
+
}
|
|
123483
|
+
]
|
|
123484
|
+
};
|
|
123485
|
+
}
|
|
123486
|
+
filters.push(
|
|
123487
|
+
sql`EXISTS (
|
|
123488
|
+
SELECT 1 FROM ${schema_exports.projectTags}
|
|
123489
|
+
WHERE ${schema_exports.projectTags.projectId} = ${schema_exports.projects.id}
|
|
123490
|
+
AND ${schema_exports.projectTags.tagId} IN (${sql.join(
|
|
123491
|
+
labelFilterIds.map((id) => sql`${id}`),
|
|
123492
|
+
sql`, `
|
|
123493
|
+
)})
|
|
123494
|
+
)`
|
|
123495
|
+
);
|
|
123496
|
+
}
|
|
123497
|
+
const rows = await db.select({
|
|
123498
|
+
id: schema_exports.projects.id,
|
|
123499
|
+
name: schema_exports.projects.name,
|
|
123500
|
+
description: schema_exports.projects.description,
|
|
123501
|
+
customerId: schema_exports.projects.customerId,
|
|
123502
|
+
createdAt: schema_exports.projects.createdAt,
|
|
123503
|
+
settings: schema_exports.projects.settings
|
|
123504
|
+
}).from(schema_exports.projects).where(and(...filters)).orderBy(asc(schema_exports.projects.name)).limit(Math.min(pageSize, 100));
|
|
123505
|
+
const tagsByProject = await getTagsForProjects(rows.map((p3) => p3.id));
|
|
123506
|
+
return {
|
|
123507
|
+
content: [
|
|
123508
|
+
{
|
|
123509
|
+
type: "text",
|
|
123510
|
+
text: `Found ${rows.length} project(s)${status !== "all" ? ` (status: ${status})` : ""}:
|
|
123511
|
+
|
|
123512
|
+
${rows.map((p3) => {
|
|
123513
|
+
const archive = getProjectArchiveState(p3.settings);
|
|
123514
|
+
const labels = tagsByProject.get(p3.id) ?? [];
|
|
123515
|
+
const labelLine = labels.length > 0 ? `Labels: ${formatTagList(labels)} (ids: ${labels.map((t9) => t9.id).join(", ")})
|
|
123516
|
+
` : "";
|
|
123517
|
+
return `**${p3.name}** (ID: ${p3.id})${archive.archived ? " \u2014 ARCHIVED" : ""}
|
|
123518
|
+
${p3.description ? `Description: ${p3.description}
|
|
123519
|
+
` : ""}` + labelLine + `Created: ${new Date(p3.createdAt).toLocaleDateString()}
|
|
123520
|
+
${archive.archived ? `Archived: ${archive.archivedAt}${archive.archiveReason ? ` (${archive.archiveReason})` : ""}
|
|
123521
|
+
` : ""}`;
|
|
123522
|
+
}).join("\n") || "No projects found."}`
|
|
123523
|
+
}
|
|
123524
|
+
]
|
|
123525
|
+
};
|
|
123526
|
+
}
|
|
123527
|
+
async function handleCreateProject(input) {
|
|
123528
|
+
const { name: name21, description, customerId, projectTags: projectTags2, projectTagIds } = input;
|
|
123529
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
123530
|
+
if (!resolved.ok) return resolved.response;
|
|
123531
|
+
const [created] = await db.insert(schema_exports.projects).values({
|
|
123532
|
+
teamId: resolved.teamId,
|
|
123533
|
+
name: name21,
|
|
123534
|
+
description: description ?? null,
|
|
123535
|
+
customerId: customerId ?? null
|
|
123536
|
+
}).returning({ id: schema_exports.projects.id });
|
|
123537
|
+
if (!created) {
|
|
123538
|
+
return textResponse7("Failed to create project.");
|
|
123539
|
+
}
|
|
123540
|
+
let labelLine = "";
|
|
123541
|
+
if (projectTags2?.length || projectTagIds?.length) {
|
|
123542
|
+
const { tags: tags2, errors } = await resolveProjectTags(resolved.teamId, {
|
|
123543
|
+
tagNames: projectTags2,
|
|
123544
|
+
tagIds: projectTagIds,
|
|
123545
|
+
createMissing: true
|
|
123546
|
+
});
|
|
123547
|
+
if (errors.length > 0) {
|
|
123548
|
+
return textResponse7(
|
|
123549
|
+
`Project created (ID: ${created.id}) but label errors:
|
|
123550
|
+
${errors.join("\n")}`
|
|
123551
|
+
);
|
|
123552
|
+
}
|
|
123553
|
+
if (tags2.length > 0) {
|
|
123554
|
+
await syncProjectTags(
|
|
123555
|
+
created.id,
|
|
123556
|
+
resolved.teamId,
|
|
123557
|
+
tags2.map((t9) => t9.id)
|
|
123558
|
+
);
|
|
123559
|
+
labelLine = `
|
|
123560
|
+
Labels: ${formatTagList(tags2)}`;
|
|
123561
|
+
}
|
|
123562
|
+
}
|
|
122999
123563
|
return {
|
|
123000
123564
|
content: [
|
|
123001
123565
|
{
|
|
@@ -123003,8 +123567,9 @@ async function handleCreateProject(input) {
|
|
|
123003
123567
|
text: `\u2705 **Project Created Successfully!**
|
|
123004
123568
|
|
|
123005
123569
|
Name: ${name21}
|
|
123570
|
+
ID: ${created.id}
|
|
123006
123571
|
${description ? `Description: ${description}
|
|
123007
|
-
` : ""}`
|
|
123572
|
+
` : ""}` + labelLine
|
|
123008
123573
|
}
|
|
123009
123574
|
]
|
|
123010
123575
|
};
|
|
@@ -123276,6 +123841,27 @@ async function handleUpdateProject(input) {
|
|
|
123276
123841
|
)
|
|
123277
123842
|
);
|
|
123278
123843
|
}
|
|
123844
|
+
let labelSyncNote = "";
|
|
123845
|
+
if (input.projectTags !== void 0 || input.projectTagIds !== void 0) {
|
|
123846
|
+
const { tags: tags2, errors } = await resolveProjectTags(owningTeamId, {
|
|
123847
|
+
tagNames: input.projectTags,
|
|
123848
|
+
tagIds: input.projectTagIds,
|
|
123849
|
+
createMissing: true
|
|
123850
|
+
});
|
|
123851
|
+
if (errors.length > 0) {
|
|
123852
|
+
return textResponse7(`Project label errors:
|
|
123853
|
+
${errors.join("\n")}`);
|
|
123854
|
+
}
|
|
123855
|
+
const syncResult = await syncProjectTags(
|
|
123856
|
+
id,
|
|
123857
|
+
owningTeamId,
|
|
123858
|
+
tags2.map((t9) => t9.id)
|
|
123859
|
+
);
|
|
123860
|
+
if (syncResult.added.length || syncResult.removed.length) {
|
|
123861
|
+
labelSyncNote = `
|
|
123862
|
+
Labels updated: +${syncResult.added.join(", ") || "none"} / -${syncResult.removed.join(", ") || "none"}`;
|
|
123863
|
+
}
|
|
123864
|
+
}
|
|
123279
123865
|
const [updated] = await db.select({
|
|
123280
123866
|
id: schema_exports.projects.id,
|
|
123281
123867
|
name: schema_exports.projects.name,
|
|
@@ -123301,6 +123887,13 @@ async function handleUpdateProject(input) {
|
|
|
123301
123887
|
);
|
|
123302
123888
|
}
|
|
123303
123889
|
lines.push(`Internal: ${updated.internal ? "yes" : "no"}`);
|
|
123890
|
+
if (labelSyncNote) lines.push(labelSyncNote.trim());
|
|
123891
|
+
const projectLabels = (await getTagsForProjects([id])).get(id) ?? [];
|
|
123892
|
+
if (projectLabels.length > 0) {
|
|
123893
|
+
lines.push(
|
|
123894
|
+
`Labels: ${formatTagList(projectLabels)} (ids: ${projectLabels.map((t9) => t9.id).join(", ")})`
|
|
123895
|
+
);
|
|
123896
|
+
}
|
|
123304
123897
|
if (willRename) {
|
|
123305
123898
|
lines.push("", "Note: tickets for this project were renumbered.");
|
|
123306
123899
|
}
|
|
@@ -123584,6 +124177,136 @@ Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
|
123584
124177
|
The project had no tickets, hours, trips, or templates. Any project-scoped config (member access, tags, slack/github links, team rates) was removed with it.`
|
|
123585
124178
|
);
|
|
123586
124179
|
}
|
|
124180
|
+
async function resolveProjectForTags(projectId, teamId) {
|
|
124181
|
+
const project = await loadProjectInTeam(projectId, teamId);
|
|
124182
|
+
if (!project) {
|
|
124183
|
+
return {
|
|
124184
|
+
ok: false,
|
|
124185
|
+
response: textResponse7(
|
|
124186
|
+
`Project ${projectId} not found, or it is not owned by this team.`
|
|
124187
|
+
)
|
|
124188
|
+
};
|
|
124189
|
+
}
|
|
124190
|
+
return { ok: true, project };
|
|
124191
|
+
}
|
|
124192
|
+
async function handleGetProjectTags(input) {
|
|
124193
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124194
|
+
if (!resolved.ok) return resolved.response;
|
|
124195
|
+
const projectResult = await resolveProjectForTags(
|
|
124196
|
+
input.projectId,
|
|
124197
|
+
resolved.teamId
|
|
124198
|
+
);
|
|
124199
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124200
|
+
const labels = (await getTagsForProjects([input.projectId])).get(input.projectId) ?? [];
|
|
124201
|
+
if (labels.length === 0) {
|
|
124202
|
+
return textResponse7(
|
|
124203
|
+
`Project "${projectResult.project.name}" (${input.projectId}) has no project labels.`
|
|
124204
|
+
);
|
|
124205
|
+
}
|
|
124206
|
+
return textResponse7(
|
|
124207
|
+
`Project labels for **${projectResult.project.name}** (${input.projectId}):
|
|
124208
|
+
|
|
124209
|
+
` + labels.map((t9) => `- ${t9.name} (id: ${t9.id})`).join("\n")
|
|
124210
|
+
);
|
|
124211
|
+
}
|
|
124212
|
+
async function handleSetProjectTags(input) {
|
|
124213
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124214
|
+
if (!resolved.ok) return resolved.response;
|
|
124215
|
+
const projectResult = await resolveProjectForTags(
|
|
124216
|
+
input.projectId,
|
|
124217
|
+
resolved.teamId
|
|
124218
|
+
);
|
|
124219
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124220
|
+
const owningTeamId = projectResult.project.teamId;
|
|
124221
|
+
const { tags: tags2, errors } = await resolveProjectTags(owningTeamId, {
|
|
124222
|
+
tagNames: input.projectTags,
|
|
124223
|
+
tagIds: input.projectTagIds,
|
|
124224
|
+
createMissing: true
|
|
124225
|
+
});
|
|
124226
|
+
if (errors.length > 0) {
|
|
124227
|
+
return textResponse7(`Project label errors:
|
|
124228
|
+
${errors.join("\n")}`);
|
|
124229
|
+
}
|
|
124230
|
+
const syncResult = await syncProjectTags(
|
|
124231
|
+
input.projectId,
|
|
124232
|
+
owningTeamId,
|
|
124233
|
+
tags2.map((t9) => t9.id)
|
|
124234
|
+
);
|
|
124235
|
+
return textResponse7(
|
|
124236
|
+
`\u2705 **Project labels set** for "${projectResult.project.name}" (${input.projectId})
|
|
124237
|
+
|
|
124238
|
+
Labels: ${formatTagList(tags2) || "(none)"}
|
|
124239
|
+
Added: ${syncResult.added.join(", ") || "none"}
|
|
124240
|
+
Removed: ${syncResult.removed.join(", ") || "none"}`
|
|
124241
|
+
);
|
|
124242
|
+
}
|
|
124243
|
+
async function handleAddProjectTag(input) {
|
|
124244
|
+
if (!input.projectTag && !input.projectTagId) {
|
|
124245
|
+
return textResponse7("Provide projectTag (name) or projectTagId.");
|
|
124246
|
+
}
|
|
124247
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124248
|
+
if (!resolved.ok) return resolved.response;
|
|
124249
|
+
const projectResult = await resolveProjectForTags(
|
|
124250
|
+
input.projectId,
|
|
124251
|
+
resolved.teamId
|
|
124252
|
+
);
|
|
124253
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124254
|
+
const owningTeamId = projectResult.project.teamId;
|
|
124255
|
+
const { tags: tags2, errors } = await resolveProjectTags(owningTeamId, {
|
|
124256
|
+
tagNames: input.projectTag ? [input.projectTag] : void 0,
|
|
124257
|
+
tagIds: input.projectTagId ? [input.projectTagId] : void 0,
|
|
124258
|
+
createMissing: true
|
|
124259
|
+
});
|
|
124260
|
+
if (errors.length > 0) {
|
|
124261
|
+
return textResponse7(`Project label errors:
|
|
124262
|
+
${errors.join("\n")}`);
|
|
124263
|
+
}
|
|
124264
|
+
if (tags2.length === 0) {
|
|
124265
|
+
return textResponse7("No matching project label to add.");
|
|
124266
|
+
}
|
|
124267
|
+
const syncResult = await syncProjectTags(
|
|
124268
|
+
input.projectId,
|
|
124269
|
+
owningTeamId,
|
|
124270
|
+
tags2.map((t9) => t9.id),
|
|
124271
|
+
"merge"
|
|
124272
|
+
);
|
|
124273
|
+
return textResponse7(
|
|
124274
|
+
`\u2705 **Project label added** to "${projectResult.project.name}"
|
|
124275
|
+
|
|
124276
|
+
Added: ${syncResult.added.join(", ") || tags2.map((t9) => t9.name).join(", ")}`
|
|
124277
|
+
);
|
|
124278
|
+
}
|
|
124279
|
+
async function handleRemoveProjectTag(input) {
|
|
124280
|
+
if (!input.projectTag && !input.projectTagId) {
|
|
124281
|
+
return textResponse7("Provide projectTag (name) or projectTagId.");
|
|
124282
|
+
}
|
|
124283
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
124284
|
+
if (!resolved.ok) return resolved.response;
|
|
124285
|
+
const projectResult = await resolveProjectForTags(
|
|
124286
|
+
input.projectId,
|
|
124287
|
+
resolved.teamId
|
|
124288
|
+
);
|
|
124289
|
+
if (!projectResult.ok) return projectResult.response;
|
|
124290
|
+
const owningTeamId = projectResult.project.teamId;
|
|
124291
|
+
const filterIds = await resolveProjectTagFilterIds(owningTeamId, {
|
|
124292
|
+
projectTag: input.projectTag,
|
|
124293
|
+
projectTagIds: input.projectTagId ? [input.projectTagId] : void 0
|
|
124294
|
+
});
|
|
124295
|
+
if (filterIds.length === 0) {
|
|
124296
|
+
return textResponse7("No matching project label found to remove.");
|
|
124297
|
+
}
|
|
124298
|
+
const syncResult = await syncProjectTags(
|
|
124299
|
+
input.projectId,
|
|
124300
|
+
owningTeamId,
|
|
124301
|
+
filterIds,
|
|
124302
|
+
"remove"
|
|
124303
|
+
);
|
|
124304
|
+
return textResponse7(
|
|
124305
|
+
`\u2705 **Project label removed** from "${projectResult.project.name}"
|
|
124306
|
+
|
|
124307
|
+
Removed: ${syncResult.removed.join(", ") || "none"}`
|
|
124308
|
+
);
|
|
124309
|
+
}
|
|
123587
124310
|
|
|
123588
124311
|
// src/tools/products.ts
|
|
123589
124312
|
var PRODUCT_STATUSES = ["active", "archived", "all"];
|
|
@@ -125988,170 +126711,6 @@ ${rendered}`
|
|
|
125988
126711
|
};
|
|
125989
126712
|
}
|
|
125990
126713
|
|
|
125991
|
-
// src/tools/ticket-tags.ts
|
|
125992
|
-
function normalizeTagName(name21) {
|
|
125993
|
-
return name21.toLowerCase().trim();
|
|
125994
|
-
}
|
|
125995
|
-
function formatTagList(tags2) {
|
|
125996
|
-
if (tags2.length === 0) return "";
|
|
125997
|
-
return tags2.map((t9) => t9.name).join(", ");
|
|
125998
|
-
}
|
|
125999
|
-
async function getTagsForTickets(ticketIds) {
|
|
126000
|
-
const result = /* @__PURE__ */ new Map();
|
|
126001
|
-
if (ticketIds.length === 0) return result;
|
|
126002
|
-
const rows = await db.select({
|
|
126003
|
-
ticketId: schema_exports.ticketTags.ticketId,
|
|
126004
|
-
id: schema_exports.tags.id,
|
|
126005
|
-
name: schema_exports.tags.name
|
|
126006
|
-
}).from(schema_exports.ticketTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.ticketTags.tagId)).where(inArray(schema_exports.ticketTags.ticketId, ticketIds)).orderBy(schema_exports.tags.name);
|
|
126007
|
-
for (const row of rows) {
|
|
126008
|
-
const existing = result.get(row.ticketId) ?? [];
|
|
126009
|
-
existing.push({ id: row.id, name: row.name });
|
|
126010
|
-
result.set(row.ticketId, existing);
|
|
126011
|
-
}
|
|
126012
|
-
return result;
|
|
126013
|
-
}
|
|
126014
|
-
async function resolveTagFilterIds(teamId, input) {
|
|
126015
|
-
const ids = new Set(input.tagIds ?? []);
|
|
126016
|
-
const names = [
|
|
126017
|
-
...input.tag ? [input.tag] : [],
|
|
126018
|
-
...input.tags ?? []
|
|
126019
|
-
].map(normalizeTagName).filter(Boolean);
|
|
126020
|
-
if (names.length > 0) {
|
|
126021
|
-
const nameConditions = names.map(
|
|
126022
|
-
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
126023
|
-
);
|
|
126024
|
-
const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
126025
|
-
for (const row of rows) ids.add(row.id);
|
|
126026
|
-
}
|
|
126027
|
-
return [...ids];
|
|
126028
|
-
}
|
|
126029
|
-
async function resolveTags(teamId, input) {
|
|
126030
|
-
const tags2 = [];
|
|
126031
|
-
const errors = [];
|
|
126032
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
126033
|
-
if (input.tagIds?.length) {
|
|
126034
|
-
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
126035
|
-
and(
|
|
126036
|
-
eq(schema_exports.tags.teamId, teamId),
|
|
126037
|
-
inArray(schema_exports.tags.id, input.tagIds)
|
|
126038
|
-
)
|
|
126039
|
-
);
|
|
126040
|
-
for (const row of rows) {
|
|
126041
|
-
if (seenIds.has(row.id)) continue;
|
|
126042
|
-
seenIds.add(row.id);
|
|
126043
|
-
tags2.push(row);
|
|
126044
|
-
}
|
|
126045
|
-
for (const id of input.tagIds) {
|
|
126046
|
-
if (!seenIds.has(id)) errors.push(`Unknown tag ID: ${id}`);
|
|
126047
|
-
}
|
|
126048
|
-
}
|
|
126049
|
-
const rawNames = input.tagNames ?? [];
|
|
126050
|
-
if (rawNames.length === 0) return { tags: tags2, errors };
|
|
126051
|
-
const normalizedNames = [
|
|
126052
|
-
...new Set(rawNames.map(normalizeTagName).filter(Boolean))
|
|
126053
|
-
];
|
|
126054
|
-
if (normalizedNames.length === 0) return { tags: tags2, errors };
|
|
126055
|
-
const nameConditions = normalizedNames.map(
|
|
126056
|
-
(name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
|
|
126057
|
-
);
|
|
126058
|
-
const existing = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
|
|
126059
|
-
const existingByNorm = /* @__PURE__ */ new Map();
|
|
126060
|
-
for (const tag2 of existing) {
|
|
126061
|
-
existingByNorm.set(normalizeTagName(tag2.name), tag2);
|
|
126062
|
-
}
|
|
126063
|
-
for (const rawName of rawNames) {
|
|
126064
|
-
const norm = normalizeTagName(rawName);
|
|
126065
|
-
if (!norm) continue;
|
|
126066
|
-
const found = existingByNorm.get(norm);
|
|
126067
|
-
if (found) {
|
|
126068
|
-
if (!seenIds.has(found.id)) {
|
|
126069
|
-
seenIds.add(found.id);
|
|
126070
|
-
tags2.push(found);
|
|
126071
|
-
}
|
|
126072
|
-
continue;
|
|
126073
|
-
}
|
|
126074
|
-
if (!input.createMissing) {
|
|
126075
|
-
errors.push(`Tag not found: ${rawName}`);
|
|
126076
|
-
continue;
|
|
126077
|
-
}
|
|
126078
|
-
try {
|
|
126079
|
-
const [created] = await db.insert(schema_exports.tags).values({
|
|
126080
|
-
teamId,
|
|
126081
|
-
name: norm,
|
|
126082
|
-
projectId: input.projectId ?? null
|
|
126083
|
-
}).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
|
|
126084
|
-
if (created) {
|
|
126085
|
-
seenIds.add(created.id);
|
|
126086
|
-
tags2.push(created);
|
|
126087
|
-
existingByNorm.set(norm, created);
|
|
126088
|
-
}
|
|
126089
|
-
} catch {
|
|
126090
|
-
const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
|
|
126091
|
-
and(
|
|
126092
|
-
eq(schema_exports.tags.teamId, teamId),
|
|
126093
|
-
sql`lower(${schema_exports.tags.name}) = ${norm}`,
|
|
126094
|
-
input.projectId ? or(
|
|
126095
|
-
eq(schema_exports.tags.projectId, input.projectId),
|
|
126096
|
-
isNull(schema_exports.tags.projectId)
|
|
126097
|
-
) : isNull(schema_exports.tags.projectId)
|
|
126098
|
-
)
|
|
126099
|
-
).limit(1);
|
|
126100
|
-
if (retry2 && !seenIds.has(retry2.id)) {
|
|
126101
|
-
seenIds.add(retry2.id);
|
|
126102
|
-
tags2.push(retry2);
|
|
126103
|
-
existingByNorm.set(norm, retry2);
|
|
126104
|
-
} else {
|
|
126105
|
-
errors.push(`Failed to create tag: ${rawName}`);
|
|
126106
|
-
}
|
|
126107
|
-
}
|
|
126108
|
-
}
|
|
126109
|
-
return { tags: tags2, errors };
|
|
126110
|
-
}
|
|
126111
|
-
async function syncTicketTags(ticketId, teamId, tagIds, mode = "replace") {
|
|
126112
|
-
const current = await db.select({ tagId: schema_exports.ticketTags.tagId }).from(schema_exports.ticketTags).where(eq(schema_exports.ticketTags.ticketId, ticketId));
|
|
126113
|
-
const currentIds = new Set(current.map((row) => row.tagId));
|
|
126114
|
-
let targetIds;
|
|
126115
|
-
if (mode === "remove") {
|
|
126116
|
-
targetIds = new Set(
|
|
126117
|
-
[...currentIds].filter((id) => !tagIds.includes(id))
|
|
126118
|
-
);
|
|
126119
|
-
} else if (mode === "merge") {
|
|
126120
|
-
targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
|
|
126121
|
-
} else {
|
|
126122
|
-
targetIds = new Set(tagIds);
|
|
126123
|
-
}
|
|
126124
|
-
const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
|
|
126125
|
-
const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
|
|
126126
|
-
if (toDelete.length > 0) {
|
|
126127
|
-
await db.delete(schema_exports.ticketTags).where(
|
|
126128
|
-
and(
|
|
126129
|
-
eq(schema_exports.ticketTags.ticketId, ticketId),
|
|
126130
|
-
inArray(schema_exports.ticketTags.tagId, toDelete)
|
|
126131
|
-
)
|
|
126132
|
-
);
|
|
126133
|
-
}
|
|
126134
|
-
if (toInsert.length > 0) {
|
|
126135
|
-
await db.insert(schema_exports.ticketTags).values(
|
|
126136
|
-
toInsert.map((tagId) => ({
|
|
126137
|
-
ticketId,
|
|
126138
|
-
tagId,
|
|
126139
|
-
teamId
|
|
126140
|
-
}))
|
|
126141
|
-
);
|
|
126142
|
-
}
|
|
126143
|
-
const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
|
|
126144
|
-
const tagNamesById = /* @__PURE__ */ new Map();
|
|
126145
|
-
if (tagIdsToDescribe.length > 0) {
|
|
126146
|
-
const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
|
|
126147
|
-
for (const row of rows) tagNamesById.set(row.id, row.name);
|
|
126148
|
-
}
|
|
126149
|
-
return {
|
|
126150
|
-
added: toInsert.map((id) => tagNamesById.get(id) ?? id),
|
|
126151
|
-
removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
|
|
126152
|
-
};
|
|
126153
|
-
}
|
|
126154
|
-
|
|
126155
126714
|
// src/tools/tags.ts
|
|
126156
126715
|
async function handleGetTags(input) {
|
|
126157
126716
|
const resolved = await resolveTeamId(input.teamId);
|
|
@@ -127476,6 +128035,9 @@ async function handleGetTickets(input) {
|
|
|
127476
128035
|
tag: tag2,
|
|
127477
128036
|
tags: tags2,
|
|
127478
128037
|
tagIds,
|
|
128038
|
+
projectTag,
|
|
128039
|
+
projectTags: projectTags2,
|
|
128040
|
+
projectTagIds,
|
|
127479
128041
|
pageSize = 20
|
|
127480
128042
|
} = input;
|
|
127481
128043
|
const resolved = await resolveTeamId(input.teamId);
|
|
@@ -127535,6 +128097,24 @@ async function handleGetTickets(input) {
|
|
|
127535
128097
|
)`
|
|
127536
128098
|
);
|
|
127537
128099
|
}
|
|
128100
|
+
const filterProjectLabelIds = await resolveProjectTagFilterIds(teamId, {
|
|
128101
|
+
projectTag,
|
|
128102
|
+
projectTags: projectTags2,
|
|
128103
|
+
projectTagIds
|
|
128104
|
+
});
|
|
128105
|
+
if (projectTag || projectTags2?.length || projectTagIds?.length) {
|
|
128106
|
+
if (filterProjectLabelIds.length === 0) {
|
|
128107
|
+
return {
|
|
128108
|
+
content: [
|
|
128109
|
+
{
|
|
128110
|
+
type: "text",
|
|
128111
|
+
text: "No tickets found (no matching project labels for the given filter)."
|
|
128112
|
+
}
|
|
128113
|
+
]
|
|
128114
|
+
};
|
|
128115
|
+
}
|
|
128116
|
+
filters.push(projectTagFilterSql(filterProjectLabelIds));
|
|
128117
|
+
}
|
|
127538
128118
|
const rows = await db.select({
|
|
127539
128119
|
id: schema_exports.tickets.id,
|
|
127540
128120
|
ticketNumber: schema_exports.tickets.ticketNumber,
|
|
@@ -127947,7 +128527,7 @@ function workStatesToStatuses(workStates) {
|
|
|
127947
128527
|
}
|
|
127948
128528
|
return [...set3];
|
|
127949
128529
|
}
|
|
127950
|
-
async function loadWorkQueue(scope, assignee, filters) {
|
|
128530
|
+
async function loadWorkQueue(scope, assignee, filters, teamId) {
|
|
127951
128531
|
const conditions = [
|
|
127952
128532
|
buildTicketAccessPredicate(
|
|
127953
128533
|
scope.teamIds,
|
|
@@ -127978,6 +128558,17 @@ async function loadWorkQueue(scope, assignee, filters) {
|
|
|
127978
128558
|
if (filters.projectId) {
|
|
127979
128559
|
conditions.push(eq(t8.projectId, filters.projectId));
|
|
127980
128560
|
}
|
|
128561
|
+
const labelFilterIds = await resolveProjectTagFilterIds(teamId, {
|
|
128562
|
+
projectTag: filters.projectTag,
|
|
128563
|
+
projectTags: filters.projectTags,
|
|
128564
|
+
projectTagIds: filters.projectTagIds
|
|
128565
|
+
});
|
|
128566
|
+
if (filters.projectTag || filters.projectTags?.length || filters.projectTagIds?.length) {
|
|
128567
|
+
if (labelFilterIds.length === 0) {
|
|
128568
|
+
return [];
|
|
128569
|
+
}
|
|
128570
|
+
conditions.push(projectTagFilterSql(labelFilterIds, t8));
|
|
128571
|
+
}
|
|
127981
128572
|
const rows = await db.select({
|
|
127982
128573
|
id: t8.id,
|
|
127983
128574
|
ticketNumber: t8.ticketNumber,
|
|
@@ -128030,10 +128621,12 @@ async function handleGetMyWorkQueue(input) {
|
|
|
128030
128621
|
const ctx = getAuthContext();
|
|
128031
128622
|
const scope = await resolveTeamScope(input.teamId);
|
|
128032
128623
|
if (!scope.ok) return scope.response;
|
|
128624
|
+
const teamId = input.teamId ?? ctx.teamId;
|
|
128033
128625
|
const rows = await loadWorkQueue(
|
|
128034
128626
|
scope,
|
|
128035
128627
|
{ mode: "one", userId: ctx.userId },
|
|
128036
|
-
input
|
|
128628
|
+
input,
|
|
128629
|
+
teamId
|
|
128037
128630
|
);
|
|
128038
128631
|
const items = rows.map(serializeItem);
|
|
128039
128632
|
return jsonResponse3({
|
|
@@ -128056,10 +128649,12 @@ async function handleGetAssigneeWorkQueue(input) {
|
|
|
128056
128649
|
const rawAssignee = input.assigneeId?.trim();
|
|
128057
128650
|
const isAll = rawAssignee === "all";
|
|
128058
128651
|
const targetUserId = !rawAssignee || rawAssignee === "me" ? ctx.userId : rawAssignee;
|
|
128652
|
+
const teamId = input.teamId ?? ctx.teamId;
|
|
128059
128653
|
const rows = await loadWorkQueue(
|
|
128060
128654
|
scope,
|
|
128061
128655
|
isAll ? { mode: "all" } : { mode: "one", userId: targetUserId },
|
|
128062
|
-
input
|
|
128656
|
+
input,
|
|
128657
|
+
teamId
|
|
128063
128658
|
);
|
|
128064
128659
|
const items = rows.map(serializeItem);
|
|
128065
128660
|
if (isAll && input.groupByAssignee) {
|
|
@@ -128095,11 +128690,13 @@ async function handleGetAssigneeWorkQueue(input) {
|
|
|
128095
128690
|
});
|
|
128096
128691
|
}
|
|
128097
128692
|
async function handleGetTeamWorkloadOverview(input) {
|
|
128693
|
+
const ctx = getAuthContext();
|
|
128098
128694
|
const scope = await resolveTeamScope(input.teamId);
|
|
128099
128695
|
if (!scope.ok) return scope.response;
|
|
128100
128696
|
const staleDays = input.staleDays ?? 7;
|
|
128101
128697
|
const upcomingDays = input.upcomingDays ?? 7;
|
|
128102
128698
|
const includeUnassigned = input.includeUnassigned ?? true;
|
|
128699
|
+
const teamId = input.teamId ?? ctx.teamId;
|
|
128103
128700
|
const conditions = [
|
|
128104
128701
|
buildTicketAccessPredicate(
|
|
128105
128702
|
scope.teamIds,
|
|
@@ -128112,6 +128709,21 @@ async function handleGetTeamWorkloadOverview(input) {
|
|
|
128112
128709
|
if (!includeUnassigned) {
|
|
128113
128710
|
conditions.push(sql`${t8.assigneeId} IS NOT NULL`);
|
|
128114
128711
|
}
|
|
128712
|
+
const labelFilterIds = await resolveProjectTagFilterIds(teamId, {
|
|
128713
|
+
projectTag: input.projectTag,
|
|
128714
|
+
projectTags: input.projectTags,
|
|
128715
|
+
projectTagIds: input.projectTagIds
|
|
128716
|
+
});
|
|
128717
|
+
if (input.projectTag || input.projectTags?.length || input.projectTagIds?.length) {
|
|
128718
|
+
if (labelFilterIds.length === 0) {
|
|
128719
|
+
return jsonResponse3({
|
|
128720
|
+
teamScope: scope.teamIds,
|
|
128721
|
+
config: { staleDays, upcomingDays, includeUnassigned },
|
|
128722
|
+
assignees: []
|
|
128723
|
+
});
|
|
128724
|
+
}
|
|
128725
|
+
conditions.push(projectTagFilterSql(labelFilterIds, t8));
|
|
128726
|
+
}
|
|
128115
128727
|
const rows = await db.select({
|
|
128116
128728
|
assigneeId: t8.assigneeId,
|
|
128117
128729
|
assigneeName: schema_exports.users.fullName,
|
|
@@ -130092,6 +130704,22 @@ function createMcpServer() {
|
|
|
130092
130704
|
return await handleCreateProject(asToolArgs(toolArgs));
|
|
130093
130705
|
case "update-project":
|
|
130094
130706
|
return await handleUpdateProject(asToolArgs(toolArgs));
|
|
130707
|
+
case "get-project-tags":
|
|
130708
|
+
return await handleGetProjectTags(
|
|
130709
|
+
asToolArgs(toolArgs)
|
|
130710
|
+
);
|
|
130711
|
+
case "set-project-tags":
|
|
130712
|
+
return await handleSetProjectTags(
|
|
130713
|
+
asToolArgs(toolArgs)
|
|
130714
|
+
);
|
|
130715
|
+
case "add-project-tag":
|
|
130716
|
+
return await handleAddProjectTag(
|
|
130717
|
+
asToolArgs(toolArgs)
|
|
130718
|
+
);
|
|
130719
|
+
case "remove-project-tag":
|
|
130720
|
+
return await handleRemoveProjectTag(
|
|
130721
|
+
asToolArgs(toolArgs)
|
|
130722
|
+
);
|
|
130095
130723
|
case "archive-project":
|
|
130096
130724
|
return await handleArchiveProject(
|
|
130097
130725
|
asToolArgs(toolArgs)
|