diff --git a/client/src/app/components/organizations/organizations-table/organizations-table.component.html b/client/src/app/components/organizations/organizations-table/organizations-table.component.html index ed58b633f..8fa006cef 100644 --- a/client/src/app/components/organizations/organizations-table/organizations-table.component.html +++ b/client/src/app/components/organizations/organizations-table/organizations-table.component.html @@ -51,17 +51,26 @@ [nzSortFn]="true"> Organization - Members + + Members + Sub Organizations Actions Last Action @@ -97,13 +106,13 @@ + [tags]="organization.childOrganizations"> - {{ organization.eventCount | number }} + {{ organization.activityCount | number }} result$!: Observable> - connection$!: Observable + connection$!: Observable // PRESENTATION STREAMS pageInfo$!: Observable @@ -126,7 +126,7 @@ export class CvcOrganizationsTableComponent implements OnInit { this.connection$ = this.result$.pipe( pluck('data', 'organizations'), filter(isNonNulled) - ) as Observable + ) as Observable // entity row nodes this.row$ = this.connection$.pipe( diff --git a/client/src/app/components/organizations/organizations-table/organizations-table.query.gql b/client/src/app/components/organizations/organizations-table/organizations-table.query.gql index 8e7846936..48f133dfc 100644 --- a/client/src/app/components/organizations/organizations-table/organizations-table.query.gql +++ b/client/src/app/components/organizations/organizations-table/organizations-table.query.gql @@ -32,19 +32,19 @@ query OrganizationsBrowse( } } -fragment OrganizationBrowseTableRowFields on Organization { +fragment OrganizationBrowseTableRowFields on BrowseOrganization { id name description - # profileImagePath(size: 256) @include(if: $cardView) url memberCount - eventCount - subGroups { - name + activityCount + mostRecentActivityTimestamp + childOrganizations { id + name } - mostRecentActivityTimestamp + # profileImagePath(size: 256) @include(if: $cardView) # orgStatsHash @include(if: $cardView) { # comments # revisions diff --git a/client/src/app/generated/civic.apollo-helpers.ts b/client/src/app/generated/civic.apollo-helpers.ts index 7cfba8731..ee89027c4 100644 --- a/client/src/app/generated/civic.apollo-helpers.ts +++ b/client/src/app/generated/civic.apollo-helpers.ts @@ -258,6 +258,35 @@ export type BrowseMolecularProfileEdgeFieldPolicy = { cursor?: FieldPolicy | FieldReadFunction, node?: FieldPolicy | FieldReadFunction }; +export type BrowseOrganizationKeySpecifier = ('activityCount' | 'childOrganizations' | 'createdAt' | 'description' | 'id' | 'memberCount' | 'mostRecentActivityTimestamp' | 'name' | 'parentId' | 'updatedAt' | 'url' | BrowseOrganizationKeySpecifier)[]; +export type BrowseOrganizationFieldPolicy = { + activityCount?: FieldPolicy | FieldReadFunction, + childOrganizations?: FieldPolicy | FieldReadFunction, + createdAt?: FieldPolicy | FieldReadFunction, + description?: FieldPolicy | FieldReadFunction, + id?: FieldPolicy | FieldReadFunction, + memberCount?: FieldPolicy | FieldReadFunction, + mostRecentActivityTimestamp?: FieldPolicy | FieldReadFunction, + name?: FieldPolicy | FieldReadFunction, + parentId?: FieldPolicy | FieldReadFunction, + updatedAt?: FieldPolicy | FieldReadFunction, + url?: FieldPolicy | FieldReadFunction +}; +export type BrowseOrganizationConnectionKeySpecifier = ('edges' | 'filteredCount' | 'lastUpdated' | 'nodes' | 'pageCount' | 'pageInfo' | 'totalCount' | BrowseOrganizationConnectionKeySpecifier)[]; +export type BrowseOrganizationConnectionFieldPolicy = { + edges?: FieldPolicy | FieldReadFunction, + filteredCount?: FieldPolicy | FieldReadFunction, + lastUpdated?: FieldPolicy | FieldReadFunction, + nodes?: FieldPolicy | FieldReadFunction, + pageCount?: FieldPolicy | FieldReadFunction, + pageInfo?: FieldPolicy | FieldReadFunction, + totalCount?: FieldPolicy | FieldReadFunction +}; +export type BrowseOrganizationEdgeKeySpecifier = ('cursor' | 'node' | BrowseOrganizationEdgeKeySpecifier)[]; +export type BrowseOrganizationEdgeFieldPolicy = { + cursor?: FieldPolicy | FieldReadFunction, + node?: FieldPolicy | FieldReadFunction +}; export type BrowsePhenotypeKeySpecifier = ('assertionCount' | 'evidenceCount' | 'hpoId' | 'id' | 'link' | 'name' | 'url' | BrowsePhenotypeKeySpecifier)[]; export type BrowsePhenotypeFieldPolicy = { assertionCount?: FieldPolicy | FieldReadFunction, @@ -1621,19 +1650,6 @@ export type OrganizationFieldPolicy = { subGroups?: FieldPolicy | FieldReadFunction, url?: FieldPolicy | FieldReadFunction }; -export type OrganizationConnectionKeySpecifier = ('edges' | 'nodes' | 'pageCount' | 'pageInfo' | 'totalCount' | OrganizationConnectionKeySpecifier)[]; -export type OrganizationConnectionFieldPolicy = { - edges?: FieldPolicy | FieldReadFunction, - nodes?: FieldPolicy | FieldReadFunction, - pageCount?: FieldPolicy | FieldReadFunction, - pageInfo?: FieldPolicy | FieldReadFunction, - totalCount?: FieldPolicy | FieldReadFunction -}; -export type OrganizationEdgeKeySpecifier = ('cursor' | 'node' | OrganizationEdgeKeySpecifier)[]; -export type OrganizationEdgeFieldPolicy = { - cursor?: FieldPolicy | FieldReadFunction, - node?: FieldPolicy | FieldReadFunction -}; export type OrganizationLeaderboardsKeySpecifier = ('commentsLeaderboard' | 'moderationLeaderboard' | 'revisionsLeaderboard' | 'submissionsLeaderboard' | OrganizationLeaderboardsKeySpecifier)[]; export type OrganizationLeaderboardsFieldPolicy = { commentsLeaderboard?: FieldPolicy | FieldReadFunction, @@ -2469,6 +2485,18 @@ export type StrictTypedTypePolicies = { keyFields?: false | BrowseMolecularProfileEdgeKeySpecifier | (() => undefined | BrowseMolecularProfileEdgeKeySpecifier), fields?: BrowseMolecularProfileEdgeFieldPolicy, }, + BrowseOrganization?: Omit & { + keyFields?: false | BrowseOrganizationKeySpecifier | (() => undefined | BrowseOrganizationKeySpecifier), + fields?: BrowseOrganizationFieldPolicy, + }, + BrowseOrganizationConnection?: Omit & { + keyFields?: false | BrowseOrganizationConnectionKeySpecifier | (() => undefined | BrowseOrganizationConnectionKeySpecifier), + fields?: BrowseOrganizationConnectionFieldPolicy, + }, + BrowseOrganizationEdge?: Omit & { + keyFields?: false | BrowseOrganizationEdgeKeySpecifier | (() => undefined | BrowseOrganizationEdgeKeySpecifier), + fields?: BrowseOrganizationEdgeFieldPolicy, + }, BrowsePhenotype?: Omit & { keyFields?: false | BrowsePhenotypeKeySpecifier | (() => undefined | BrowsePhenotypeKeySpecifier), fields?: BrowsePhenotypeFieldPolicy, @@ -2985,14 +3013,6 @@ export type StrictTypedTypePolicies = { keyFields?: false | OrganizationKeySpecifier | (() => undefined | OrganizationKeySpecifier), fields?: OrganizationFieldPolicy, }, - OrganizationConnection?: Omit & { - keyFields?: false | OrganizationConnectionKeySpecifier | (() => undefined | OrganizationConnectionKeySpecifier), - fields?: OrganizationConnectionFieldPolicy, - }, - OrganizationEdge?: Omit & { - keyFields?: false | OrganizationEdgeKeySpecifier | (() => undefined | OrganizationEdgeKeySpecifier), - fields?: OrganizationEdgeFieldPolicy, - }, OrganizationLeaderboards?: Omit & { keyFields?: false | OrganizationLeaderboardsKeySpecifier | (() => undefined | OrganizationLeaderboardsKeySpecifier), fields?: OrganizationLeaderboardsFieldPolicy, diff --git a/client/src/app/generated/civic.apollo.ts b/client/src/app/generated/civic.apollo.ts index 1fd37bdc6..b3da83f60 100644 --- a/client/src/app/generated/civic.apollo.ts +++ b/client/src/app/generated/civic.apollo.ts @@ -680,6 +680,49 @@ export type BrowseMolecularProfileEdge = { node?: Maybe; }; +export type BrowseOrganization = { + __typename: 'BrowseOrganization'; + activityCount: Scalars['Int']; + childOrganizations: Array; + createdAt?: Maybe; + description: Scalars['String']; + id: Scalars['Int']; + memberCount: Scalars['Int']; + mostRecentActivityTimestamp?: Maybe; + name: Scalars['String']; + parentId?: Maybe; + updatedAt?: Maybe; + url: Scalars['String']; +}; + +/** The connection type for BrowseOrganization. */ +export type BrowseOrganizationConnection = { + __typename: 'BrowseOrganizationConnection'; + /** A list of edges. */ + edges: Array; + /** The total number of records in this set. */ + filteredCount: Scalars['Int']; + /** The last time the data in this browse table was refreshed */ + lastUpdated: Scalars['ISO8601DateTime']; + /** A list of nodes. */ + nodes: Array; + /** Total number of pages, based on filtered count and pagesize. */ + pageCount: Scalars['Int']; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** The total number of records of this type, regardless of any filtering. */ + totalCount: Scalars['Int']; +}; + +/** An edge in a connection. */ +export type BrowseOrganizationEdge = { + __typename: 'BrowseOrganizationEdge'; + /** A cursor for use in pagination. */ + cursor: Scalars['String']; + /** The item at the end of the edge. */ + node?: Maybe; +}; + export type BrowsePhenotype = { __typename: 'BrowsePhenotype'; assertionCount: Scalars['Int']; @@ -4072,30 +4115,6 @@ export type OrganizationProfileImagePathArgs = { size?: InputMaybe; }; -/** The connection type for Organization. */ -export type OrganizationConnection = { - __typename: 'OrganizationConnection'; - /** A list of edges. */ - edges: Array; - /** A list of nodes. */ - nodes: Array; - /** Total number of pages, based on filtered count and pagesize. */ - pageCount: Scalars['Int']; - /** Information to aid in pagination. */ - pageInfo: PageInfo; - /** The total number of records in this filtered collection. */ - totalCount: Scalars['Int']; -}; - -/** An edge in a connection. */ -export type OrganizationEdge = { - __typename: 'OrganizationEdge'; - /** A cursor for use in pagination. */ - cursor: Scalars['String']; - /** The item at the end of the edge. */ - node?: Maybe; -}; - /** Filter on organization id and whether or not to include the organization's subgroups */ export type OrganizationFilter = { /** The organization ID. */ @@ -4162,7 +4181,10 @@ export type OrganizationSort = { }; export enum OrganizationSortColumns { + ActivityCount = 'ACTIVITY_COUNT', Id = 'ID', + MemberCount = 'MEMBER_COUNT', + MostRecentActivityTimestamp = 'MOST_RECENT_ACTIVITY_TIMESTAMP', Name = 'NAME' } @@ -4294,7 +4316,7 @@ export type Query = { organization?: Maybe; organizationLeaderboards: OrganizationLeaderboards; /** List and filter organizations. */ - organizations: OrganizationConnection; + organizations: BrowseOrganizationConnection; /** Find a phenotype by CIViC ID */ phenotype?: Maybe; /** Retrieve popover fields for a specific phenotype. */ @@ -7368,9 +7390,9 @@ export type OrganizationsBrowseQueryVariables = Exact<{ }>; -export type OrganizationsBrowseQuery = { __typename: 'Query', organizations: { __typename: 'OrganizationConnection', totalCount: number, pageInfo: { __typename: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | undefined, endCursor?: string | undefined }, edges: Array<{ __typename: 'OrganizationEdge', cursor: string, node?: { __typename: 'Organization', id: number, name: string, description: string, url: string, memberCount: number, eventCount: number, mostRecentActivityTimestamp?: any | undefined, subGroups: Array<{ __typename: 'Organization', name: string, id: number }> } | undefined }> } }; +export type OrganizationsBrowseQuery = { __typename: 'Query', organizations: { __typename: 'BrowseOrganizationConnection', totalCount: number, pageInfo: { __typename: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | undefined, endCursor?: string | undefined }, edges: Array<{ __typename: 'BrowseOrganizationEdge', cursor: string, node?: { __typename: 'BrowseOrganization', id: number, name: string, description: string, url: string, memberCount: number, activityCount: number, mostRecentActivityTimestamp?: any | undefined, childOrganizations: Array<{ __typename: 'Organization', id: number, name: string }> } | undefined }> } }; -export type OrganizationBrowseTableRowFieldsFragment = { __typename: 'Organization', id: number, name: string, description: string, url: string, memberCount: number, eventCount: number, mostRecentActivityTimestamp?: any | undefined, subGroups: Array<{ __typename: 'Organization', name: string, id: number }> }; +export type OrganizationBrowseTableRowFieldsFragment = { __typename: 'BrowseOrganization', id: number, name: string, description: string, url: string, memberCount: number, activityCount: number, mostRecentActivityTimestamp?: any | undefined, childOrganizations: Array<{ __typename: 'Organization', id: number, name: string }> }; export type PhenotypePopoverQueryVariables = Exact<{ phenotypeId: Scalars['Int']; @@ -9673,18 +9695,18 @@ export const OrgPopoverFragmentDoc = gql` } `; export const OrganizationBrowseTableRowFieldsFragmentDoc = gql` - fragment OrganizationBrowseTableRowFields on Organization { + fragment OrganizationBrowseTableRowFields on BrowseOrganization { id name description url memberCount - eventCount - subGroups { - name + activityCount + mostRecentActivityTimestamp + childOrganizations { id + name } - mostRecentActivityTimestamp } `; export const PhenotypeBrowseTableRowFieldsFragmentDoc = gql` diff --git a/client/src/app/generated/server.model.graphql b/client/src/app/generated/server.model.graphql index e213f55ec..156e2029c 100644 --- a/client/src/app/generated/server.model.graphql +++ b/client/src/app/generated/server.model.graphql @@ -1123,6 +1123,75 @@ type BrowseMolecularProfileEdge { node: BrowseMolecularProfile } +type BrowseOrganization { + activityCount: Int! + childOrganizations: [Organization!]! + createdAt: ISO8601DateTime + description: String! + id: Int! + memberCount: Int! + mostRecentActivityTimestamp: ISO8601DateTime + name: String! + parentId: Int + updatedAt: ISO8601DateTime + url: String! +} + +""" +The connection type for BrowseOrganization. +""" +type BrowseOrganizationConnection { + """ + A list of edges. + """ + edges: [BrowseOrganizationEdge!]! + + """ + The total number of records in this set. + """ + filteredCount: Int! + + """ + The last time the data in this browse table was refreshed + """ + lastUpdated: ISO8601DateTime! + + """ + A list of nodes. + """ + nodes: [BrowseOrganization!]! + + """ + Total number of pages, based on filtered count and pagesize. + """ + pageCount: Int! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + The total number of records of this type, regardless of any filtering. + """ + totalCount: Int! +} + +""" +An edge in a connection. +""" +type BrowseOrganizationEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: BrowseOrganization +} + type BrowsePhenotype { assertionCount: Int! evidenceCount: Int! @@ -6742,51 +6811,6 @@ type Organization { url: String! } -""" -The connection type for Organization. -""" -type OrganizationConnection { - """ - A list of edges. - """ - edges: [OrganizationEdge!]! - - """ - A list of nodes. - """ - nodes: [Organization!]! - - """ - Total number of pages, based on filtered count and pagesize. - """ - pageCount: Int! - - """ - Information to aid in pagination. - """ - pageInfo: PageInfo! - - """ - The total number of records in this filtered collection. - """ - totalCount: Int! -} - -""" -An edge in a connection. -""" -type OrganizationEdge { - """ - A cursor for use in pagination. - """ - cursor: String! - - """ - The item at the end of the edge. - """ - node: Organization -} - """ Filter on organization id and whether or not to include the organization's subgroups """ @@ -6915,7 +6939,10 @@ input OrganizationSort { } enum OrganizationSortColumns { + ACTIVITY_COUNT ID + MEMBER_COUNT + MOST_RECENT_ACTIVITY_TIMESTAMP NAME } @@ -7954,7 +7981,7 @@ type Query { Columm and direction to sort evidence on. """ sortBy: OrganizationSort - ): OrganizationConnection! + ): BrowseOrganizationConnection! """ Find a phenotype by CIViC ID diff --git a/client/src/app/generated/server.schema.json b/client/src/app/generated/server.schema.json index 69e19aa40..d55e06ffa 100644 --- a/client/src/app/generated/server.schema.json +++ b/client/src/app/generated/server.schema.json @@ -5268,6 +5268,363 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "BrowseOrganization", + "description": null, + "fields": [ + { + "name": "activityCount", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "childOrganizations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Organization", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "createdAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "memberCount", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mostRecentActivityTimestamp", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "parentId", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updatedAt", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "url", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "BrowseOrganizationConnection", + "description": "The connection type for BrowseOrganization.", + "fields": [ + { + "name": "edges", + "description": "A list of edges.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "BrowseOrganizationEdge", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "filteredCount", + "description": "The total number of records in this set.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "lastUpdated", + "description": "The last time the data in this browse table was refreshed", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nodes", + "description": "A list of nodes.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "BrowseOrganization", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageCount", + "description": "Total number of pages, based on filtered count and pagesize.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pageInfo", + "description": "Information to aid in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "PageInfo", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "The total number of records of this type, regardless of any filtering.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "BrowseOrganizationEdge", + "description": "An edge in a connection.", + "fields": [ + { + "name": "cursor", + "description": "A cursor for use in pagination.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "node", + "description": "The item at the end of the edge.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "BrowseOrganization", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "BrowsePhenotype", @@ -32151,152 +32508,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "OrganizationConnection", - "description": "The connection type for Organization.", - "fields": [ - { - "name": "edges", - "description": "A list of edges.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "OrganizationEdge", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nodes", - "description": "A list of nodes.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Organization", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pageCount", - "description": "Total number of pages, based on filtered count and pagesize.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pageInfo", - "description": "Information to aid in pagination.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "totalCount", - "description": "The total number of records in this filtered collection.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "OrganizationEdge", - "description": "An edge in a connection.", - "fields": [ - { - "name": "cursor", - "description": "A cursor for use in pagination.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "node", - "description": "The item at the end of the edge.", - "args": [], - "type": { - "kind": "OBJECT", - "name": "Organization", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "OrganizationFilter", @@ -32773,6 +32984,24 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "ACTIVITY_COUNT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MEMBER_COUNT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MOST_RECENT_ACTIVITY_TIMESTAMP", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -36866,7 +37095,7 @@ "name": null, "ofType": { "kind": "OBJECT", - "name": "OrganizationConnection", + "name": "BrowseOrganizationConnection", "ofType": null } }, diff --git a/server/Gemfile b/server/Gemfile index 046a1fa9f..63b23dc55 100644 --- a/server/Gemfile +++ b/server/Gemfile @@ -31,6 +31,9 @@ gem 'scenic', '~>1.5.4' #entrez symbol downloads gem 'net-ftp', '~>0.3.3' +#NCIt term download +gem 'rubyzip', '~>2.3.2' + #higher performance json encoding gem 'oj', '~> 3.16.3' diff --git a/server/Gemfile.lock b/server/Gemfile.lock index 8ac7bd9fd..1318e8fe5 100644 --- a/server/Gemfile.lock +++ b/server/Gemfile.lock @@ -418,6 +418,7 @@ GEM ffi (~> 1.12) ruby2_keywords (0.0.5) ruby_dig (0.0.2) + rubyzip (2.3.2) sanitize (6.0.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -576,6 +577,7 @@ DEPENDENCIES rack-mini-profiler (~> 2.0) rails (~> 7.1) rinku (~> 2.0.6) + rubyzip (~> 2.3.2) sanitize (~> 6.0.2) sass-rails (>= 6) scenic (~> 1.5.4) diff --git a/server/app/graphql/resolvers/top_level_organizations.rb b/server/app/graphql/resolvers/top_level_organizations.rb index baa0d155f..e0a40be2d 100644 --- a/server/app/graphql/resolvers/top_level_organizations.rb +++ b/server/app/graphql/resolvers/top_level_organizations.rb @@ -4,26 +4,32 @@ class Resolvers::TopLevelOrganizations < GraphQL::Schema::Resolver include SearchObject.module(:graphql) - type Types::Entities::OrganizationType.connection_type, null: false + type Types::BrowseTables::BrowseOrganizationType.connection_type, null: false description 'List and filter organizations.' - scope { Organization.order(:name) } + scope { MaterializedViews::OrganizationBrowseTableRow.order(:name) } option(:id, type: GraphQL::Types::Int, description: 'Exact match filtering on the id of the organization.') do |scope, value | - scope.where("organizations.id = ?", value) + scope.where(id: value) end option(:name, type: GraphQL::Types::String, description: 'Substring filtering on the name of the organization.') do |scope, value| - scope.where("organizations.name ILIKE ?", "%#{value}%") + scope.where("name ILIKE ?", "%#{value}%") end option :sort_by, type: Types::BrowseTables::OrganizationSortType, description: 'Columm and direction to sort evidence on.' do |scope, value| case value.column when 'ID' - scope.reorder("organizations.id #{value.direction}") + scope.reorder("id #{value.direction}") when 'NAME' - scope.reorder("organizations.name #{value.direction}") + scope.reorder("name #{value.direction}") + when 'ACTIVITY_COUNT' + scope.reorder("activity_count #{value.direction}") + when 'MEMBER_COUNT' + scope.reorder("member_count #{value.direction}") + when 'MOST_RECENT_ACTIVITY_TIMESTAMP' + scope.reorder("most_recent_activity_timestamp #{value.direction}") end end end diff --git a/server/app/graphql/types/browse_tables/browse_organization_type.rb b/server/app/graphql/types/browse_tables/browse_organization_type.rb new file mode 100644 index 000000000..795cd33d1 --- /dev/null +++ b/server/app/graphql/types/browse_tables/browse_organization_type.rb @@ -0,0 +1,23 @@ +module Types::BrowseTables + class BrowseOrganizationType < Types::BaseObject + connection_type_class(Types::Connections::BrowseTableConnection) + + field :id, Int, null: false + field :name, String, null: false + field :url, String, null: false + field :description, String, null: false + field :parent_id, Int, null: true + field :created_at, GraphQL::Types::ISO8601DateTime, null: true + field :updated_at, GraphQL::Types::ISO8601DateTime, null: true + field :most_recent_activity_timestamp, GraphQL::Types::ISO8601DateTime, null: true + field :activity_count, Int, null: false + field :member_count, Int, null: false + field :child_organizations, [Types::Entities::OrganizationType], null: false + + def child_organizations + Array(object.child_organizations) + .sort_by { |f| f['child_name'] } + .map { |f| { id: f['child_id'], name: f['child_name']} } + end + end +end \ No newline at end of file diff --git a/server/app/graphql/types/browse_tables/organization_sort_columns.rb b/server/app/graphql/types/browse_tables/organization_sort_columns.rb index 2e3d9bbab..a539093ac 100644 --- a/server/app/graphql/types/browse_tables/organization_sort_columns.rb +++ b/server/app/graphql/types/browse_tables/organization_sort_columns.rb @@ -2,5 +2,8 @@ module Types::BrowseTables class OrganizationSortColumns < Types::BaseEnum value 'ID' value 'NAME' + value 'ACTIVITY_COUNT' + value 'MEMBER_COUNT' + value 'MOST_RECENT_ACTIVITY_TIMESTAMP' end end diff --git a/server/app/jobs/update_nci_thesaurus.rb b/server/app/jobs/update_nci_thesaurus.rb index 50c290228..bdefd86d6 100644 --- a/server/app/jobs/update_nci_thesaurus.rb +++ b/server/app/jobs/update_nci_thesaurus.rb @@ -34,7 +34,7 @@ def remove_download end def latest_ncit_path - "https://stars.renci.org/var/NCIt/ncit.obo" + "https://evs.nci.nih.gov/ftp1/NCI_Thesaurus/Thesaurus.FLAT.zip" end end diff --git a/server/app/lib/importer/nci_thesaurus_mirror.rb b/server/app/lib/importer/nci_thesaurus_mirror.rb index d1a4ece59..8368c9a7d 100644 --- a/server/app/lib/importer/nci_thesaurus_mirror.rb +++ b/server/app/lib/importer/nci_thesaurus_mirror.rb @@ -1,14 +1,25 @@ +require "csv" +require "zip" + module Importer class NciThesaurusMirror attr_reader :parser, :version def initialize(path, version = Time.now.utc.iso8601) - @parser = Obo::Parser.new(path) + zip_file = Zip::File.open(path) + entry = zip_file.glob('*.txt').first + csv_text = entry.get_input_stream.read + @parser = CSV.parse( + csv_text, + col_sep: "\t", + liberal_parsing: true, + headers: ['code', 'concept_iri', 'parents', 'synonyms', 'definition', 'display_name', 'concept_status', 'semantic_type', 'concept_in_subset'], + ) @version = version end def import - parser.elements.each do |elem| + parser.each do |elem| if valid_entry?(elem) create_object_from_entry(elem) end @@ -16,33 +27,19 @@ def import end def valid_entry?(entry) - semantic_types = semantic_types(entry) - obsolete_concepts = obsolete_concepts(entry) - (entry['id'].present? && entry['name'].present? && entry.respond_to?(:name) && entry.name == 'Term' && - (semantic_types & valid_semantic_types).length > 0 && - (obsolete_concepts & ['Obsolete_Concept']).length == 0) - end - - def semantic_types(entry) - matcher = /^NCIT:P106 "(?.+)"/ - entry['property_value'].map { |s| s.match(matcher) }.compact.map { |s| s[:semantic_type] } + valid_semantic_types.include?(entry['semantic_type']) && entry['concept_status'].nil? end def valid_semantic_types ['Pharmacologic Substance', 'Pharmacological Substance', 'Clinical Drug', 'Therapeutic or Preventive Procedure', 'Hazardous or Poisonous Substance'] end - def obsolete_concepts(entry) - matcher = /^NCIT:P310 "(?.+)"/ - entry['property_value'].map { |s| s.match(matcher) }.compact.map { |s| s[:obsolete_concept] } - end - def create_object_from_entry(entry) - name = Therapy.capitalize_name(entry['name']) - ncit_id = entry['id'].sub('NCIT:', '') + synonyms = entry['synonyms'].split('|').map{|s| Therapy.capitalize_name(s)} + name = synonyms.shift() + ncit_id = entry['code'] therapy = ::Therapy.where(ncit_id: ncit_id).first_or_initialize therapy.name = name - synonyms = process_synonyms(entry['synonym']).uniq synonyms.each do |syn| therapy_alias = ::TherapyAlias.where(name: syn).first_or_create if !therapy.therapy_aliases.map{|a| a.name.downcase}.include?(syn.downcase) && !(syn.downcase == therapy.name.downcase) @@ -51,25 +48,6 @@ def create_object_from_entry(entry) end therapy.save end - - def process_synonyms(synonym_element) - vals = if synonym_element.blank? - [] - elsif synonym_element.is_a?(String) - [extract_synonym(synonym_element)] - elsif synonym_element.is_a?(Array) - synonym_element.map { |s| extract_synonym(s) } - end - vals.compact - end - - def extract_synonym(value) - if match_data = value.match(/^"(?.+)" EXACT \[.*\]/) - Therapy.capitalize_name(match_data[:name]) - else - nil - end - end end end diff --git a/server/app/models/materialized_views/organization_browse_table_row.rb b/server/app/models/materialized_views/organization_browse_table_row.rb new file mode 100644 index 000000000..4206d2e8c --- /dev/null +++ b/server/app/models/materialized_views/organization_browse_table_row.rb @@ -0,0 +1,2 @@ +class MaterializedViews::OrganizationBrowseTableRow < MaterializedViews::MaterializedView +end \ No newline at end of file diff --git a/server/db/migrate/20240625182325_create_organization_browse_table_rows.rb b/server/db/migrate/20240625182325_create_organization_browse_table_rows.rb new file mode 100644 index 000000000..dd47443f9 --- /dev/null +++ b/server/db/migrate/20240625182325_create_organization_browse_table_rows.rb @@ -0,0 +1,5 @@ +class CreateOrganizationBrowseTableRows < ActiveRecord::Migration[7.1] + def change + create_view :organization_browse_table_rows, materialized: true + end +end diff --git a/server/db/schema.rb b/server/db/schema.rb index 0b3bf90cb..3f99f9f3a 100644 --- a/server/db/schema.rb +++ b/server/db/schema.rb @@ -10,7 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. + ActiveRecord::Schema[7.1].define(version: 2024_06_28_151626) do + # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1399,6 +1401,7 @@ LEFT JOIN organizations child ON ((child.parent_id = organizations.id))) GROUP BY organizations.id; SQL + create_view "user_browse_table_rows", materialized: true, sql_definition: <<-SQL SELECT users.id, users.email, diff --git a/server/db/views/organization_browse_table_rows_v01.sql b/server/db/views/organization_browse_table_rows_v01.sql new file mode 100644 index 000000000..728b5750d --- /dev/null +++ b/server/db/views/organization_browse_table_rows_v01.sql @@ -0,0 +1,16 @@ +SELECT organizations.id, + organizations.name, + organizations.url, + organizations.description, + organizations.parent_id, + organizations.created_at, + organizations.updated_at, + organizations.most_recent_activity_timestamp, + COUNT(DISTINCT(activities.id)) as activity_count, + COUNT(DISTINCT(affiliations.user_id)) as member_count, + json_agg(distinct jsonb_build_object('child_id', child.id, 'child_name', child.name)) FILTER (WHERE child.id IS NOT NULL) as child_organizations +FROM organizations +LEFT OUTER JOIN activities on activities.organization_id = organizations.id +LEFT OUTER JOIN affiliations on affiliations.organization_id = organizations.id +LEFT OUTER JOIN organizations child on child.parent_id = organizations.id +GROUP BY organizations.id; \ No newline at end of file