From 9001540400c35d63220809dc8e81021b3d4eb445 Mon Sep 17 00:00:00 2001 From: Nina Bernick <40436239+ninabernick@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:23:00 -0800 Subject: [PATCH] feat: federate WorkflowsAggregate endpoint (#108) Co-authored-by: Nina Bernick Co-authored-by: Nina Bernick --- .meshrc.yaml | 7 ++ .../workflows-aggregate-query.graphql | 23 ++++ .../workflowRunsAggregateRequest.json | 105 ++++++++++++++++++ .../workflowRunsAggregateResponse.json | 26 +++++ resolvers.ts | 38 +++++++ sample-requests/workflowRunsAggregate.json | 50 +++++++++ sample-responses/workflowRunsAggregate.json | 8 ++ tests/WorkflowRunsAggregateQuery.test.ts | 49 ++++++++ .../__snapshots__/UnifiedSchema.test.ts.snap | 47 ++++++++ 9 files changed, 353 insertions(+) create mode 100644 example-queries/workflows-aggregate-query.graphql create mode 100644 json-schemas/workflowRunsAggregateRequest.json create mode 100644 json-schemas/workflowRunsAggregateResponse.json create mode 100644 sample-requests/workflowRunsAggregate.json create mode 100644 sample-responses/workflowRunsAggregate.json create mode 100644 tests/WorkflowRunsAggregateQuery.test.ts diff --git a/.meshrc.yaml b/.meshrc.yaml index 75098704..aab4028e 100644 --- a/.meshrc.yaml +++ b/.meshrc.yaml @@ -173,6 +173,13 @@ sources: method: POST requestSchema: ./json-schemas/workflowRunsRequest.json responseSchema: ./json-schemas/workflowRunsResponse.json + - type: Query + field: workflowRunsAggregate + path: /projects.json + method: GET + requestSchema: ./json-schemas/workflowRunsAggregateRequest.json + responseSchema: ./json-schemas/workflowRunsAggregateResponse.json + responseTypeName: workflowRunsAggregate - type: Query field: ZipLink path: /workflow_runs/{args.workflowRunId}/zip_link.json diff --git a/example-queries/workflows-aggregate-query.graphql b/example-queries/workflows-aggregate-query.graphql new file mode 100644 index 00000000..2fd67225 --- /dev/null +++ b/example-queries/workflows-aggregate-query.graphql @@ -0,0 +1,23 @@ +query workflowRunsAggregateQuery { + workflowRunsAggregate( + input: { + where: { + id: { + _in: ["1", "2", "3"] + } + }, + todoRemove: { + domain: "my_data", + offset: 0, + visibility: "public", + search: "abc", + time: ["20240214", "20240222"] + } + } + ) { + collectionId + amrRunsCount + cgRunsCount + mngsRunsCount + } +} diff --git a/json-schemas/workflowRunsAggregateRequest.json b/json-schemas/workflowRunsAggregateRequest.json new file mode 100644 index 00000000..93a08d92 --- /dev/null +++ b/json-schemas/workflowRunsAggregateRequest.json @@ -0,0 +1,105 @@ +{ + "type": "object", + "properties": { + "where": { + "type": "object", + "properties": { + "id": { + "type": "object", + "properties": { + "_in": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "todoRemove": { + "type": "object", + "properties": { + "projectId": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "host": { + "type": "array", + "items": { + "type": "integer" + } + }, + "locationV2": { + "type": "array", + "items": { + "type": "string" + } + }, + "taxonThresholds": { + "type": "array", + "items": { + "type": "object", + "properties": { + "metric": { + "type": "string" + }, + "count_type": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "annotations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + } + }, + "tissue": { + "type": "array", + "items": { + "type": "string" + } + }, + "visibility": { + "type": "string" + }, + "time": { + "type": "array", + "items": { + "type": "string" + } + }, + "taxaLevels": { + "type": "array", + "items": { + "type": "string" + } + }, + "taxon": { + "type": "array", + "items": { + "type": "integer" + } + }, + "search": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/json-schemas/workflowRunsAggregateResponse.json b/json-schemas/workflowRunsAggregateResponse.json new file mode 100644 index 00000000..5d051a4a --- /dev/null +++ b/json-schemas/workflowRunsAggregateResponse.json @@ -0,0 +1,26 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties": { + "collectionId": { + "type": "string" + }, + "mngsRunsCount": { + "type": "integer" + }, + "cgRunsCount": { + "type": "integer" + }, + "amrRunsCount": { + "type": "integer" + } + }, + "required": [ + "collectionId", + "mngsRunsCount", + "cgRunsCount", + "amrRunsCount" + ] + } +} \ No newline at end of file diff --git a/resolvers.ts b/resolvers.ts index fd3ccad9..8f58af84 100644 --- a/resolvers.ts +++ b/resolvers.ts @@ -4,6 +4,7 @@ import { query_consensusGenomes_items, query_samples_items, query_sequencingReads_items, + query_workflowRunsAggregate_items, query_workflowRuns_items, } from "./.mesh"; import { @@ -787,6 +788,43 @@ export const resolvers: Resolvers = { }) ); }, + workflowRunsAggregate: async (root, args, context, info) => { + const input = args.input; + + const { projects } = await get("/projects.json" + + formatUrlParams({ + projectId: input?.todoRemove?.projectId, + domain: input?.todoRemove?.domain, + limit: TEN_MILLION, + listAllIds: false, + offset: 0, + host: input?.todoRemove?.host, + locationV2: input?.todoRemove?.locationV2, + taxonThresholds: input?.todoRemove?.taxonThresholds, + annotations: input?.todoRemove?.annotations, + search: input?.todoRemove?.search, + tissue: input?.todoRemove?.tissue, + visibility: input?.todoRemove?.visibility, + time: input?.todoRemove?.time, + taxaLevels: input?.todoRemove?.taxaLevels, + taxon: input?.todoRemove?.taxon, + }), args, context); + + if (!projects?.length) { + return []; + } + return projects.map((project): query_workflowRunsAggregate_items => { + return { + collectionId: project.id.toString(), + mngsRunsCount: project.sample_counts.mngs_runs_count, + cgRunsCount: + project.sample_counts.cg_runs_count, + amrRunsCount: project.sample_counts.amr_runs_count, + }; + }); + + // TODO (nina): call nextgen in addition to rails to get CG count + }, ZipLink: async (root, args, context, info) => { const res = await getFullResponse( `/workflow_runs/${args.workflowRunId}/zip_link.json`, diff --git a/sample-requests/workflowRunsAggregate.json b/sample-requests/workflowRunsAggregate.json new file mode 100644 index 00000000..63672740 --- /dev/null +++ b/sample-requests/workflowRunsAggregate.json @@ -0,0 +1,50 @@ +{ + "where": { + "id": { + "_in": { + "items": [ + "1234" + ] + } + } + }, + "todoRemove": { + "projectId": "1056", + "domain": "my_data", + "host": [ + 1, + 11 + ], + "locationV2": [ + "California, USA" + ], + "taxonThresholds": [ + { + "metric": "rpm", + "count_type": "NT", + "operator": ">=", + "value": "1" + } + ], + "annotations": [ + { + "name": "Hit" + } + ], + "tissue": [ + "CSF" + ], + "visibility": "public", + "time": [ + "20240205", + "20240213" + ], + "taxaLevels": [ + "species" + ], + "taxon": [ + 244366 + ], + "search": "abc" + } +} \ No newline at end of file diff --git a/sample-responses/workflowRunsAggregate.json b/sample-responses/workflowRunsAggregate.json new file mode 100644 index 00000000..db64a41e --- /dev/null +++ b/sample-responses/workflowRunsAggregate.json @@ -0,0 +1,8 @@ +[ + { + "collectionId": "123", + "mngsRunsCount": 0, + "cgRunsCount": 5, + "amrRunsCount": 0 + } +] \ No newline at end of file diff --git a/tests/WorkflowRunsAggregateQuery.test.ts b/tests/WorkflowRunsAggregateQuery.test.ts new file mode 100644 index 00000000..c72e4740 --- /dev/null +++ b/tests/WorkflowRunsAggregateQuery.test.ts @@ -0,0 +1,49 @@ +import { ExecuteMeshFn } from "@graphql-mesh/runtime"; +import { getMeshInstance } from "./utils/MeshInstance"; + +import * as httpUtils from "../utils/httpUtils"; +import { getExampleQuery } from "./utils/ExampleQueryFiles"; +jest.spyOn(httpUtils, "get"); + +beforeEach(() => { + (httpUtils.get as jest.Mock).mockClear(); +}); + +describe("workflows aggregate query:", () => { + let execute: ExecuteMeshFn; + + beforeEach(async () => { + const mesh$ = await getMeshInstance(); + ({ execute } = mesh$); + }); + + it("Returns aggregate counts for each workflow", async () => { + (httpUtils.get as jest.Mock).mockImplementation(() => ({ + projects: [ + { + id: 1, + sample_counts: { + cg_runs_count: 1, + amr_runs_count: 2, + mngs_runs_count: 3 + } + } + ], + })); + + const query = getExampleQuery("workflows-aggregate-query"); + + const response = await execute(query, {}); + expect(httpUtils.get).toHaveBeenCalledWith( + "/projects.json?&domain=my_data&limit=10000000&listAllIds=false&offset=0&search=abc&visibility=public&time[]=20240214&time[]=20240222", + expect.anything(), + expect.anything() + ); + + expect(response.data.workflowRunsAggregate).toHaveLength(1); + expect(response.data.workflowRunsAggregate[0].collectionId).toBe("1"); + expect(response.data.workflowRunsAggregate[0].amrRunsCount).toBe(2); + expect(response.data.workflowRunsAggregate[0].cgRunsCount).toBe(1); + expect(response.data.workflowRunsAggregate[0].mngsRunsCount).toBe(3); + }); +}); diff --git a/tests/__snapshots__/UnifiedSchema.test.ts.snap b/tests/__snapshots__/UnifiedSchema.test.ts.snap index ccaefcdd..afae4995 100644 --- a/tests/__snapshots__/UnifiedSchema.test.ts.snap +++ b/tests/__snapshots__/UnifiedSchema.test.ts.snap @@ -75,6 +75,7 @@ type Query @globalOptions(sourceName: "CZIDREST", endpoint: "http://web:3001/") UserBlastAnnotations(sampleId: String, workflowVersionId: String): [query_UserBlastAnnotations_items] @httpOperation(path: "/samples/{args.sampleId}/report_v2?&id={args.sampleId}&pipeline_version={args.workflowVersionId}&merge_nt_nr=false", httpMethod: GET) ValidateUserCanDeleteObjects(input: queryInput_ValidateUserCanDeleteObjects_input_Input): ValidateUserCanDeleteObjects @httpOperation(path: "/samples/validate_user_can_delete_objects.json", httpMethod: POST) workflowRuns(input: queryInput_workflowRuns_input_Input): [query_workflowRuns_items] @httpOperation(path: "/workflow_runs.json", httpMethod: POST) + workflowRunsAggregate(input: queryInput_workflowRunsAggregate_input_Input): [query_workflowRunsAggregate_items] @httpOperation(path: "/projects.json", httpMethod: GET) ZipLink(workflowRunId: String): ZipLink @httpOperation(path: "/workflow_runs/{args.workflowRunId}/zip_link.json", httpMethod: GET) GraphQLFederationVersion: GraphQLFederationVersion } @@ -3316,6 +3317,52 @@ input queryInput_workflowRuns_input_entityInputsInput_where_fieldName_Input { _eq: String } +type query_workflowRunsAggregate_items { + collectionId: String! + mngsRunsCount: Int! + cgRunsCount: Int! + amrRunsCount: Int! +} + +input queryInput_workflowRunsAggregate_input_Input { + where: queryInput_workflowRunsAggregate_input_where_Input + todoRemove: queryInput_workflowRunsAggregate_input_todoRemove_Input +} + +input queryInput_workflowRunsAggregate_input_where_Input { + id: queryInput_workflowRunsAggregate_input_where_id_Input +} + +input queryInput_workflowRunsAggregate_input_where_id_Input { + _in: [String] +} + +input queryInput_workflowRunsAggregate_input_todoRemove_Input { + projectId: String + domain: String + host: [Int] + locationV2: [String] + taxonThresholds: [queryInput_workflowRunsAggregate_input_todoRemove_taxonThresholds_items_Input] + annotations: [queryInput_workflowRunsAggregate_input_todoRemove_annotations_items_Input] + tissue: [String] + visibility: String + time: [String] + taxaLevels: [String] + taxon: [Int] + search: String +} + +input queryInput_workflowRunsAggregate_input_todoRemove_taxonThresholds_items_Input { + metric: String + count_type: String + operator: String + value: String +} + +input queryInput_workflowRunsAggregate_input_todoRemove_annotations_items_Input { + name: String +} + type ZipLink { url: String error: String