fix(services): resolve project identifier in queries/work packages and use results link
This commit is contained in:
@@ -32,6 +32,7 @@ test_project.description = 'Dedicated test project for openproject-mcp companion
|
|||||||
test_project.public = true
|
test_project.public = true
|
||||||
test_project.workspace_type = 'project' if test_project.respond_to?(:workspace_type=)
|
test_project.workspace_type = 'project' if test_project.respond_to?(:workspace_type=)
|
||||||
test_project.types = Type.all if Type.any?
|
test_project.types = Type.all if Type.any?
|
||||||
|
test_project.enabled_module_names = OpenProject::AccessControl.available_project_modules if defined?(OpenProject::AccessControl)
|
||||||
test_project.save!(validate: false)
|
test_project.save!(validate: false)
|
||||||
puts "[Seeder] Ensured project 'MCP Test Project' (ID: #{test_project.id}, identifier: #{test_project.identifier})."
|
puts "[Seeder] Ensured project 'MCP Test Project' (ID: #{test_project.id}, identifier: #{test_project.identifier})."
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -52,10 +52,8 @@ OPENPROJECT_API_KEY=${API_KEY}
|
|||||||
OPENPROJECT_TEST_PROJECT=${PROJECT_IDENTIFIER}
|
OPENPROJECT_TEST_PROJECT=${PROJECT_IDENTIFIER}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Also update .env.local if not present or populate key
|
# Also update .env.local
|
||||||
if [ ! -f "${ROOT_DIR}/.env.local" ]; then
|
cp "${ROOT_DIR}/.env.test" "${ROOT_DIR}/.env.local"
|
||||||
cp "${ROOT_DIR}/.env.test" "${ROOT_DIR}/.env.local"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "[seed.sh] Success! Test environment configured."
|
echo "[seed.sh] Success! Test environment configured."
|
||||||
|
|||||||
@@ -12,3 +12,24 @@ import type { OpenProjectClient } from "../client/api-client.ts";
|
|||||||
export function resolveClient(client?: OpenProjectClient): OpenProjectClient {
|
export function resolveClient(client?: OpenProjectClient): OpenProjectClient {
|
||||||
return client ?? getRequestContext().client;
|
return client ?? getRequestContext().client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a numeric project ID from either a number or string identifier.
|
||||||
|
* If the value is a number or integer string, returns the parsed integer.
|
||||||
|
* Otherwise, queries the OpenProject API for the project by identifier to get its numeric ID.
|
||||||
|
*/
|
||||||
|
export async function resolveProjectId(
|
||||||
|
projectId: number | string,
|
||||||
|
client?: OpenProjectClient
|
||||||
|
): Promise<number> {
|
||||||
|
if (typeof projectId === "number") {
|
||||||
|
return projectId;
|
||||||
|
}
|
||||||
|
const trimmed = projectId.trim();
|
||||||
|
if (/^\d+$/.test(trimmed)) {
|
||||||
|
return parseInt(trimmed, 10);
|
||||||
|
}
|
||||||
|
const opClient = resolveClient(client);
|
||||||
|
const response = await opClient.get<{ id: number }>(`projects/${encodeURIComponent(trimmed)}`);
|
||||||
|
return response.id;
|
||||||
|
}
|
||||||
|
|||||||
+23
-9
@@ -2,7 +2,7 @@
|
|||||||
* Queries Domain Service.
|
* Queries Domain Service.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { OpenProjectClient } from "../client/api-client.ts";
|
import { OpenProjectNotFoundError, type OpenProjectClient } from "../client/api-client.ts";
|
||||||
import {
|
import {
|
||||||
normalizeQuery,
|
normalizeQuery,
|
||||||
normalizeQuerySummary,
|
normalizeQuerySummary,
|
||||||
@@ -17,7 +17,7 @@ import type {
|
|||||||
QuerySummary,
|
QuerySummary,
|
||||||
WorkPackageSummary,
|
WorkPackageSummary,
|
||||||
} from "../client/types.ts";
|
} from "../client/types.ts";
|
||||||
import { resolveClient } from "./helper.ts";
|
import { resolveClient, resolveProjectId } from "./helper.ts";
|
||||||
|
|
||||||
export interface ListQueriesParams {
|
export interface ListQueriesParams {
|
||||||
projectId?: number | string;
|
projectId?: number | string;
|
||||||
@@ -43,9 +43,10 @@ export async function listQueries(
|
|||||||
if (params?.pageSize !== undefined) query.pageSize = params.pageSize;
|
if (params?.pageSize !== undefined) query.pageSize = params.pageSize;
|
||||||
if (params?.offset !== undefined) query.offset = params.offset;
|
if (params?.offset !== undefined) query.offset = params.offset;
|
||||||
|
|
||||||
if (params?.projectId !== undefined) {
|
if (params?.projectId !== undefined && params.projectId !== "") {
|
||||||
|
const numericProjectId = await resolveProjectId(params.projectId, opClient);
|
||||||
query.filters = JSON.stringify([
|
query.filters = JSON.stringify([
|
||||||
{ project: { operator: "=", values: [String(params.projectId)] } },
|
{ project: { operator: "=", values: [String(numericProjectId)] } },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +80,22 @@ export async function getQueryResults(
|
|||||||
if (params?.pageSize !== undefined) query.pageSize = params.pageSize;
|
if (params?.pageSize !== undefined) query.pageSize = params.pageSize;
|
||||||
if (params?.offset !== undefined) query.offset = params.offset;
|
if (params?.offset !== undefined) query.offset = params.offset;
|
||||||
|
|
||||||
const response = await opClient.get<HalCollection<HalResource>>(
|
try {
|
||||||
`queries/${id}/results`,
|
const response = await opClient.get<HalCollection<HalResource>>(
|
||||||
query
|
`queries/${id}/results`,
|
||||||
);
|
query
|
||||||
return unpackCollection(response, normalizeWorkPackageSummary);
|
);
|
||||||
|
return unpackCollection(response, normalizeWorkPackageSummary);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof OpenProjectNotFoundError) {
|
||||||
|
// In OpenProject API v3, query results endpoint is dynamically linked in query._links.results.href
|
||||||
|
const queryDetail = await opClient.get<HalResource>(`queries/${id}`);
|
||||||
|
const resultsHref = queryDetail._links?.results?.href;
|
||||||
|
if (typeof resultsHref === "string" && resultsHref.length > 0) {
|
||||||
|
const response = await opClient.get<HalCollection<HalResource>>(resultsHref, query);
|
||||||
|
return unpackCollection(response, normalizeWorkPackageSummary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import type {
|
|||||||
WorkPackageFilterParams,
|
WorkPackageFilterParams,
|
||||||
WorkPackageSummary,
|
WorkPackageSummary,
|
||||||
} from "../client/types.ts";
|
} from "../client/types.ts";
|
||||||
import { resolveClient } from "./helper.ts";
|
import { resolveClient, resolveProjectId } from "./helper.ts";
|
||||||
|
|
||||||
export interface ListWorkPackagesParams extends WorkPackageFilterParams {
|
export interface ListWorkPackagesParams extends WorkPackageFilterParams {
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
@@ -46,7 +46,12 @@ export async function listWorkPackages(
|
|||||||
if (params?.sortBy !== undefined) query.sortBy = params.sortBy;
|
if (params?.sortBy !== undefined) query.sortBy = params.sortBy;
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
const filters = buildWorkPackageFilters(params);
|
let filterParams = params;
|
||||||
|
if (params.projectId !== undefined && params.projectId !== "") {
|
||||||
|
const numericProjectId = await resolveProjectId(params.projectId, opClient);
|
||||||
|
filterParams = { ...params, projectId: numericProjectId };
|
||||||
|
}
|
||||||
|
const filters = buildWorkPackageFilters(filterParams);
|
||||||
if (filters.length > 0) {
|
if (filters.length > 0) {
|
||||||
query.filters = JSON.stringify(filters);
|
query.filters = JSON.stringify(filters);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user