diff --git a/client/src/app/components/molecular-profiles/molecular-profile-fusion-variant-card/molecular-profile-fusion-variant-card.component.html b/client/src/app/components/molecular-profiles/molecular-profile-fusion-variant-card/molecular-profile-fusion-variant-card.component.html index bb4573c58..9a2f8fc6c 100644 --- a/client/src/app/components/molecular-profiles/molecular-profile-fusion-variant-card/molecular-profile-fusion-variant-card.component.html +++ b/client/src/app/components/molecular-profiles/molecular-profile-fusion-variant-card/molecular-profile-fusion-variant-card.component.html @@ -27,10 +27,10 @@ - + + nzTitle="VICC Nomenclature"> {{ variant.viccCompliantName }} diff --git a/client/src/app/components/variants/fusion-variant-summary/fusion-variant-summary.page.html b/client/src/app/components/variants/fusion-variant-summary/fusion-variant-summary.page.html index e8d3d3b3f..37c513e3c 100644 --- a/client/src/app/components/variants/fusion-variant-summary/fusion-variant-summary.page.html +++ b/client/src/app/components/variants/fusion-variant-summary/fusion-variant-summary.page.html @@ -114,9 +114,9 @@ nzSize="small" [nzColumn]="{ xxl: 2, xl: 2, lg: 1, md: 1, sm: 1, xs: 1 }" nzBordered="true"> - + {{ variant.viccCompliantName }} diff --git a/client/src/app/components/variants/variant-popover/variant-popover.component.html b/client/src/app/components/variants/variant-popover/variant-popover.component.html index 5aa804b1f..2920fc2d1 100644 --- a/client/src/app/components/variants/variant-popover/variant-popover.component.html +++ b/client/src/app/components/variants/variant-popover/variant-popover.component.html @@ -25,9 +25,9 @@ nzLayout="horizontal" [nzColumn]="1" nzBordered="true"> - + {{ variant.viccCompliantName }} diff --git a/client/src/app/forms/types/feature-select/feature-select.type.ts b/client/src/app/forms/types/feature-select/feature-select.type.ts index cf5e80534..b1db3df1c 100644 --- a/client/src/app/forms/types/feature-select/feature-select.type.ts +++ b/client/src/app/forms/types/feature-select/feature-select.type.ts @@ -211,6 +211,7 @@ export class CvcFeatureSelectField nzContent: CvcFusionSelectForm, nzData: {}, nzFooter: null, + nzWidth: '40%', } ) diff --git a/client/src/app/forms/types/feature-select/fusion-select/fusion-add.query.gql b/client/src/app/forms/types/feature-select/fusion-select/fusion-add.query.gql index 76335dbe9..2d49aafb1 100644 --- a/client/src/app/forms/types/feature-select/fusion-select/fusion-add.query.gql +++ b/client/src/app/forms/types/feature-select/fusion-select/fusion-add.query.gql @@ -1,13 +1,15 @@ -mutation SelectOrCreateFusion($organizationId: Int, $fivePrimeGeneId: Int, $fivePrimePartnerStatus: FusionPartnerStatus!, $threePrimeGeneId: Int, $threePrimePartnerStatus: FusionPartnerStatus! ) { +mutation SelectOrCreateFusion($organizationId: Int, $fivePrimeGeneId: Int, $fivePrimeRegulatoryFusionType: RegulatoryFusionType, $fivePrimePartnerStatus: FusionPartnerStatus!, $threePrimeGeneId: Int, $threePrimeRegulatoryFusionType: RegulatoryFusionType, $threePrimePartnerStatus: FusionPartnerStatus!) { createFusionFeature(input: { organizationId: $organizationId fivePrimeGene: { geneId: $fivePrimeGeneId, partnerStatus: $fivePrimePartnerStatus + regulatoryFusionType: $fivePrimeRegulatoryFusionType }, threePrimeGene: { geneId: $threePrimeGeneId, - partnerStatus: $threePrimePartnerStatus + partnerStatus: $threePrimePartnerStatus, + regulatoryFusionType: $threePrimeRegulatoryFusionType } }) { diff --git a/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.html b/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.html index f8c57f847..2f9bdda69 100644 --- a/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.html +++ b/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.html @@ -1,13 +1,46 @@ + + + + +
+
+@if(fusionType == 'transcript') {
+ (ngSubmit)="submitFusion(transcriptModel)" + [formGroup]="transcriptForm">
+} @else { +
+ + +
+} diff --git a/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.ts b/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.ts index 2f6634f99..4d0c2bc8b 100644 --- a/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.ts +++ b/client/src/app/forms/types/feature-select/fusion-select/fusion-select.form.ts @@ -7,6 +7,7 @@ import { } from '@angular/core' import { AbstractControl, + FormsModule, ReactiveFormsModule, UntypedFormGroup, } from '@angular/forms' @@ -14,6 +15,7 @@ import { FeatureInstanceTypes, FusionPartnerStatus, Maybe, + RegulatoryFusionType, SelectOrCreateFusionGQL, SelectOrCreateFusionMutation, SelectOrCreateFusionMutationVariables, @@ -36,12 +38,16 @@ import { } from '@app/core/utilities/mutation-state-wrapper' import { NetworkErrorsService } from '@app/core/services/network-errors.service' import { NZ_MODAL_DATA, NzModalModule, NzModalRef } from 'ng-zorro-antd/modal' +import { NzRadioModule } from 'ng-zorro-antd/radio' +import { NzSpaceModule } from 'ng-zorro-antd/space' type FusionSelectModel = { fivePrimeGeneId?: number fivePrimePartnerStatus: FusionPartnerStatus + fivePrimeRegulatoryFusionType?: RegulatoryFusionType threePrimeGeneId?: number threePrimePartnerStatus: FusionPartnerStatus + threePrimeRegulatoryFusionType?: RegulatoryFusionType } export interface FusionSelectModalData { @@ -56,11 +62,14 @@ export interface FusionSelectModalData { changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CommonModule, + FormsModule, ReactiveFormsModule, NzFormModule, NzButtonModule, NzAlertModule, NzModalModule, + NzRadioModule, + NzSpaceModule, RouterModule, FormlyModule, ], @@ -71,11 +80,31 @@ export class CvcFusionSelectForm { readonly #modal = inject(NzModalRef) readonly nzModalData: FusionSelectModalData = inject(NZ_MODAL_DATA) - model: FusionSelectModel - form: UntypedFormGroup - config: FormlyFieldConfig[] + transcriptForm: UntypedFormGroup + regulatoryForm: UntypedFormGroup layout: NzFormLayoutType = 'vertical' + transcriptConfig: FormlyFieldConfig[] + regulatoryConfig: FormlyFieldConfig[] + + transcriptModel: FusionSelectModel = { + fivePrimeGeneId: undefined, + threePrimeGeneId: undefined, + fivePrimePartnerStatus: FusionPartnerStatus.Known, + threePrimePartnerStatus: FusionPartnerStatus.Known, + fivePrimeRegulatoryFusionType: undefined, + threePrimeRegulatoryFusionType: undefined, + } + + regulatoryModel: FusionSelectModel = { + fivePrimeGeneId: undefined, + threePrimeGeneId: undefined, + fivePrimePartnerStatus: FusionPartnerStatus.Regulatory, + threePrimePartnerStatus: FusionPartnerStatus.Known, + fivePrimeRegulatoryFusionType: undefined, + threePrimeRegulatoryFusionType: undefined, + } + options: FormlyFormOptions selectOrCreateFusionMutator: MutatorWithState< @@ -86,20 +115,17 @@ export class CvcFusionSelectForm { mutationState?: MutationState + fusionType: 'transcript' | 'regulatory' = 'transcript' + constructor( private query: SelectOrCreateFusionGQL, - private errors: NetworkErrorsService + errors: NetworkErrorsService ) { this.selectOrCreateFusionMutator = new MutatorWithState(errors) - this.form = new UntypedFormGroup({}) + this.transcriptForm = new UntypedFormGroup({}) + this.regulatoryForm = new UntypedFormGroup({}) - this.model = { - fivePrimeGeneId: undefined, - threePrimeGeneId: undefined, - fivePrimePartnerStatus: FusionPartnerStatus.Known, - threePrimePartnerStatus: FusionPartnerStatus.Known, - } this.options = {} const selectOptions = [ @@ -117,7 +143,22 @@ export class CvcFusionSelectForm { }, ] - this.config = [ + const regulatoryOptions = Object.keys(RegulatoryFusionType) + .map((x) => { + const val = RegulatoryFusionType[x as keyof typeof RegulatoryFusionType] + return { label: val, value: val } + }) + .sort((a, b) => { + if (a.label == 'enhancer') { + return -1 + } else if (a.label == 'promoter') { + return -1 + } else { + return a.label.localeCompare(b.label) + } + }) + + this.transcriptConfig = [ { wrappers: ['form-layout'], props: { @@ -157,11 +198,7 @@ export class CvcFusionSelectForm { fieldGroup: [ { wrappers: ['form-card'], - props: { - formCardOptions: { - title: 'New Fusion Feature', - }, - }, + props: {}, fieldGroup: [ { wrappers: ['form-row'], @@ -177,13 +214,36 @@ export class CvcFusionSelectForm { props: { label: "5' Partner Status", tooltip: - "Select Known if the specific 5' Gene partner is known, Unknown if not. Select Multiple if there are multiple potential 5' Gene partners", + "Select Known if the specific 5' Gene partner is known, Unknown if not. Select Multiple if there are multiple potential 5' Gene partners.", required: true, placeholder: "5' Partner Status", options: selectOptions, multiple: false, }, }, + { + key: 'threePrimePartnerStatus', + type: 'base-select', + props: { + required: true, + placeholder: "3' Partner Status", + label: "3' Partner Status", + tooltip: + "Select Known if the specific 3' Gene partner is known, Unknown if not. Select Multiple if there are multiple potential 3' Gene partners.", + options: selectOptions, + multiple: false, + }, + }, + ], + }, + { + wrappers: ['form-row'], + props: { + formRowOptions: { + span: 12, + }, + }, + fieldGroup: [ { key: 'fivePrimeGeneId', type: 'feature-select', @@ -204,8 +264,78 @@ export class CvcFusionSelectForm { FusionPartnerStatus.Known, }, }, + { + key: 'threePrimeGeneId', + type: 'feature-select', + props: { + label: "3' Fusion Partner", + placeholder: 'Select Gene', + tooltip: "Select the 3' Gene partner in the Fusion", + canChangeFeatureType: false, + hideFeatureTypeSelect: true, + featureType: FeatureInstanceTypes.Gene, + }, + expressions: { + 'props.disabled': (field) => + field.model.threePrimePartnerStatus != + FusionPartnerStatus.Known, + 'props.required': (field) => + field.model.threePrimePartnerStatus == + FusionPartnerStatus.Known, + }, + }, + ], + }, + { + wrappers: ['form-row'], + props: { + formRowOptions: { + span: 24, + }, + }, + fieldGroup: [ + { + key: 'organizationId', + type: 'org-submit-button', + props: { + submitLabel: 'Create Fusion', + align: 'right', + }, + }, ], }, + ], + }, + ], + }, + ] + + this.regulatoryConfig = [ + { + wrappers: ['form-layout'], + props: { + showDevPanel: false, + }, + validators: { + sameGene: { + message: "5' and 3' Genes must be different", + expression: (x: AbstractControl) => { + const model = x.value + if (model && model.fivePrimeGeneId && model.threePrimeGeneId) { + if (model.fivePrimeGeneId == model.threePrimeGeneId) { + return false + } + } + return true + }, + errorPath: 'fivePrimeGeneId', + }, + }, + fieldGroup: [ + { + wrappers: ['form-card'], + props: {}, + fieldGroup: [ { wrappers: ['form-row'], props: { @@ -214,6 +344,18 @@ export class CvcFusionSelectForm { }, }, fieldGroup: [ + { + key: 'fivePrimeRegulatoryFusionType', + type: 'base-select', + props: { + label: 'Regulatory Element Type', + tooltip: '', + required: true, + placeholder: 'Regulatory Element', + options: regulatoryOptions, + multiple: false, + }, + }, { key: 'threePrimePartnerStatus', type: 'base-select', @@ -222,11 +364,35 @@ export class CvcFusionSelectForm { placeholder: "3' Partner Status", label: "3' Partner Status", tooltip: - "Select Known if the specific 3' Gene partner is known, Unknown if not. Select Multiple if there are multiple potential 3' Gene partners", + "Select Known if the specific 3' Gene partner is known, Unknown if not. Select Multiple if there are multiple potential 3' Gene partners.", options: selectOptions, multiple: false, }, }, + ], + }, + { + wrappers: ['form-row'], + props: { + formRowOptions: { + span: 12, + }, + }, + fieldGroup: [ + { + key: 'fivePrimeGeneId', + type: 'feature-select', + props: { + label: 'Regulatory Partner', + placeholder: 'Select Gene', + tooltip: + 'Select the Regulatory Gene partner in the Fusion', + canChangeFeatureType: false, + hideFeatureTypeSelect: true, + featureType: FeatureInstanceTypes.Gene, + required: true, + }, + }, { key: 'threePrimeGeneId', type: 'feature-select', @@ -276,18 +442,24 @@ export class CvcFusionSelectForm { modelChange(model: Maybe) { if (model) { - if (this.model.fivePrimePartnerStatus != FusionPartnerStatus.Known) { - this.model = { - ...this.model, - fivePrimeGeneId: undefined, - } - } - if (this.model.threePrimePartnerStatus != FusionPartnerStatus.Known) { - this.model = { - ...this.model, - threePrimeGeneId: undefined, - } - } + //if ( + // this.model.fivePrimePartnerStatus != FusionPartnerStatus.Known && + // this.model.fivePrimePartnerStatus != FusionPartnerStatus.Regulatory + //) { + // this.model = { + // ...this.model, + // fivePrimeGeneId: undefined, + // } + //} + //if ( + // this.model.threePrimePartnerStatus != FusionPartnerStatus.Known && + // this.model.threePrimePartnerStatus != FusionPartnerStatus.Regulatory + //) { + // this.model = { + // ...this.model, + // threePrimeGeneId: undefined, + // } + //} //mark form as invalid here? if ( diff --git a/client/src/app/generated/civic.apollo-helpers.ts b/client/src/app/generated/civic.apollo-helpers.ts index 30b8db065..6f02e4b8c 100644 --- a/client/src/app/generated/civic.apollo-helpers.ts +++ b/client/src/app/generated/civic.apollo-helpers.ts @@ -1193,7 +1193,7 @@ export type FlaggableFieldPolicy = { link?: FieldPolicy | FieldReadFunction, name?: FieldPolicy | FieldReadFunction }; -export type FusionKeySpecifier = ('comments' | 'creationActivity' | 'deprecated' | 'deprecationActivity' | 'deprecationReason' | 'description' | 'events' | 'featureAliases' | 'featureInstance' | 'featureType' | 'fivePrimeGene' | 'fivePrimePartnerStatus' | 'flagged' | 'flags' | 'fullName' | 'id' | 'lastAcceptedRevisionEvent' | 'lastCommentEvent' | 'lastSubmittedRevisionEvent' | 'link' | 'name' | 'openRevisionCount' | 'revisions' | 'sources' | 'threePrimeGene' | 'threePrimePartnerStatus' | 'variants' | FusionKeySpecifier)[]; +export type FusionKeySpecifier = ('comments' | 'creationActivity' | 'deprecated' | 'deprecationActivity' | 'deprecationReason' | 'description' | 'events' | 'featureAliases' | 'featureInstance' | 'featureType' | 'fivePrimeGene' | 'fivePrimePartnerStatus' | 'flagged' | 'flags' | 'fullName' | 'id' | 'lastAcceptedRevisionEvent' | 'lastCommentEvent' | 'lastSubmittedRevisionEvent' | 'link' | 'name' | 'openRevisionCount' | 'regulatoryFusionType' | 'revisions' | 'sources' | 'threePrimeGene' | 'threePrimePartnerStatus' | 'variants' | FusionKeySpecifier)[]; export type FusionFieldPolicy = { comments?: FieldPolicy | FieldReadFunction, creationActivity?: FieldPolicy | FieldReadFunction, @@ -1217,6 +1217,7 @@ export type FusionFieldPolicy = { link?: FieldPolicy | FieldReadFunction, name?: FieldPolicy | FieldReadFunction, openRevisionCount?: FieldPolicy | FieldReadFunction, + regulatoryFusionType?: FieldPolicy | FieldReadFunction, revisions?: FieldPolicy | FieldReadFunction, sources?: FieldPolicy | FieldReadFunction, threePrimeGene?: FieldPolicy | FieldReadFunction, diff --git a/client/src/app/generated/civic.apollo.ts b/client/src/app/generated/civic.apollo.ts index 5405547ce..91bcd3fbb 100644 --- a/client/src/app/generated/civic.apollo.ts +++ b/client/src/app/generated/civic.apollo.ts @@ -2931,6 +2931,7 @@ export type Fusion = Commentable & EventOriginObject & EventSubject & Flaggable link: Scalars['String']['output']; name: Scalars['String']['output']; openRevisionCount: Scalars['Int']['output']; + regulatoryFusionType?: Maybe; /** List and filter revisions. */ revisions: RevisionConnection; sources: Array; @@ -3052,11 +3053,14 @@ export type FusionPartnerInput = { geneId?: InputMaybe; /** The status of the fusion partner */ partnerStatus: FusionPartnerStatus; + /** If the fusion partner status is set to regulatory, what type of regulatory fusion is it? */ + regulatoryFusionType?: InputMaybe; }; export enum FusionPartnerStatus { Known = 'KNOWN', Multiple = 'MULTIPLE', + Regulatory = 'REGULATORY', Unknown = 'UNKNOWN' } @@ -5731,6 +5735,35 @@ export enum ReferenceBuild { Ncbi36 = 'NCBI36' } +export enum RegulatoryFusionType { + CaatSignal = 'CAAT_signal', + DNaseIHypersensitiveSite = 'DNase_I_hypersensitive_site', + GcSignal = 'GC_signal', + TataBox = 'TATA_box', + Attenuator = 'attenuator', + Enhancer = 'enhancer', + EnhancerBlockingElement = 'enhancer_blocking_element', + ImprintingControlRegion = 'imprinting_control_region', + Insulator = 'insulator', + LocusControlRegion = 'locus_control_region', + MatrixAttachmentRegion = 'matrix_attachment_region', + Minus_10Signal = 'minus_10_signal', + Minus_35Signal = 'minus_35_signal', + Other = 'other', + PolyASignalSequence = 'polyA_signal_sequence', + Promoter = 'promoter', + RecodingStimulatoryRegion = 'recoding_stimulatory_region', + RecombinationEnhancer = 'recombination_enhancer', + ReplicationRegulatoryRegion = 'replication_regulatory_region', + ResponseElement = 'response_element', + RibosomeBindingSite = 'ribosome_binding_site', + Riboswitch = 'riboswitch', + Silencer = 'silencer', + Terminator = 'terminator', + TranscriptionalCisRegulatoryRegion = 'transcriptional_cis_regulatory_region', + UOrf = 'uORF' +} + export type RejectRevisionsActivity = ActivityInterface & { __typename: 'RejectRevisionsActivity'; createdAt: Scalars['ISO8601DateTime']['output']; @@ -9407,8 +9440,10 @@ export type FeatureSelectTypeaheadFieldsFragment = { __typename: 'Feature', id: export type SelectOrCreateFusionMutationVariables = Exact<{ organizationId?: InputMaybe; fivePrimeGeneId?: InputMaybe; + fivePrimeRegulatoryFusionType?: InputMaybe; fivePrimePartnerStatus: FusionPartnerStatus; threePrimeGeneId?: InputMaybe; + threePrimeRegulatoryFusionType?: InputMaybe; threePrimePartnerStatus: FusionPartnerStatus; }>; @@ -17374,9 +17409,9 @@ export const FeatureSelectTagDocument = gql` } } export const SelectOrCreateFusionDocument = gql` - mutation SelectOrCreateFusion($organizationId: Int, $fivePrimeGeneId: Int, $fivePrimePartnerStatus: FusionPartnerStatus!, $threePrimeGeneId: Int, $threePrimePartnerStatus: FusionPartnerStatus!) { + mutation SelectOrCreateFusion($organizationId: Int, $fivePrimeGeneId: Int, $fivePrimeRegulatoryFusionType: RegulatoryFusionType, $fivePrimePartnerStatus: FusionPartnerStatus!, $threePrimeGeneId: Int, $threePrimeRegulatoryFusionType: RegulatoryFusionType, $threePrimePartnerStatus: FusionPartnerStatus!) { createFusionFeature( - input: {organizationId: $organizationId, fivePrimeGene: {geneId: $fivePrimeGeneId, partnerStatus: $fivePrimePartnerStatus}, threePrimeGene: {geneId: $threePrimeGeneId, partnerStatus: $threePrimePartnerStatus}} + input: {organizationId: $organizationId, fivePrimeGene: {geneId: $fivePrimeGeneId, partnerStatus: $fivePrimePartnerStatus, regulatoryFusionType: $fivePrimeRegulatoryFusionType}, threePrimeGene: {geneId: $threePrimeGeneId, partnerStatus: $threePrimePartnerStatus, regulatoryFusionType: $threePrimeRegulatoryFusionType}} ) { new feature { diff --git a/client/src/app/generated/server.model.graphql b/client/src/app/generated/server.model.graphql index cbd4f2c88..bdd9f4d0a 100644 --- a/client/src/app/generated/server.model.graphql +++ b/client/src/app/generated/server.model.graphql @@ -5034,6 +5034,7 @@ type Fusion implements Commentable & EventOriginObject & EventSubject & Flaggabl link: String! name: String! openRevisionCount: Int! + regulatoryFusionType: RegulatoryFusionType """ List and filter revisions. @@ -5229,11 +5230,17 @@ input FusionPartnerInput { The status of the fusion partner """ partnerStatus: FusionPartnerStatus! + + """ + If the fusion partner status is set to regulatory, what type of regulatory fusion is it? + """ + regulatoryFusionType: RegulatoryFusionType } enum FusionPartnerStatus { KNOWN MULTIPLE + REGULATORY UNKNOWN } @@ -9878,6 +9885,35 @@ enum ReferenceBuild { NCBI36 } +enum RegulatoryFusionType { + CAAT_signal + DNase_I_hypersensitive_site + GC_signal + TATA_box + attenuator + enhancer + enhancer_blocking_element + imprinting_control_region + insulator + locus_control_region + matrix_attachment_region + minus_10_signal + minus_35_signal + other + polyA_signal_sequence + promoter + recoding_stimulatory_region + recombination_enhancer + replication_regulatory_region + response_element + ribosome_binding_site + riboswitch + silencer + terminator + transcriptional_cis_regulatory_region + uORF +} + type RejectRevisionsActivity implements ActivityInterface { createdAt: ISO8601DateTime! events: [Event!]! diff --git a/client/src/app/generated/server.schema.json b/client/src/app/generated/server.schema.json index 9abd2613d..a11cc1037 100644 --- a/client/src/app/generated/server.schema.json +++ b/client/src/app/generated/server.schema.json @@ -23958,6 +23958,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "regulatoryFusionType", + "description": null, + "args": [], + "type": { + "kind": "ENUM", + "name": "RegulatoryFusionType", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "revisions", "description": "List and filter revisions.", @@ -24607,6 +24619,18 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "regulatoryFusionType", + "description": "If the fusion partner status is set to regulatory, what type of regulatory fusion is it?", + "type": { + "kind": "ENUM", + "name": "RegulatoryFusionType", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "interfaces": null, @@ -24638,6 +24662,12 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "REGULATORY", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -44810,6 +44840,173 @@ ], "possibleTypes": null }, + { + "kind": "ENUM", + "name": "RegulatoryFusionType", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "attenuator", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CAAT_signal", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "DNase_I_hypersensitive_site", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enhancer", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enhancer_blocking_element", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "GC_signal", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "imprinting_control_region", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "insulator", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locus_control_region", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "matrix_attachment_region", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "minus_35_signal", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "minus_10_signal", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "polyA_signal_sequence", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "promoter", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "recoding_stimulatory_region", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "recombination_enhancer", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "replication_regulatory_region", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "response_element", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ribosome_binding_site", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "riboswitch", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "silencer", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "TATA_box", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "terminator", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "transcriptional_cis_regulatory_region", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "uORF", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "other", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "OBJECT", "name": "RejectRevisionsActivity", diff --git a/server/app/graphql/mutations/create_fusion_feature.rb b/server/app/graphql/mutations/create_fusion_feature.rb index cafd2a96d..cde0a1de7 100644 --- a/server/app/graphql/mutations/create_fusion_feature.rb +++ b/server/app/graphql/mutations/create_fusion_feature.rb @@ -36,14 +36,22 @@ def ready?(organization_id: nil, five_prime_gene:, three_prime_gene:, **kwargs) # check that partner status matches gene_id presence [ five_prime_gene, three_prime_gene ].each do |gene_input| - if gene_input.gene_id.present? && gene_input.partner_status != "known" - raise GraphQL::ExecutionError, "Partner status needs to be 'known' if a gene_id is set" + if gene_input.gene_id.present? && (gene_input.partner_status != "known" && gene_input.partner_status != "regulatory") + raise GraphQL::ExecutionError, "Partner status needs to be 'known' or 'regulatory' if a gene_id is set" end - if gene_input.gene_id.blank? && gene_input.partner_status == "known" - raise GraphQL::ExecutionError, "Partner status can't be 'known' if a gene_id is not set" + if gene_input.gene_id.blank? && (gene_input.partner_status == "known" || gene_input.partner_status == "regulatory") + raise GraphQL::ExecutionError, "Partner status can't be 'known' or 'regulatory' if a gene_id is not set" end end + # check that maximum one gene has regulatory_fusion_type set + if five_prime_gene.partner_status == "regulatory" && three_prime_gene.partner_status == "regulatory" + raise GraphQL::ExecutionError, "Only one Fusion partner can be marked 'regulatory'" + end + if five_prime_gene.regulatory_fusion_type.present? && three_prime_gene.regulatory_fusion_type.present? + raise GraphQL::ExecutionError, "Only one Fusion partner can have a regulatory fusion type set." + end + return true end @@ -53,12 +61,16 @@ def authorized?(organization_id: nil, **kwargs) end def resolve(five_prime_gene:, three_prime_gene:, organization_id: nil) + # only one can be set + regulatory_fusion_type = five_prime_gene.regulatory_fusion_type || three_prime_gene.regulatory_fusion_type + existing_feature_instance = Features::Fusion .find_by( five_prime_gene_id: five_prime_gene.gene_id, three_prime_gene_id: three_prime_gene.gene_id, five_prime_partner_status: five_prime_gene.partner_status, three_prime_partner_status: three_prime_gene.partner_status, + regulatory_fusion_type: regulatory_fusion_type ) if existing_feature_instance.present? @@ -73,6 +85,7 @@ def resolve(five_prime_gene:, three_prime_gene:, organization_id: nil) three_prime_gene_id: three_prime_gene.gene_id, five_prime_partner_status: five_prime_gene.partner_status, three_prime_partner_status: three_prime_gene.partner_status, + regulatory_fusion_type: regulatory_fusion_type, originating_user: context[:current_user], organization_id: organization_id, ) diff --git a/server/app/graphql/types/entities/fusion_type.rb b/server/app/graphql/types/entities/fusion_type.rb index b438ebaad..c7ebf745e 100644 --- a/server/app/graphql/types/entities/fusion_type.rb +++ b/server/app/graphql/types/entities/fusion_type.rb @@ -6,6 +6,8 @@ class FusionType < Types::Entities::FeatureType field :five_prime_partner_status, Types::Fusion::FusionPartnerStatus, null: false field :three_prime_partner_status, Types::Fusion::FusionPartnerStatus, null: false + field :regulatory_fusion_type, Types::Fusion::RegulatoryFusionTypeType, null: true + def five_prime_gene Loaders::AssociationLoader.for(Features::Fusion, :five_prime_gene).load(object) end diff --git a/server/app/graphql/types/fusion/fusion_partner_input_type.rb b/server/app/graphql/types/fusion/fusion_partner_input_type.rb index a150e802f..55b3bc657 100644 --- a/server/app/graphql/types/fusion/fusion_partner_input_type.rb +++ b/server/app/graphql/types/fusion/fusion_partner_input_type.rb @@ -6,5 +6,7 @@ class FusionPartnerInputType < Types::BaseInputObject description: "The status of the fusion partner" argument :gene_id, Int, required: false, description: "The CIViC gene ID of the partner, if known" + argument :regulatory_fusion_type, Types::Fusion::RegulatoryFusionTypeType, required: false, + description: "If the fusion partner status is set to regulatory, what type of regulatory fusion is it?" end end diff --git a/server/app/graphql/types/fusion/fusion_partner_status.rb b/server/app/graphql/types/fusion/fusion_partner_status.rb index c0842ead0..ac59781e7 100644 --- a/server/app/graphql/types/fusion/fusion_partner_status.rb +++ b/server/app/graphql/types/fusion/fusion_partner_status.rb @@ -3,5 +3,6 @@ class FusionPartnerStatus < Types::BaseEnum value "KNOWN", value: "known" value "UNKNOWN", value: "unknown" value "MULTIPLE", value: "multiple" + value "REGULATORY", value: "regulatory" end end diff --git a/server/app/graphql/types/fusion/regulatory_fusion_type_type.rb b/server/app/graphql/types/fusion/regulatory_fusion_type_type.rb new file mode 100644 index 000000000..28e547c20 --- /dev/null +++ b/server/app/graphql/types/fusion/regulatory_fusion_type_type.rb @@ -0,0 +1,8 @@ +module Types::Fusion + class RegulatoryFusionTypeType < Types::BaseEnum + Constants::REGULATORY_FUSION_ENUM_TYPES + .each do |(name, _)| + value name, value: name + end + end +end diff --git a/server/app/models/actions/create_fusion_feature.rb b/server/app/models/actions/create_fusion_feature.rb index fd34cdfe6..bb5321036 100644 --- a/server/app/models/actions/create_fusion_feature.rb +++ b/server/app/models/actions/create_fusion_feature.rb @@ -2,10 +2,18 @@ module Actions class CreateFusionFeature include Actions::Transactional - attr_reader :feature, :originating_user, :organization_id, :create_variant, :five_prime_partner_status, :three_prime_partner_status + attr_reader :feature, :originating_user, :organization_id, :create_variant, :five_prime_partner_status, :three_prime_partner_status, :regulatory_fusion_type + + def initialize(originating_user:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, regulatory_fusion_type:, organization_id: nil, create_variant: true) + @five_prime_partner_status = five_prime_partner_status + @three_prime_partner_status = three_prime_partner_status + @regulatory_fusion_type = regulatory_fusion_type + @originating_user = originating_user + @organization_id = organization_id + @create_variant = create_variant - def initialize(originating_user:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, organization_id: nil, create_variant: true) feature_name = "#{construct_fusion_partner_name(five_prime_gene_id, five_prime_partner_status)}::#{construct_fusion_partner_name(three_prime_gene_id, three_prime_partner_status)}" + @feature = Feature.new( name: feature_name, ) @@ -14,18 +22,18 @@ def initialize(originating_user:, five_prime_gene_id:, three_prime_gene_id:, fiv three_prime_gene_id: three_prime_gene_id, five_prime_partner_status: five_prime_partner_status, three_prime_partner_status: three_prime_partner_status, + regulatory_fusion_type: regulatory_fusion_type, feature: feature, ) - @five_prime_partner_status = five_prime_partner_status - @three_prime_partner_status = three_prime_partner_status - @originating_user = originating_user - @organization_id = organization_id - @create_variant = create_variant end def construct_fusion_partner_name(gene_id, partner_status) if partner_status == "known" Features::Gene.find(gene_id).name + elsif partner_status == "regulatory" + gene_name = Features::Gene.find(gene_id).name + rft = Features::Fusion.format_regulatory_fusion_type(regulatory_fusion_type) + "#{rft}@#{gene_name}" elsif partner_status == "unknown" "?" elsif partner_status == "multiple" diff --git a/server/app/models/activities/create_fusion_feature.rb b/server/app/models/activities/create_fusion_feature.rb index 9ae8e0e41..0329ccaf7 100644 --- a/server/app/models/activities/create_fusion_feature.rb +++ b/server/app/models/activities/create_fusion_feature.rb @@ -1,13 +1,14 @@ module Activities class CreateFusionFeature < Base - attr_reader :feature, :five_prime_gene_id, :three_prime_gene_id, :five_prime_partner_status, :three_prime_partner_status, :create_variant + attr_reader :feature, :five_prime_gene_id, :three_prime_gene_id, :five_prime_partner_status, :three_prime_partner_status, :create_variant, :regulatory_fusion_type - def initialize(originating_user:, organization_id:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, create_variant: true) + def initialize(originating_user:, organization_id:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, regulatory_fusion_type:, create_variant: true) super(organization_id: organization_id, user: originating_user) @five_prime_gene_id = five_prime_gene_id @three_prime_gene_id = three_prime_gene_id @five_prime_partner_status = five_prime_partner_status @three_prime_partner_status = three_prime_partner_status + @regulatory_fusion_type = regulatory_fusion_type @create_variant = create_variant end @@ -25,6 +26,7 @@ def call_actions three_prime_gene_id: three_prime_gene_id, five_prime_partner_status: five_prime_partner_status, three_prime_partner_status: three_prime_partner_status, + regulatory_fusion_type: regulatory_fusion_type, originating_user: user, organization_id: organization&.id, create_variant: create_variant diff --git a/server/app/models/constants.rb b/server/app/models/constants.rb index b887c8b26..5a595771b 100644 --- a/server/app/models/constants.rb +++ b/server/app/models/constants.rb @@ -139,5 +139,39 @@ module Constants REPRESENTATIVE_FUSION_VARIANT_NAME = "Fusion" + # INSDC regulatory class vocabulary as required here: https://fusions.cancervariants.org/en/latest/nomenclature.html#regulatory-nomenclature + REGULATORY_FUSION_TYPES = [ + [ "attenuator", "SO:0000140" ], + [ "CAAT_signal", "SO:0000172" ], + [ "DNase_I_hypersensitive_site", "SO:0000685" ], + [ "enhancer", "SO:0000165" ], + [ "enhancer_blocking_element", nil ], + [ "GC_signal", "SO:0000173" ], + [ "imprinting_control_region", nil ], + [ "insulator", "SO:0000627" ], + [ "locus_control_region", "SO:0000037" ], + [ "matrix_attachment_region", "SO:0000036" ], + [ "minus_35_signal", "SO:0000176" ], + [ "minus_10_signal", "SO:0000175" ], + [ "polyA_signal_sequence", "SO:0000551" ], + [ "promoter", "SO:0000167" ], + [ "recoding_stimulatory_region", "SO:1001268" ], + [ "recombination_enhancer", "SO:0002059" ], + [ "replication_regulatory_region", "SO:0001682" ], + [ "response_element", nil ], + [ "ribosome_binding_site", "SO:0000552" ], + [ "riboswitch", "SO:0000035" ], + [ "silencer", "SO:0000625" ], + [ "TATA_box", "SO:0000174" ], + [ "terminator", "SO:0000141" ], + [ "transcriptional_cis_regulatory_region", "SO:0001055" ], + [ "uORF", "SO:0002027" ], + [ "other", nil ], + ] + + REGULATORY_FUSION_ENUM_TYPES = REGULATORY_FUSION_TYPES.map { |(type, _)| [ type, type ] }.to_h + + FUSION_PARTNER_STATUSES = [ "known", "unknown", "multiple", "regulatory" ].map { [ _1, _1 ] }.to_h + API_HMAC_KEY = ENV["CIVIC_API_HMAC_KEY"] || Rails.application.credentials.api_hmac_key end diff --git a/server/app/models/features/fusion.rb b/server/app/models/features/fusion.rb index c883162a2..22bb99573 100644 --- a/server/app/models/features/fusion.rb +++ b/server/app/models/features/fusion.rb @@ -6,17 +6,10 @@ class Fusion < ActiveRecord::Base belongs_to :five_prime_gene, class_name: "Features::Gene", optional: true belongs_to :three_prime_gene, class_name: "Features::Gene", optional: true - enum :five_prime_partner_status, { - known: "known", - unknown: "unknown", - multiple: "multiple", - }, prefix: true + enum :five_prime_partner_status, Constants::FUSION_PARTNER_STATUSES, prefix: true + enum :three_prime_partner_status, Constants::FUSION_PARTNER_STATUSES, prefix: true - enum :three_prime_partner_status, { - known: "known", - unknown: "unknown", - multiple: "multiple", - }, prefix: true + enum :regulatory_fusion_type, Constants::REGULATORY_FUSION_ENUM_TYPES has_many :variant_groups has_many :source_suggestions @@ -24,31 +17,22 @@ class Fusion < ActiveRecord::Base # TODO - move to feature? has_many :comment_mentions, foreign_key: :comment_id, class_name: "EntityMention" - validate :partner_status_valid_for_gene_ids - validate :at_least_one_gene_id + validates_with FusionFeatureValidator - def partner_status_valid_for_gene_ids - if !self.in_revision_validation_context - [ self.five_prime_gene, self.three_prime_gene ].zip([ self.five_prime_partner_status, self.three_prime_partner_status ], [ :five_prime_gene, :three_prime_gene ]).each do |gene, status, fk| - if gene.nil? && status == "known" - errors.add(fk, "Partner status cannot be 'known' if the gene isn't set") - elsif !gene.nil? && status != "known" - errors.add(fk, "Partner status has to be 'known' if gene is set") - end - end - end + def display_name + name end - def at_least_one_gene_id - if !self.in_revision_validation_context && self.five_prime_gene_id.nil? && self.three_prime_gene_id.nil? - errors.add(:base, "One or both of the genes need to be set") + def self.format_regulatory_fusion_type(rft) + if rft == "enhancer" + "reg_e" + elsif rft == "promoter" + "reg_p" + else + "reg_#{rft}" end end - def display_name - name - end - def editable_fields [ :description, diff --git a/server/app/models/variant_type.rb b/server/app/models/variant_type.rb index fe029b4ef..925eff052 100644 --- a/server/app/models/variant_type.rb +++ b/server/app/models/variant_type.rb @@ -3,6 +3,7 @@ class VariantType < ActiveRecord::Base has_and_belongs_to_many :variants has_and_belongs_to_many :pipeline_types + enum :regulatory_fusion_type, Constants::REGULATORY_FUSION_ENUM_TYPES def url if self.soid != "N/A" diff --git a/server/app/models/variants/fusion_variant.rb b/server/app/models/variants/fusion_variant.rb index 37c919794..2216260f3 100644 --- a/server/app/models/variants/fusion_variant.rb +++ b/server/app/models/variants/fusion_variant.rb @@ -65,7 +65,16 @@ def required_fields def generate_vicc_name if name == Constants::REPRESENTATIVE_FUSION_VARIANT_NAME - "#{construct_five_prime_name(name_type: :representative)}::#{construct_three_prime_name(name_type: :representative)}" + # for vicc names, the regulatory component is always first, regardless of 5' vs 3' + if fusion.regulatory_fusion_type.present? + if fusion.five_prime_partner_status == "regulatory" + "#{construct_five_prime_name(name_type: :representative)}::#{construct_three_prime_name(name_type: :representative)}" + else + "#{construct_three_prime_name(name_type: :representative)}::#{construct_five_prime_name(name_type: :representative)}" + end + else + "#{construct_five_prime_name(name_type: :representative)}::#{construct_three_prime_name(name_type: :representative)}" + end else "#{construct_five_prime_name(name_type: :vicc)}::#{construct_three_prime_name(name_type: :vicc)}" end @@ -102,6 +111,7 @@ def construct_five_prime_name(name_type:) partner_status: fusion.five_prime_partner_status, gene: fusion.five_prime_gene, exon_coords: five_prime_end_exon_coordinates, + regulatory_type: fusion.regulatory_fusion_type ) end @@ -111,10 +121,11 @@ def construct_three_prime_name(name_type:) partner_status: fusion.three_prime_partner_status, gene: fusion.three_prime_gene, exon_coords: three_prime_start_exon_coordinates, + regulatory_type: fusion.regulatory_fusion_type ) end - def construct_partner_name(name_type:, partner_status:, gene:, exon_coords:) + def construct_partner_name(name_type:, partner_status:, gene:, exon_coords:, regulatory_type:) if partner_status == "known" case name_type when :representative @@ -128,6 +139,8 @@ def construct_partner_name(name_type:, partner_status:, gene:, exon_coords:) "?" elsif partner_status == "multiple" "v" + elsif partner_status == "regulatory" + "#{Features::Fusion.format_regulatory_fusion_type(regulatory_type)}@#{gene.name}(entrez:#{gene.entrez_id})" end end diff --git a/server/app/validators/fusion_feature_validator.rb b/server/app/validators/fusion_feature_validator.rb new file mode 100644 index 000000000..e1b09b8d1 --- /dev/null +++ b/server/app/validators/fusion_feature_validator.rb @@ -0,0 +1,36 @@ +class FusionFeatureValidator < ActiveModel::Validator + def validate(record) + partner_status_valid_for_gene_ids(record) + at_least_one_gene_id(record) + regulatory_status_set_correctly(record) + end + + private + def partner_status_valid_for_gene_ids(record) + if !record.in_revision_validation_context + [ record.five_prime_gene, record.three_prime_gene ].zip([ record.five_prime_partner_status, record.three_prime_partner_status ], [ :five_prime_gene, :three_prime_gene ]).each do |gene, status, fk| + if gene.nil? && (status == "known" || status == "regulatory") + record.errors.add(fk, "Partner status cannot be 'known' or 'regulatory' if the gene isn't set") + elsif !gene.nil? && (status != "known" && status != "regulatory") + record.errors.add(fk, "Partner status has to be 'known' or 'regulatory' if gene is set") + end + end + end + end + + def at_least_one_gene_id(record) + if !record.in_revision_validation_context && record.five_prime_gene_id.nil? && record.three_prime_gene_id.nil? + record.errors.add(:base, "One or both of the genes need to be set") + end + end + + def regulatory_status_set_correctly(record) + if record.three_prime_partner_status == "regulatory" && record.five_prime_partner_status == "regulatory" + record.errors.add(:base, "Only one fusion partner may be marked as regulatory") + elsif record.three_prime_partner_status == "regulatory" || record.five_prime_partner_status == "regulatory" + if record.regulatory_fusion_type.blank? + record.errors.add(:regulatory_fusion_type, "You must select a regulatory fusion type if one of the fusion partners is marked as regulatory") + end + end + end +end diff --git a/server/db/migrate/20250102170055_add_regulatory_fusion_types_enum.rb b/server/db/migrate/20250102170055_add_regulatory_fusion_types_enum.rb new file mode 100644 index 000000000..36fd399ce --- /dev/null +++ b/server/db/migrate/20250102170055_add_regulatory_fusion_types_enum.rb @@ -0,0 +1,26 @@ +class AddRegulatoryFusionTypesEnum < ActiveRecord::Migration[7.1] + def up + create_enum :regulatory_fusion_types, Constants::REGULATORY_FUSION_TYPES.map(&:first) + add_enum_value :fusion_partner_status, "regulatory" + + add_column :variant_types, :regulatory_fusion_type, :enum, enum_type: :regulatory_fusion_types, null: true + add_column :fusions, :regulatory_fusion_type, :enum, enum_type: :regulatory_fusion_types, null: true + add_index :variant_types, :regulatory_fusion_type + + Constants::REGULATORY_FUSION_TYPES.each do |(type, soid)| + if soid.present? + vt = VariantType.find_by!(soid: soid) + vt.regulatory_fusion_type = type + vt.save! + end + end + end + + def down + remove_column :variant_types, :regulatory_fusion_type + remove_column :fusions, :regulatory_fusion_type + execute <<-SQL + DROP TYPE regulatory_fusion_types; + SQL + end +end diff --git a/server/db/schema.rb b/server/db/schema.rb index f95fba0c7..a1d9b22c2 100644 --- a/server/db/schema.rb +++ b/server/db/schema.rb @@ -10,15 +10,17 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_12_20_161652) do +ActiveRecord::Schema[8.0].define(version: 2025_03_07_152119) do # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" + enable_extension "pg_catalog.plpgsql" # Custom types defined in this database. # Note that some types may not work with other database engines. Be careful if changing database. + create_enum "endorsement_status", ["active", "revoked", "requires_review"] create_enum "exon_coordinate_record_state", ["stub", "exons_provided", "fully_curated"] create_enum "exon_offset_direction", ["positive", "negative"] - create_enum "fusion_partner_status", ["known", "unknown", "multiple"] + create_enum "fusion_partner_status", ["known", "unknown", "multiple", "regulatory"] + create_enum "regulatory_fusion_types", ["attenuator", "CAAT_signal", "DNase_I_hypersensitive_site", "enhancer", "enhancer_blocking_element", "GC_signal", "imprinting_control_region", "insulator", "locus_control_region", "matrix_attachment_region", "minus_35_signal", "minus_10_signal", "polyA_signal_sequence", "promoter", "recoding_stimulatory_region", "recombination_enhancer", "replication_regulatory_region", "response_element", "ribosome_binding_site", "riboswitch", "silencer", "TATA_box", "terminator", "transcriptional_cis_regulatory_region", "uORF", "other"] create_enum "variant_coordinate_record_state", ["stub", "fully_curated"] create_table "acmg_codes", id: :serial, force: :cascade do |t| @@ -97,11 +99,12 @@ t.index ["token", "search_type"], name: "index_advanced_searches_on_token_and_search_type" end - create_table "affiliations", id: false, force: :cascade do |t| + create_table "affiliations", force: :cascade do |t| t.bigint "user_id" t.bigint "organization_id" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.boolean "can_endorse", default: false, null: false t.index ["organization_id"], name: "index_affiliations_on_organization_id" t.index ["user_id"], name: "index_affiliations_on_user_id" end @@ -382,6 +385,19 @@ t.index ["user_id"], name: "index_domain_expert_tags_on_user_id" end + create_table "endorsements", force: :cascade do |t| + t.bigint "organization_id", null: false + t.bigint "user_id", null: false + t.bigint "assertion_id", null: false + t.enum "status", default: "active", null: false, enum_type: "endorsement_status" + t.datetime "last_reviewed", precision: nil, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["assertion_id"], name: "index_endorsements_on_assertion_id" + t.index ["organization_id"], name: "index_endorsements_on_organization_id" + t.index ["user_id"], name: "index_endorsements_on_user_id" + end + create_table "entity_mentions", force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -550,6 +566,7 @@ t.enum "three_prime_partner_status", default: "unknown", null: false, enum_type: "fusion_partner_status" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.enum "regulatory_fusion_type", enum_type: "regulatory_fusion_types" t.index ["five_prime_gene_id"], name: "index_fusions_on_five_prime_gene_id" t.index ["three_prime_gene_id"], name: "index_fusions_on_three_prime_gene_id" end @@ -680,6 +697,8 @@ t.datetime "created_at" t.datetime "updated_at" t.datetime "most_recent_activity_timestamp", precision: nil + t.boolean "can_endorse", default: false, null: false + t.boolean "is_approved_vcep", default: false, null: false t.index ["most_recent_activity_timestamp"], name: "index_organizations_on_most_recent_activity_timestamp" end @@ -977,8 +996,10 @@ t.integer "parent_id" t.integer "lft" t.integer "rgt" + t.enum "regulatory_fusion_type", enum_type: "regulatory_fusion_types" t.index ["display_name"], name: "index_variant_types_on_display_name" t.index ["name"], name: "index_variant_types_on_name" + t.index ["regulatory_fusion_type"], name: "index_variant_types_on_regulatory_fusion_type" t.index ["soid"], name: "index_variant_types_on_soid" end @@ -1017,7 +1038,7 @@ t.boolean "deprecated", default: false, null: false t.integer "deprecation_reason" t.integer "deprecation_comment_id" - t.text "open_cravat_url_parameters" + t.text "open_cravat_url" t.bigint "feature_id" t.string "type", null: false t.string "ncit_id" diff --git a/server/test/fixtures/feature_fusions.yml b/server/test/fixtures/feature_fusions.yml new file mode 100644 index 000000000..b4a5cce50 --- /dev/null +++ b/server/test/fixtures/feature_fusions.yml @@ -0,0 +1,27 @@ +braf_vhl_fusion: + five_prime_gene: braf + three_prime_gene: vhl + five_prime_partner_status: known + three_prime_partner_status: known + regulatory_fusion_type: enhancer + +braf_fusion: + five_prime_gene: braf + three_prime_gene: null + five_prime_partner_status: known + three_prime_partner_status: unknown + regulatory_fusion_type: null + +vhl_fusion: + five_prime_gene: null + three_prime_gene: vhl + five_prime_partner_status: unknown + three_prime_partner_status: known + regulatory_fusion_type: null + +regulatory_fusion: + five_prime_gene: braf + three_prime_gene: vhl + five_prime_partner_status: regulatory + three_prime_partner_status: known + regulatory_fusion_type: enhancer diff --git a/server/test/fixtures/features.yml b/server/test/fixtures/features.yml index 86ffe0e40..816423435 100644 --- a/server/test/fixtures/features.yml +++ b/server/test/fixtures/features.yml @@ -13,3 +13,8 @@ msi: full_name: Microsatellite Instability description: The description for the MSI factor feature_instance: msi (Features::Factor) + +braf_fusion: + name: BRAF::? + description: BRAF and unknown partner + feature_instance: braf_fusion (Features::Fusion) diff --git a/server/test/models/fusion_feature_test.rb b/server/test/models/fusion_feature_test.rb new file mode 100644 index 000000000..d0ae823c2 --- /dev/null +++ b/server/test/models/fusion_feature_test.rb @@ -0,0 +1,47 @@ +require "test_helper" + +class FusionFeatureTest < ActiveSupport::TestCase + setup do + feature = features(:braf_fusion) + @braf_vhl_fusion = feature_fusions(:braf_vhl_fusion) + @braf_vhl_fusion.feature = feature + @braf_fusion = feature_fusions(:braf_fusion) + @braf_fusion.feature = feature + @vhl_fusion = feature_fusions(:vhl_fusion) + @vhl_fusion.feature = feature + @regulatory_fusion = feature_fusions(:regulatory_fusion) + @regulatory_fusion.feature = feature + end + + test "valid fusion with both genes set and known partner status" do + assert @braf_vhl_fusion.valid? + end + + test "invalid fusion with one gene set and known partner status" do + @braf_fusion.three_prime_partner_status = "known" + assert_not @braf_fusion.valid? + assert_includes @braf_fusion.errors[:three_prime_gene], "Partner status cannot be 'known' or 'regulatory' if the gene isn't set" + end + + test "invalid fusion with both genes unset" do + @braf_fusion.five_prime_gene = nil + assert_not @braf_fusion.valid? + assert_includes @braf_fusion.errors[:base], "One or both of the genes need to be set" + end + + test "invalid fusion with both partners marked as regulatory" do + @regulatory_fusion.three_prime_partner_status = "regulatory" + assert_not @regulatory_fusion.valid? + assert_includes @regulatory_fusion.errors[:base], "Only one fusion partner may be marked as regulatory" + end + + test "invalid fusion with regulatory partner but no regulatory fusion type" do + @regulatory_fusion.regulatory_fusion_type = nil + assert_not @regulatory_fusion.valid? + assert_includes @regulatory_fusion.errors[:regulatory_fusion_type], "You must select a regulatory fusion type if one of the fusion partners is marked as regulatory" + end + + test "valid fusion with regulatory partner and regulatory fusion type" do + assert @regulatory_fusion.valid? + end +end diff --git a/server/test/test_helper.rb b/server/test/test_helper.rb index b509ff1ff..9c733692d 100644 --- a/server/test/test_helper.rb +++ b/server/test/test_helper.rb @@ -8,6 +8,7 @@ class ActiveSupport::TestCase set_fixture_class feature_genes: Features::Gene set_fixture_class feature_factors: Features::Factor + set_fixture_class feature_fusions: Features::Fusion # Add more helper methods to be used by all tests here... end