diff --git a/codegen.yml b/codegen.yml index 577f417..9fe3e5b 100644 --- a/codegen.yml +++ b/codegen.yml @@ -18,4 +18,4 @@ generates: withHooks: false ./graphql.schema.json: plugins: - - "introspection" \ No newline at end of file + - "introspection" diff --git a/frontend/src/generated/graphql.ts b/frontend/src/generated/graphql.ts index 2c22836..9e238a5 100644 --- a/frontend/src/generated/graphql.ts +++ b/frontend/src/generated/graphql.ts @@ -1776,6 +1776,28 @@ export type DashboardRecordSort = { sort: AgGridSortDirection; }; +export type DashboardRequest = { + __typename?: "DashboardRequest"; + bicAnalysis?: Maybe; + dataAccessEmails?: Maybe; + dataAnalystEmail?: Maybe; + dataAnalystName?: Maybe; + genePanel?: Maybe; + igoProjectId?: Maybe; + igoRequestId: Scalars["String"]; + importDate?: Maybe; + investigatorEmail?: Maybe; + investigatorName?: Maybe; + isCmoRequest?: Maybe; + labHeadEmail?: Maybe; + labHeadName?: Maybe; + otherContactEmails?: Maybe; + piEmail?: Maybe; + projectManagerName?: Maybe; + qcAccessEmails?: Maybe; + totalSampleCount?: Maybe; +}; + export type DashboardSample = { __typename?: "DashboardSample"; accessLevel?: Maybe; @@ -4550,6 +4572,8 @@ export type Query = { cohortsConnection: CohortsConnection; dashboardPatientCount: DashboardRecordCount; dashboardPatients: Array; + dashboardRequestCount: DashboardRecordCount; + dashboardRequests: Array; dashboardSampleCount: DashboardRecordCount; dashboardSamples: Array; mafCompletes: Array; @@ -4652,6 +4676,19 @@ export type QueryDashboardPatientsArgs = { sort: DashboardRecordSort; }; +export type QueryDashboardRequestCountArgs = { + filter?: InputMaybe; + searchVals?: InputMaybe>; +}; + +export type QueryDashboardRequestsArgs = { + filter?: InputMaybe; + limit: Scalars["Int"]; + offset: Scalars["Int"]; + searchVals?: InputMaybe>; + sort: DashboardRecordSort; +}; + export type QueryDashboardSampleCountArgs = { context?: InputMaybe; filter?: InputMaybe; @@ -6760,7 +6797,7 @@ export type Sample = { requestsHasSample: Array; requestsHasSampleAggregate?: Maybe; requestsHasSampleConnection: SampleRequestsHasSampleConnection; - revisable: Scalars["Boolean"]; + revisable?: Maybe; sampleAliasesIsAlias: Array; sampleAliasesIsAliasAggregate?: Maybe; sampleAliasesIsAliasConnection: SampleSampleAliasesIsAliasConnection; @@ -7534,7 +7571,7 @@ export type SampleCreateInput = { hasTempoTempos?: InputMaybe; patientsHasSample?: InputMaybe; requestsHasSample?: InputMaybe; - revisable: Scalars["Boolean"]; + revisable?: InputMaybe; sampleAliasesIsAlias?: InputMaybe; sampleCategory: Scalars["String"]; sampleClass: Scalars["String"]; @@ -12505,6 +12542,43 @@ export type PatientsListQuery = { }>; }; +export type DashboardRequestsQueryVariables = Exact<{ + searchVals?: InputMaybe | Scalars["String"]>; + filter?: InputMaybe; + sort: DashboardRecordSort; + limit: Scalars["Int"]; + offset: Scalars["Int"]; +}>; + +export type DashboardRequestsQuery = { + __typename?: "Query"; + dashboardRequestCount: { + __typename?: "DashboardRecordCount"; + totalCount: number; + }; + dashboardRequests: Array<{ + __typename?: "DashboardRequest"; + igoRequestId: string; + igoProjectId?: string | null; + importDate?: string | null; + totalSampleCount?: number | null; + projectManagerName?: string | null; + investigatorName?: string | null; + investigatorEmail?: string | null; + piEmail?: string | null; + dataAnalystName?: string | null; + dataAnalystEmail?: string | null; + genePanel?: string | null; + labHeadName?: string | null; + labHeadEmail?: string | null; + qcAccessEmails?: string | null; + dataAccessEmails?: string | null; + bicAnalysis?: boolean | null; + isCmoRequest?: boolean | null; + otherContactEmails?: string | null; + }>; +}; + export type DashboardPatientsQueryVariables = Exact<{ searchVals?: InputMaybe | Scalars["String"]>; filter?: InputMaybe; @@ -13007,6 +13081,100 @@ export type PatientsListQueryResult = Apollo.QueryResult< PatientsListQuery, PatientsListQueryVariables >; +export const DashboardRequestsDocument = gql` + query DashboardRequests( + $searchVals: [String!] + $filter: DashboardRecordFilter + $sort: DashboardRecordSort! + $limit: Int! + $offset: Int! + ) { + dashboardRequestCount(searchVals: $searchVals, filter: $filter) { + totalCount + } + dashboardRequests( + searchVals: $searchVals + filter: $filter + sort: $sort + limit: $limit + offset: $offset + ) { + igoRequestId + igoProjectId + importDate + totalSampleCount + projectManagerName + investigatorName + investigatorEmail + piEmail + dataAnalystName + dataAnalystEmail + genePanel + labHeadName + labHeadEmail + qcAccessEmails + dataAccessEmails + bicAnalysis + isCmoRequest + otherContactEmails + } + } +`; + +/** + * __useDashboardRequestsQuery__ + * + * To run a query within a React component, call `useDashboardRequestsQuery` and pass it any options that fit your needs. + * When your component renders, `useDashboardRequestsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useDashboardRequestsQuery({ + * variables: { + * searchVals: // value for 'searchVals' + * filter: // value for 'filter' + * sort: // value for 'sort' + * limit: // value for 'limit' + * offset: // value for 'offset' + * }, + * }); + */ +export function useDashboardRequestsQuery( + baseOptions: Apollo.QueryHookOptions< + DashboardRequestsQuery, + DashboardRequestsQueryVariables + > +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery< + DashboardRequestsQuery, + DashboardRequestsQueryVariables + >(DashboardRequestsDocument, options); +} +export function useDashboardRequestsLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + DashboardRequestsQuery, + DashboardRequestsQueryVariables + > +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery< + DashboardRequestsQuery, + DashboardRequestsQueryVariables + >(DashboardRequestsDocument, options); +} +export type DashboardRequestsQueryHookResult = ReturnType< + typeof useDashboardRequestsQuery +>; +export type DashboardRequestsLazyQueryHookResult = ReturnType< + typeof useDashboardRequestsLazyQuery +>; +export type DashboardRequestsQueryResult = Apollo.QueryResult< + DashboardRequestsQuery, + DashboardRequestsQueryVariables +>; export const DashboardPatientsDocument = gql` query DashboardPatients( $searchVals: [String!] diff --git a/frontend/src/pages/patients/PatientsPage.tsx b/frontend/src/pages/patients/PatientsPage.tsx index b7af435..d23fb6e 100644 --- a/frontend/src/pages/patients/PatientsPage.tsx +++ b/frontend/src/pages/patients/PatientsPage.tsx @@ -13,6 +13,8 @@ import { Tooltip } from "@material-ui/core"; import InfoIcon from "@material-ui/icons/InfoOutlined"; import { parseUserSearchVal } from "../../utils/parseSearchQueries"; import { + MAX_ROWS_EXPORT, + MAX_ROWS_EXPORT_WARNING, PatientsListColumns, SampleMetadataDetailsColumns, } from "../../shared/helpers"; @@ -24,14 +26,6 @@ function addCDashToCMOId(cmoId: string): string { return cmoId.length === 6 ? `C-${cmoId}` : cmoId; } -const MAX_ROWS_EXPORT = 10000; - -const MAX_ROWS_EXPORT_WARNING = { - title: "Warning", - content: - "You can only download up to 10,000 rows of data at a time. Please refine your search and try again. If you need the full dataset, contact the SMILE team at cmosmile@mskcc.org.", -}; - const PHI_WARNING = { title: "Warning", content: @@ -204,7 +198,7 @@ export default function PatientsPage({ const sampleQueryParamFieldName = "patientId"; const sampleQueryParamValue = params[sampleQueryParamFieldName]; const defaultSort = { - colId: "dmpPatientId", + colId: "latestImportDate", sort: AgGridSortDirection.Desc, }; diff --git a/frontend/src/pages/requests/RequestsPage.tsx b/frontend/src/pages/requests/RequestsPage.tsx index 93ccaa3..ed5d212 100644 --- a/frontend/src/pages/requests/RequestsPage.tsx +++ b/frontend/src/pages/requests/RequestsPage.tsx @@ -1,96 +1,76 @@ import { - RequestWhere, - SortDirection, - useRequestsListLazyQuery, + AgGridSortDirection, + useDashboardRequestsLazyQuery, } from "../../generated/graphql"; import { useState } from "react"; import { + MAX_ROWS_EXPORT, + MAX_ROWS_EXPORT_WARNING, RequestsListColumns, SampleMetadataDetailsColumns, - handleSearch, } from "../../shared/helpers"; -import RecordsList from "../../components/RecordsList"; import { useParams } from "react-router-dom"; - -function requestFilterWhereVariables( - parsedSearchVals: string[] -): RequestWhere[] { - if (parsedSearchVals.length > 1) { - return [ - { igoProjectId_IN: parsedSearchVals }, - { igoRequestId_IN: parsedSearchVals }, - { projectManagerName_IN: parsedSearchVals }, - { investigatorName_IN: parsedSearchVals }, - { investigatorEmail_IN: parsedSearchVals }, - { piEmail_IN: parsedSearchVals }, - { dataAnalystName_IN: parsedSearchVals }, - { dataAnalystEmail_IN: parsedSearchVals }, - { genePanel_IN: parsedSearchVals }, - { labHeadName_IN: parsedSearchVals }, - { labHeadEmail_IN: parsedSearchVals }, - { qcAccessEmails_IN: parsedSearchVals }, - { dataAccessEmails_IN: parsedSearchVals }, - { otherContactEmails_IN: parsedSearchVals }, - ]; - } - - if (parsedSearchVals.length === 1) { - return [ - { igoProjectId_CONTAINS: parsedSearchVals[0] }, - { igoRequestId_CONTAINS: parsedSearchVals[0] }, - { projectManagerName_CONTAINS: parsedSearchVals[0] }, - { investigatorName_CONTAINS: parsedSearchVals[0] }, - { investigatorEmail_CONTAINS: parsedSearchVals[0] }, - { piEmail_CONTAINS: parsedSearchVals[0] }, - { dataAnalystName_CONTAINS: parsedSearchVals[0] }, - { dataAnalystEmail_CONTAINS: parsedSearchVals[0] }, - { genePanel_CONTAINS: parsedSearchVals[0] }, - { labHeadName_CONTAINS: parsedSearchVals[0] }, - { labHeadEmail_CONTAINS: parsedSearchVals[0] }, - { qcAccessEmails_CONTAINS: parsedSearchVals[0] }, - { dataAccessEmails_CONTAINS: parsedSearchVals[0] }, - { otherContactEmails_CONTAINS: parsedSearchVals[0] }, - ]; - } - - return []; -} +import NewRecordsList from "../../components/NewRecordsList"; +import { AlertModal } from "../../components/AlertModal"; export default function RequestsPage() { const params = useParams(); const [userSearchVal, setUserSearchVal] = useState(""); - const [parsedSearchVals, setParsedSearchVals] = useState([]); const [showDownloadModal, setShowDownloadModal] = useState(false); + const [alertModal, setAlertModal] = useState<{ + show: boolean; + title: string; + content: string; + }>({ show: false, title: "", content: "" }); const dataName = "requests"; const sampleQueryParamFieldName = "igoRequestId"; const sampleQueryParamValue = params[sampleQueryParamFieldName]; - const defaultSort = [{ importDate: SortDirection.Desc }]; + const defaultSort = { + colId: "importDate", + sort: AgGridSortDirection.Desc, + }; return ( - handleSearch(userSearchVal, setParsedSearchVals)} - showDownloadModal={showDownloadModal} - setShowDownloadModal={setShowDownloadModal} - handleDownload={() => setShowDownloadModal(true)} - samplesColDefs={SampleMetadataDetailsColumns} - sampleContext={ - sampleQueryParamValue - ? { - fieldName: sampleQueryParamFieldName, - values: [sampleQueryParamValue], - } - : undefined - } - /> + <> + { + if (recordCount && recordCount > MAX_ROWS_EXPORT) { + setAlertModal({ + show: true, + ...MAX_ROWS_EXPORT_WARNING, + }); + } else { + setShowDownloadModal(true); + } + }} + samplesColDefs={SampleMetadataDetailsColumns} + sampleContext={ + sampleQueryParamValue + ? { + fieldName: sampleQueryParamFieldName, + values: [sampleQueryParamValue], + } + : undefined + } + /> + + { + setAlertModal({ ...alertModal, show: false }); + }} + title={alertModal.title} + content={alertModal.content} + /> + ); } diff --git a/frontend/src/shared/helpers.tsx b/frontend/src/shared/helpers.tsx index 3800921..b455b1b 100644 --- a/frontend/src/shared/helpers.tsx +++ b/frontend/src/shared/helpers.tsx @@ -843,3 +843,11 @@ export function isValidCostCenter(costCenter: string): boolean { const validCostCenter = new RegExp("^\\d{5}/\\d{5}$"); return validCostCenter.test(costCenter); } + +export const MAX_ROWS_EXPORT = 10000; + +export const MAX_ROWS_EXPORT_WARNING = { + title: "Warning", + content: + "You can only download up to 10,000 rows of data at a time. Please refine your search and try again. If you need the full dataset, contact the SMILE team at cmosmile@mskcc.org.", +}; diff --git a/graphql-server/src/generated/graphql.ts b/graphql-server/src/generated/graphql.ts index 5b03aa1..53d75f4 100644 --- a/graphql-server/src/generated/graphql.ts +++ b/graphql-server/src/generated/graphql.ts @@ -1775,6 +1775,28 @@ export type DashboardRecordSort = { sort: AgGridSortDirection; }; +export type DashboardRequest = { + __typename?: "DashboardRequest"; + bicAnalysis?: Maybe; + dataAccessEmails?: Maybe; + dataAnalystEmail?: Maybe; + dataAnalystName?: Maybe; + genePanel?: Maybe; + igoProjectId?: Maybe; + igoRequestId: Scalars["String"]; + importDate?: Maybe; + investigatorEmail?: Maybe; + investigatorName?: Maybe; + isCmoRequest?: Maybe; + labHeadEmail?: Maybe; + labHeadName?: Maybe; + otherContactEmails?: Maybe; + piEmail?: Maybe; + projectManagerName?: Maybe; + qcAccessEmails?: Maybe; + totalSampleCount?: Maybe; +}; + export type DashboardSample = { __typename?: "DashboardSample"; accessLevel?: Maybe; @@ -4549,6 +4571,8 @@ export type Query = { cohortsConnection: CohortsConnection; dashboardPatientCount: DashboardRecordCount; dashboardPatients: Array; + dashboardRequestCount: DashboardRecordCount; + dashboardRequests: Array; dashboardSampleCount: DashboardRecordCount; dashboardSamples: Array; mafCompletes: Array; @@ -4651,6 +4675,19 @@ export type QueryDashboardPatientsArgs = { sort: DashboardRecordSort; }; +export type QueryDashboardRequestCountArgs = { + filter?: InputMaybe; + searchVals?: InputMaybe>; +}; + +export type QueryDashboardRequestsArgs = { + filter?: InputMaybe; + limit: Scalars["Int"]; + offset: Scalars["Int"]; + searchVals?: InputMaybe>; + sort: DashboardRecordSort; +}; + export type QueryDashboardSampleCountArgs = { context?: InputMaybe; filter?: InputMaybe; @@ -6759,7 +6796,7 @@ export type Sample = { requestsHasSample: Array; requestsHasSampleAggregate?: Maybe; requestsHasSampleConnection: SampleRequestsHasSampleConnection; - revisable: Scalars["Boolean"]; + revisable?: Maybe; sampleAliasesIsAlias: Array; sampleAliasesIsAliasAggregate?: Maybe; sampleAliasesIsAliasConnection: SampleSampleAliasesIsAliasConnection; @@ -7533,7 +7570,7 @@ export type SampleCreateInput = { hasTempoTempos?: InputMaybe; patientsHasSample?: InputMaybe; requestsHasSample?: InputMaybe; - revisable: Scalars["Boolean"]; + revisable?: InputMaybe; sampleAliasesIsAlias?: InputMaybe; sampleCategory: Scalars["String"]; sampleClass: Scalars["String"]; @@ -12504,6 +12541,43 @@ export type PatientsListQuery = { }>; }; +export type DashboardRequestsQueryVariables = Exact<{ + searchVals?: InputMaybe | Scalars["String"]>; + filter?: InputMaybe; + sort: DashboardRecordSort; + limit: Scalars["Int"]; + offset: Scalars["Int"]; +}>; + +export type DashboardRequestsQuery = { + __typename?: "Query"; + dashboardRequestCount: { + __typename?: "DashboardRecordCount"; + totalCount: number; + }; + dashboardRequests: Array<{ + __typename?: "DashboardRequest"; + igoRequestId: string; + igoProjectId?: string | null; + importDate?: string | null; + totalSampleCount?: number | null; + projectManagerName?: string | null; + investigatorName?: string | null; + investigatorEmail?: string | null; + piEmail?: string | null; + dataAnalystName?: string | null; + dataAnalystEmail?: string | null; + genePanel?: string | null; + labHeadName?: string | null; + labHeadEmail?: string | null; + qcAccessEmails?: string | null; + dataAccessEmails?: string | null; + bicAnalysis?: boolean | null; + isCmoRequest?: boolean | null; + otherContactEmails?: string | null; + }>; +}; + export type DashboardPatientsQueryVariables = Exact<{ searchVals?: InputMaybe | Scalars["String"]>; filter?: InputMaybe; @@ -12910,6 +12984,49 @@ export type PatientsListQueryResult = Apollo.QueryResult< PatientsListQuery, PatientsListQueryVariables >; +export const DashboardRequestsDocument = gql` + query DashboardRequests( + $searchVals: [String!] + $filter: DashboardRecordFilter + $sort: DashboardRecordSort! + $limit: Int! + $offset: Int! + ) { + dashboardRequestCount(searchVals: $searchVals, filter: $filter) { + totalCount + } + dashboardRequests( + searchVals: $searchVals + filter: $filter + sort: $sort + limit: $limit + offset: $offset + ) { + igoRequestId + igoProjectId + importDate + totalSampleCount + projectManagerName + investigatorName + investigatorEmail + piEmail + dataAnalystName + dataAnalystEmail + genePanel + labHeadName + labHeadEmail + qcAccessEmails + dataAccessEmails + bicAnalysis + isCmoRequest + otherContactEmails + } + } +`; +export type DashboardRequestsQueryResult = Apollo.QueryResult< + DashboardRequestsQuery, + DashboardRequestsQueryVariables +>; export const DashboardPatientsDocument = gql` query DashboardPatients( $searchVals: [String!] diff --git a/graphql-server/src/schemas/custom.ts b/graphql-server/src/schemas/custom.ts index 112a38c..d5adc43 100644 --- a/graphql-server/src/schemas/custom.ts +++ b/graphql-server/src/schemas/custom.ts @@ -8,6 +8,8 @@ import { DashboardSampleInput, QueryDashboardPatientCountArgs, QueryDashboardPatientsArgs, + QueryDashboardRequestCountArgs, + QueryDashboardRequestsArgs, QueryDashboardSampleCountArgs, QueryDashboardSamplesArgs, } from "../generated/graphql"; @@ -36,7 +38,7 @@ export async function buildCustomSchema(ogm: OGM) { oncotreeCache, }); - const partialCypherQuery = buildPartialCypherQuery({ + const queryBody = buildSamplesQueryBody({ searchVals, context, filter, @@ -44,7 +46,7 @@ export async function buildCustomSchema(ogm: OGM) { }); return await queryDashboardSamples({ - partialCypherQuery, + queryBody, sort, limit, offset, @@ -61,7 +63,7 @@ export async function buildCustomSchema(ogm: OGM) { oncotreeCache, }); - const partialCypherQuery = buildPartialCypherQuery({ + const queryBody = buildSamplesQueryBody({ searchVals, context, filter, @@ -69,9 +71,30 @@ export async function buildCustomSchema(ogm: OGM) { }); return await queryDashboardSampleCount({ - partialCypherQuery, + queryBody, + }); + }, + + async dashboardRequests( + _source: undefined, + { searchVals, filter, sort, limit, offset }: QueryDashboardRequestsArgs + ) { + const queryBody = buildRequestsQueryBody({ searchVals, filter }); + return await queryDashboardRequests({ + queryBody, + sort, + limit, + offset, }); }, + async dashboardRequestCount( + _source: undefined, + { searchVals, filter }: QueryDashboardRequestCountArgs + ) { + const queryBody = buildRequestsQueryBody({ searchVals, filter }); + return await queryDashboardRequestCount({ queryBody }); + }, + async dashboardPatients( _source: undefined, { searchVals, filter, sort, limit, offset }: QueryDashboardPatientsArgs @@ -92,6 +115,7 @@ export async function buildCustomSchema(ogm: OGM) { return await queryDashboardPatientCount({ queryBody }); }, }, + Mutation: { async updateDashboardSamples( _source: undefined, @@ -177,6 +201,27 @@ export async function buildCustomSchema(ogm: OGM) { values: [String!]! } + type DashboardRequest { + igoRequestId: String! + igoProjectId: String + importDate: String + totalSampleCount: Int + projectManagerName: String + investigatorName: String + investigatorEmail: String + piEmail: String + dataAnalystName: String + dataAnalystEmail: String + genePanel: String + labHeadName: String + labHeadEmail: String + qcAccessEmails: String + dataAccessEmails: String + bicAnalysis: Boolean + isCmoRequest: Boolean + otherContactEmails: String + } + type DashboardPatient { smilePatientId: String! cmoPatientId: String @@ -229,9 +274,21 @@ export async function buildCustomSchema(ogm: OGM) { searchVals: [String!] filter: DashboardRecordFilter ): DashboardRecordCount! + + dashboardRequests( + searchVals: [String!] + filter: DashboardRecordFilter + sort: DashboardRecordSort! + limit: Int! + offset: Int! + ): [DashboardRequest!]! + dashboardRequestCount( + searchVals: [String!] + filter: DashboardRecordFilter + ): DashboardRecordCount! } - # We have to define a separate "input" type and can't reuse DashboardSample. + # We have to define a separate "input" type for the mutation and can't reuse DashboardSample. # For more context, see: https://stackoverflow.com/q/41743253 input DashboardSampleInput { changedFieldNames: [String!]! @@ -307,20 +364,20 @@ export async function buildCustomSchema(ogm: OGM) { } async function queryDashboardSamples({ - partialCypherQuery, + queryBody, sort, limit, offset, oncotreeCache, }: { - partialCypherQuery: string; + queryBody: string; sort: QueryDashboardSamplesArgs["sort"]; limit: QueryDashboardSamplesArgs["limit"]; offset: QueryDashboardSamplesArgs["offset"]; oncotreeCache: NodeCache; }) { const cypherQuery = ` - ${partialCypherQuery} + ${queryBody} RETURN s.smileSampleId AS smileSampleId, s.revisable AS revisable, @@ -392,13 +449,9 @@ async function queryDashboardSamples({ } } -async function queryDashboardSampleCount({ - partialCypherQuery, -}: { - partialCypherQuery: string; -}) { +async function queryDashboardSampleCount({ queryBody }: { queryBody: string }) { const cypherQuery = ` - ${partialCypherQuery} + ${queryBody} RETURN count(s) AS totalCount `; @@ -471,7 +524,7 @@ const searchFiltersConfig = [ { variable: "latestQC", fields: ["date", "result", "reason", "status"] }, ]; -function buildPartialCypherQuery({ +function buildSamplesQueryBody({ searchVals, context, filter, @@ -552,7 +605,7 @@ function buildPartialCypherQuery({ } } - const partialCypherQuery = ` + const samplesQueryBody = ` // Get Sample and the most recent SampleMetadata MATCH (s:Sample)-[:HAS_METADATA]->(sm:SampleMetadata) WITH s, collect(sm) AS allSampleMetadata, max(sm.importDate) AS latestImportDate @@ -615,7 +668,7 @@ function buildPartialCypherQuery({ } `; - return partialCypherQuery; + return samplesQueryBody; } function getAddlOtCodesMatchingCtOrCtdVals({ @@ -802,6 +855,146 @@ async function publishNatsMessage(topic: string, message: string) { } } +function buildRequestsQueryBody({ + searchVals, + filter, +}: { + searchVals: QueryDashboardRequestsArgs["searchVals"]; + filter: QueryDashboardRequestsArgs["filter"]; +}) { + const fieldsToSearch = [ + "igoRequestId", + "igoProjectId", + "importDate", + "projectManagerName", + "investigatorName", + "investigatorEmail", + "piEmail", + "dataAnalystName", + "dataAnalystEmail", + "genePanel", + "labHeadName", + "labHeadEmail", + "qcAccessEmails", + "dataAccessEmails", + "bicAnalysis", + "isCmoRequest", + "otherContactEmails", + ]; + + const searchFilters = searchVals?.length + ? "WHERE " + + fieldsToSearch + .map((field) => `${field} =~ '(?i).*(${searchVals.join("|")}).*'`) + .join(" OR ") + : ""; + + const requestQueryBody = ` + MATCH (r:Request) + + // Get the latest SampleMetadata of each Sample + OPTIONAL MATCH (r)-[:HAS_SAMPLE]->(s:Sample)-[:HAS_METADATA]->(sm:SampleMetadata) + WITH + r, + collect(s) as samples, + collect(sm) AS allSampleMetadata, + max(sm.importDate) AS latestImportDate + WITH + r, + size(samples) as totalSampleCount, + [sm IN allSampleMetadata WHERE sm.importDate = latestImportDate][0] AS latestSm + + WITH + r.igoRequestId as igoRequestId, + r.igoProjectId as igoProjectId, + latestSm.importDate as importDate, + totalSampleCount, + r.projectManagerName as projectManagerName, + r.investigatorName as investigatorName, + r.investigatorEmail as investigatorEmail, + r.piEmail as piEmail, + r.dataAnalystName as dataAnalystName, + r.dataAnalystEmail as dataAnalystEmail, + r.genePanel as genePanel, + r.labHeadName as labHeadName, + r.labHeadEmail as labHeadEmail, + r.qcAccessEmails as qcAccessEmails, + r.dataAccessEmails as dataAccessEmails, + r.bicAnalysis as bicAnalysis, + r.isCmoRequest as isCmoRequest, + r.otherContactEmails as otherContactEmails + + ${searchFilters} + `; + + return requestQueryBody; +} + +async function queryDashboardRequests({ + queryBody, + sort, + limit, + offset, +}: { + queryBody: string; + sort: QueryDashboardRequestsArgs["sort"]; + limit: QueryDashboardRequestsArgs["limit"]; + offset: QueryDashboardRequestsArgs["offset"]; +}) { + const cypherQuery = ` + ${queryBody} + RETURN + igoRequestId, + igoProjectId, + importDate, + totalSampleCount, + projectManagerName, + investigatorName, + investigatorEmail, + piEmail, + dataAnalystName, + dataAnalystEmail, + genePanel, + labHeadName, + labHeadEmail, + qcAccessEmails, + dataAccessEmails, + bicAnalysis, + isCmoRequest, + otherContactEmails + ORDER BY ${getNeo4jCustomSort(sort)} + SKIP ${offset} + LIMIT ${limit} + `; + + const session = neo4jDriver.session(); + try { + const result = await session.run(cypherQuery); + return result.records.map((record) => record.toObject()); + } catch (error) { + console.error("Error with queryDashboardRequests:", error); + } +} + +async function queryDashboardRequestCount({ + queryBody, +}: { + queryBody: string; +}) { + const cypherQuery = ` + ${queryBody} + RETURN count(igoRequestId) AS totalCount + `; + + const session = neo4jDriver.session(); + try { + const result = await session.run(cypherQuery); + return result.records[0].toObject(); + } catch (error) { + console.error("Error with queryDashboardRequestCount:", error); + } +} + function buildPatientsQueryBody({ searchVals, filter, @@ -846,6 +1039,7 @@ function buildPatientsQueryBody({ cmoPa, dmpPa, s, + latestImportDate, [sm IN allSampleMetadata WHERE sm.importDate = latestImportDate][0] AS latestSm // Get the CMO Sample IDs and additionalProperties JSONs from SampleMetadata @@ -854,6 +1048,7 @@ function buildPatientsQueryBody({ cmoPa, dmpPa, s, + latestImportDate, CASE WHEN latestSm.cmoSampleName IS NOT NULL THEN latestSm.cmoSampleName ELSE latestSm.primaryId @@ -865,6 +1060,7 @@ function buildPatientsQueryBody({ p, cmoPa, dmpPa, + latestImportDate, collect(s) as samples, collect(cmoSampleId) AS cmoSampleIds, collect(latestSmAddlPropsJson.\`consent-parta\`) as consentPartAs, @@ -874,6 +1070,7 @@ function buildPatientsQueryBody({ p.smilePatientId AS smilePatientId, cmoPa.value AS cmoPatientId, dmpPa.value AS dmpPatientId, + latestImportDate, size(samples) as totalSampleCount, apoc.text.join([id IN cmoSampleIds WHERE id <> ''], ', ') AS cmoSampleIds, consentPartAs[0] as consentPartA, diff --git a/graphql.schema.json b/graphql.schema.json index 8dc3701..c55d26d 100644 --- a/graphql.schema.json +++ b/graphql.schema.json @@ -15478,6 +15478,237 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "DashboardRequest", + "description": null, + "fields": [ + { + "name": "bicAnalysis", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dataAccessEmails", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dataAnalystEmail", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dataAnalystName", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "genePanel", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "igoProjectId", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "igoRequestId", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "importDate", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "investigatorEmail", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "investigatorName", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isCmoRequest", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "labHeadEmail", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "labHeadName", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "otherContactEmails", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "piEmail", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "projectManagerName", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "qcAccessEmails", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalSampleCount", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "DashboardSample", @@ -40827,6 +41058,160 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "dashboardRequestCount", + "description": null, + "args": [ + { + "name": "filter", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "DashboardRecordFilter", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "searchVals", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "DashboardRecordCount", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "dashboardRequests", + "description": null, + "args": [ + { + "name": "filter", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "DashboardRecordFilter", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "limit", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "offset", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "searchVals", + "description": null, + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "sort", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "DashboardRecordSort", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "DashboardRequest", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "dashboardSampleCount", "description": null, @@ -60937,13 +61322,9 @@ "description": null, "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } + "kind": "SCALAR", + "name": "Boolean", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -67171,13 +67552,9 @@ "name": "revisable", "description": null, "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } + "kind": "SCALAR", + "name": "Boolean", + "ofType": null }, "defaultValue": null, "isDeprecated": false, diff --git a/graphql/operations.graphql b/graphql/operations.graphql index 5afe03d..4ca7831 100644 --- a/graphql/operations.graphql +++ b/graphql/operations.graphql @@ -46,6 +46,44 @@ query PatientsList($options: PatientOptions, $where: PatientWhere) { } } +query DashboardRequests( + $searchVals: [String!] + $filter: DashboardRecordFilter + $sort: DashboardRecordSort! + $limit: Int! + $offset: Int! +) { + dashboardRequestCount(searchVals: $searchVals, filter: $filter) { + totalCount + } + dashboardRequests( + searchVals: $searchVals + filter: $filter + sort: $sort + limit: $limit + offset: $offset + ) { + igoRequestId + igoProjectId + importDate + totalSampleCount + projectManagerName + investigatorName + investigatorEmail + piEmail + dataAnalystName + dataAnalystEmail + genePanel + labHeadName + labHeadEmail + qcAccessEmails + dataAccessEmails + bicAnalysis + isCmoRequest + otherContactEmails + } +} + query DashboardPatients( $searchVals: [String!] $filter: DashboardRecordFilter