From cd98bc878aa17d3d9f05b5e3388bd4fc8fe84e97 Mon Sep 17 00:00:00 2001 From: psubram3 Date: Wed, 20 May 2026 15:57:25 -0700 Subject: [PATCH 01/10] introduce ModelDerivationGroup type --- src/types/external-source.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/types/external-source.ts b/src/types/external-source.ts index fdb770f0f8..9646479ba1 100644 --- a/src/types/external-source.ts +++ b/src/types/external-source.ts @@ -53,6 +53,11 @@ export type PlanDerivationGroup = { plan_id: number; }; +export type ModelDerivationGroup = { + derivation_group_name: string; + model_id: number; +}; + export type ExternalSourceEventTypeSchema = { event_types?: SchemaObject; source_types?: SchemaObject; From 905a46d35fd3fa9ef62500d28313c3d3389e189a Mon Sep 17 00:00:00 2001 From: psubram3 Date: Wed, 20 May 2026 15:58:06 -0700 Subject: [PATCH 02/10] add ModelDerivationGroup type to model type --- src/types/model.ts | 2 ++ src/utilities/gql.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/types/model.ts b/src/types/model.ts index 6c36cd2eca..3608a0c6e3 100644 --- a/src/types/model.ts +++ b/src/types/model.ts @@ -1,5 +1,6 @@ import type { UserId } from './app'; import type { ConstraintModelSpecification } from './constraint'; +import type { ModelDerivationGroup } from './external-source'; import type { ParametersMap } from './parameter'; import type { SchedulingConditionModelSpecification, SchedulingGoalModelSpecification } from './scheduling'; import type { View, ViewSlim } from './view'; @@ -37,6 +38,7 @@ export type ModelSchema = { constraint_specification: ConstraintModelSpecification[]; created_at: string; default_view_id: number | null; + derivation_group_specification: ModelDerivationGroup[]; description?: string; id: number; jar_id: number; diff --git a/src/utilities/gql.ts b/src/utilities/gql.ts index ee7eb51c61..c8ede2764b 100644 --- a/src/utilities/gql.ts +++ b/src/utilities/gql.ts @@ -2637,6 +2637,10 @@ const gql = { SUB_MODEL: `#graphql subscription SubModel($id: Int!) { model: ${Queries.MISSION_MODEL}(id: $id) { + derivation_group_specification { + model_id + derivation_group_name + } constraint_specification(order_by: { order: asc }) { arguments constraint_id From ecc1ada398fe4e7bd9c8f660c63416e8ca044ff9 Mon Sep 17 00:00:00 2001 From: psubram3 Date: Wed, 20 May 2026 16:06:41 -0700 Subject: [PATCH 03/10] add effects for ModelDerivationGroup --- src/enums/gql.ts | 2 + src/utilities/effects.ts | 89 +++++++++++++++++++++++++++++++++++++++- src/utilities/gql.ts | 23 +++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/enums/gql.ts b/src/enums/gql.ts index 1961ab4770..59bd3d7c92 100644 --- a/src/enums/gql.ts +++ b/src/enums/gql.ts @@ -40,6 +40,7 @@ export enum Queries { DELETE_CONSTRAINT_DEFINITION_TAGS = 'delete_constraint_definition_tags', DELETE_CONSTRAINT_METADATA = 'delete_constraint_metadata_by_pk', DELETE_CONSTRAINT_MODEL_SPECIFICATIONS = 'delete_constraint_model_specification', + DELETE_MODEL_DERIVATION_GROUP = 'delete_model_derivation_group', DELETE_CONSTRAINT_SPECIFICATIONS = 'delete_constraint_specification', DELETE_CONSTRAINT_TAGS = 'delete_constraint_tags', DELETE_DERIVATION_GROUP = 'delete_derivation_group', @@ -142,6 +143,7 @@ export enum Queries { INSERT_EXTERNAL_SOURCE = 'insert_external_source_one', INSERT_EXTERNAL_SOURCE_TYPE = 'insert_external_source_type_one', INSERT_MISSION_MODEL = 'insert_mission_model_one', + INSERT_MODEL_DERIVATION_GROUP = 'insert_model_derivation_group_one', INSERT_PARAMETER_DICTIONARY = 'insert_parameter_dictionary_one', INSERT_PARCEL = 'insert_parcel_one', INSERT_PARCEL_TO_PARAMETER_DICTIONARY = 'insert_parcel_to_parameter_dictionary', diff --git a/src/utilities/effects.ts b/src/utilities/effects.ts index 879bb63059..723b1a4ee3 100644 --- a/src/utilities/effects.ts +++ b/src/utilities/effects.ts @@ -44,6 +44,7 @@ import { createExternalSourceError as createExternalSourceErrorStore, createExternalSourceEventTypeError as createExternalSourceEventTypeErrorStore, creatingExternalSource as creatingExternalSourceStore, + derivationGroupModelLinkError as derivationGroupModelLinkErrorStore, derivationGroupPlanLinkError as derivationGroupPlanLinkErrorStore, } from '../stores/external-source'; import { @@ -135,6 +136,7 @@ import type { DerivationGroupInsertInput, ExternalSourcePkey, ExternalSourceSlim, + ModelDerivationGroup, PlanDerivationGroup, } from '../types/external-source'; import type { Model, ModelInsertInput, ModelLog, ModelSchema, ModelSetInput, ModelSlim } from '../types/model'; @@ -2991,6 +2993,52 @@ const effects = { } }, + async deleteDerivationGroupForModel( + derivation_group_name: string, + model: Model | null, + user: User | null, + ): Promise { + try { + if ((model && !queryPermissions.DELETE_MODEL_DERIVATION_GROUP(user, model)) || !model) { + throwPermissionError('delete a derivation group from the plan'); + } + if (model) { + derivationGroupModelLinkErrorStore.set(null); + if (plan !== null) { + const data = await reqHasura<{ + returning: { + derivation_group_name: string; + model: number; + }[]; + }>( + gql.DELETE_MODEL_DERIVATION_GROUP, + { + where: { + _and: { + derivation_group_name: { _eq: derivation_group_name }, + model_id: { _eq: model.id }, + }, + }, + }, + user, + ); + const sourceDissociation = data.modelDerivationGroupLink?.returning[0]; + // If the return was null, do nothing - only act on success or non-null + if (sourceDissociation) { + logMessage(`Deleted derivation group "${derivation_group_name}" for Model ID=${model.id}.`); + showSuccessToast('Derivation Group Disassociated Successfully'); + } + } else { + throw Error('Plan is not defined.'); + } + } + } catch (e) { + catchError('Derivation Group De-linking Failed', e as Error); + showFailureToast('Derivation Group De-linking Failed'); + derivationGroupModelLinkErrorStore.set((e as Error).message); + } + }, + async deleteDerivationGroupForPlan( derivation_group_name: string, plan: Plan | null, @@ -3232,7 +3280,7 @@ const effects = { async deleteExternalSource( externalSources: ExternalSourceSlim[] | null, - planDerivationGroupLinks: PlanDerivationGroup[], + planDerivationGroupLinks: PlanDerivationGroup[], // TODO: add modelDerivationGroup checking? Don't believe it is necessary. user: User | null, ): Promise { try { @@ -6260,6 +6308,45 @@ const effects = { } }, + async insertDerivationGroupForModel( + derivationGroupName: string, + model: Model | null, + user: User | null, + ): Promise { + try { + if ((model && !queryPermissions.CREATE_MODEL_DERIVATION_GROUP(user, model)) || !model) { + throwPermissionError('add a derivation group to the plan'); + } + if (model) { + derivationGroupModelLinkErrorStore.set(null); + if (plan !== null) { + const data = await reqHasura( + gql.CREATE_MODEL_DERIVATION_GROUP, + { + source: { + derivation_group_name: derivationGroupName, + model_id: model.id, + }, + }, + user, + ); + const { planExternalSourceLink: sourceAssociation } = data; + // If the return was null, do nothing - only act on success or non-null + if (sourceAssociation !== null) { + logMessage(`Linked derivation group "${derivationGroupName}" to plan "${model.name}" (ID=${model.id}).`); + showSuccessToast('Derivation Group Linked Successfully'); + } + } else { + throw Error('Plan is not defined.'); + } + } + } catch (e) { + catchError('Derivation Group Linking Failed', e as Error); + showFailureToast('Derivation Group Linking Failed'); + derivationGroupModelLinkErrorStore.set((e as Error).message); + } + }, + async insertDerivationGroupForPlan(derivationGroupName: string, plan: Plan | null, user: User | null): Promise { try { if ((plan && !queryPermissions.CREATE_PLAN_DERIVATION_GROUP(user, plan)) || !plan) { diff --git a/src/utilities/gql.ts b/src/utilities/gql.ts index c8ede2764b..530e92f120 100644 --- a/src/utilities/gql.ts +++ b/src/utilities/gql.ts @@ -337,6 +337,19 @@ const gql = { } `, + CREATE_MODEL_DERIVATION_GROUP: `#graphql + mutation CreateModelDerivationGroup($source: model_derivation_group_insert_input!) { + planExternalSourceLink: ${Queries.INSERT_MODEL_DERIVATION_GROUP}( + object: $source, + on_conflict: { + constraint: model_derivation_group_pkey + } + ) { + derivation_group_name + } + } + `, + CREATE_PARAMETER_DICTIONARY: `#graphql mutation CreateParameterDictionary($parameterDictionary: parameter_dictionary_insert_input!) { createParameterDictionary: ${Queries.INSERT_PARAMETER_DICTIONARY}(object: $parameterDictionary) { @@ -884,6 +897,16 @@ const gql = { } `, + DELETE_MODEL_DERIVATION_GROUP: `#graphql + mutation DeletePlanExternalSource($where: model_derivation_group_bool_exp!) { + modelDerivationGroupLink: ${Queries.DELETE_MODEL_DERIVATION_GROUP}(where: $where) { + returning { + derivation_group_name + } + } + } + `, + DELETE_PARAMETER_DICTIONARY: `#graphql mutation DeleteParameterDictionary($id: Int!) { deleteParameterDictionary: ${Queries.DELETE_PARAMETER_DICTIONARY}(id: $id) { From 748d024fa6fd2ba3d03c19557b38172e72668bd9 Mon Sep 17 00:00:00 2001 From: psubram3 Date: Wed, 20 May 2026 16:07:10 -0700 Subject: [PATCH 04/10] add minor stores to reflect derivation group state --- src/stores/external-source.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stores/external-source.ts b/src/stores/external-source.ts index 112054943c..25f8fb30bd 100644 --- a/src/stores/external-source.ts +++ b/src/stores/external-source.ts @@ -18,6 +18,7 @@ export const createExternalSourceError: Writable = writable(null) export const createExternalSourceEventTypeError: Writable = writable(null); export const createDerivationGroupError: Writable = writable(null); export const derivationGroupPlanLinkError: Writable = writable(null); +export const derivationGroupModelLinkError: Writable = writable(null); export const derivationGroupVisibilityMap: Writable> = writable({}); /* Subscriptions. */ @@ -43,6 +44,9 @@ export const sourcesUsingExternalEventTypes = gqlSubscribable = derived( [externalSourceTypes, externalSources, derivationGroups], From 808f5a9dd934c2dd0a145f5b3f85a604a9acf1ba Mon Sep 17 00:00:00 2001 From: psubram3 Date: Wed, 20 May 2026 16:07:25 -0700 Subject: [PATCH 05/10] add derivation_group to Association type --- src/types/metadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/metadata.ts b/src/types/metadata.ts index 87699ea99e..0828088cbc 100644 --- a/src/types/metadata.ts +++ b/src/types/metadata.ts @@ -39,7 +39,7 @@ export type BaseMetadataSlim = Omit< 'models_using' | 'plans_using' | 'versions' >; -export type Association = 'constraint' | 'condition' | 'goal'; +export type Association = 'constraint' | 'condition' | 'goal' | 'derivation_group'; export type AssociationSpecificationEntry = { arguments?: ArgumentsMap; From 5fbd55dcf5035d9b910cf1943f279f6416a23b2a Mon Sep 17 00:00:00 2001 From: psubram3 Date: Wed, 20 May 2026 16:07:35 -0700 Subject: [PATCH 06/10] create DGModelSpecification.svelte --- .../model/DGModelSpecification.svelte | 340 ++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 src/components/model/DGModelSpecification.svelte diff --git a/src/components/model/DGModelSpecification.svelte b/src/components/model/DGModelSpecification.svelte new file mode 100644 index 0000000000..456bd1a0e8 --- /dev/null +++ b/src/components/model/DGModelSpecification.svelte @@ -0,0 +1,340 @@ + + + + + + + From 8f247cfda3cc832b924d7340b81b70fc36f4461d Mon Sep 17 00:00:00 2001 From: psubram3 Date: Wed, 20 May 2026 16:08:06 -0700 Subject: [PATCH 07/10] incorporate DGModelSpecification.svelte --- src/components/model/ModelAssociations.svelte | 233 ++++++++++-------- src/routes/models/[id]/+page.svelte | 56 ++++- 2 files changed, 176 insertions(+), 113 deletions(-) diff --git a/src/components/model/ModelAssociations.svelte b/src/components/model/ModelAssociations.svelte index 3202e9f27c..b409626f61 100644 --- a/src/components/model/ModelAssociations.svelte +++ b/src/components/model/ModelAssociations.svelte @@ -3,6 +3,7 @@