Skip to content

Add default Derivation Group associations for Plans at the Model level#1934

Open
pranav-super wants to merge 10 commits into
developfrom
model_derivation_group-update-v2
Open

Add default Derivation Group associations for Plans at the Model level#1934
pranav-super wants to merge 10 commits into
developfrom
model_derivation_group-update-v2

Conversation

@pranav-super
Copy link
Copy Markdown
Contributor

@pranav-super pranav-super commented May 20, 2026

___REQUIRES_AERIE_PR___="1831"

This is the UI update based on the tables introduced in this Plandev PR.

It introduces a derivation group tab along with the other model association tabs on the models page.
This feature is modeled off of the other associations, but since derivation group associations are much simpler than goal/condition/constraint specifications, is a lot lighter-weight and doesn't precisely match functionality.

There are a few minor unresolved TODOs, namely:

  • deleting an external source gets blocked if there are any outstanding plan-derivation-group links.
    We don't believe this is a necessary check for model-derivation-group links (i.e. if a derivation group is linked to a model, and a planner wants to delete a source from said group, they shouldn't be blocked from doing so). We believe this is an unnecessary check because with a model, there are no activities scheduled off of any sources or external events visible to planners at a model level, so there is no anticipated loss when a source is deleted.
  • a few formatting issues with the derivation group table on the models page. The table is modeled off of the ManagePlanDerivationGroupsModal, and a lot of the formatting and divs were retained as attempts to format otherwise were unsuccessful.

<RadioButton id="constraint"><div class="association-button">Constraints</div></RadioButton>
<RadioButton id="goal"><div class="association-button">Goals</div></RadioButton>
<RadioButton id="condition"><div class="association-button">Conditions</div></RadioButton>
<RadioButton id="derivation_group"><div class="association-button">Derivation Groups</div></RadioButton>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs styling to make it wider to prevent this overflow. Image

export let user: User | null;
export let selectedDerivationGroups: string[];

$: console.log('dgmodelspec -> selectedDerivationGroups', selectedDerivationGroups);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove log

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Styling seems off for the table - it is getting cut off and I can't select any of the rows via checkbox.

import SectionTitle from '../ui/SectionTitle.svelte';

export let user: User | null;
export let selectedDerivationGroups: string[];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to select a derivation group by clicking on a row in the DG table

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - i have to click on the expand button.. any reason not to just use left click to trigger the detail view like we do in the other tables in the other tabs?

Copy link
Copy Markdown
Contributor Author

@pranav-super pranav-super May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason in particular. This was modeled after how it works in the plan view (see ManagePlanDerivationGroupsModal.svelte), where there is an expansion button, but that can be updated!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense - I'd vote to align it with the other tabs in there just for consistency

featurePermissions.derivationGroupModelLink.canCreate(user) &&
featurePermissions.derivationGroupModelLink.canDelete(user);

// $: selectedDerivationGroups = $selectedModelDerivationGroupNames.reduce(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be removed?

Comment thread src/components/model/DGModelSpecification.svelte
return false;
}

function isModelOwner(user: User | null, model: AssetWithOwner<ModelWithOwner> | undefined): boolean {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason for this new permission when it just wraps isUserOwner with the same args?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It checks for model ownership specifically, and is used a few times! ModelWithOwner has a different set of properties than PlanWithOwner, so giving it a named function seemed appropriate. With that being said...this function is only used twice, whereas the isPlanOwner analogue is used 44 times. If it seems easier to unwrap this and directly use isUserOwner directly instead of this new function, that can be done, but having a named function seemed cleaner, and useful in case model ownership checking should be useful in future features.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I guess at the end of the day model ownership is just the same as user ownership? Given that it just wraps a function and doesn't make any modifications I feel like it is fine to keep it as just isUserOwner and modify it if/when user ownership =/ model ownership?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that's true. But then is the isPlanOwner function necessary? I added this function as an analogue to that one.

Comment thread src/routes/models/[id]/+page.svelte Outdated
version: string;
} | null = null;

// to get derivation groups here, just need the store, and the table toggle button sends a request to update it.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment needed?

Comment thread src/utilities/effects.ts Outdated
async deleteExternalSource(
externalSources: ExternalSourceSlim[] | null,
planDerivationGroupLinks: PlanDerivationGroup[],
planDerivationGroupLinks: PlanDerivationGroup[], // TODO: add modelDerivationGroup checking? Don't believe it is necessary.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still a TODO?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was more of a question to the UI team. When deleting a source, we are stopped if that source is part of a derivation group that is associated with a plan to prevent unexpected effects. I don't think it makes sense to do so when a derivation group is only associated with a model and a source is to be deleted, but I wanted to check if you thought that might be worth adding. I describe this in better detail in the original PR comment/description (the first bullet point)!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it - yeah seems unnecessary to check all of that since if i understand this all correctly, we're just setting defaults for newly created plans and not existing plans for DGs? That's what happens for constraints/goals/conditions at least.

Copy link
Copy Markdown
Contributor Author

@pranav-super pranav-super May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'd agree. I'll go ahead and remove the TODO!

Comment thread src/utilities/effects.ts Outdated
}
if (model) {
derivationGroupModelLinkErrorStore.set(null);
if (plan !== null) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should pass plan in here instead of relying on the store. Not sure if any effects rely on this store (something must..) but this will always evaluate to true since the store exists. If you want to get the value of the store you need to use the svelte store get(store) to get the current value. But the pattern we should follow for effects is to pass in whatever args are needed such as plan.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further review, this check should not be there! Removing...

Comment thread src/utilities/permissions.ts Outdated
},
CREATE_MODEL_DERIVATION_GROUP: (user: User | null, model: ModelWithOwner): boolean => {
return (
isUserAdmin(user) || (getPermission([Queries.INSERT_PLAN_DERIVATION_GROUP], user) && isModelOwner(user, model))
Copy link
Copy Markdown
Contributor

@AaronPlave AaronPlave May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be queries.INSERT_MODEL_DERIVATION_GROUP?

Comment thread src/utilities/permissions.ts Outdated
},
DELETE_MODEL_DERIVATION_GROUP: (user: User | null, model: ModelWithOwner): boolean => {
return (
isUserAdmin(user) || (getPermission([Queries.DELETE_PLAN_DERIVATION_GROUP], user) && isModelOwner(user, model))
Copy link
Copy Markdown
Contributor

@AaronPlave AaronPlave May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be queries.DELETE_MODEL_DERIVATION_GROUP?

Comment thread src/routes/models/[id]/+page.svelte Outdated
{},
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra line

Comment thread src/utilities/gql.ts Outdated
`,

DELETE_MODEL_DERIVATION_GROUP: `#graphql
mutation DeletePlanExternalSource($where: model_derivation_group_bool_exp!) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be DeleteModelDerivationGroup?

Comment thread src/utilities/gql.ts Outdated

CREATE_MODEL_DERIVATION_GROUP: `#graphql
mutation CreateModelDerivationGroup($source: model_derivation_group_insert_input!) {
planExternalSourceLink: ${Queries.INSERT_MODEL_DERIVATION_GROUP}(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should planExternalSourceLink be modelDerivationGroupLink?

Copy link
Copy Markdown
Contributor

@AaronPlave AaronPlave left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add an e2e test for this workflow? Left some other comments but nothing too big to fix.

Comment thread src/utilities/effects.ts Outdated
): Promise<void> {
try {
if ((model && !queryPermissions.DELETE_MODEL_DERIVATION_GROUP(user, model)) || !model) {
throwPermissionError('delete a derivation group from the plan');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "...from the model"

Comment thread src/utilities/effects.ts Outdated
): Promise<void> {
try {
if ((model && !queryPermissions.CREATE_MODEL_DERIVATION_GROUP(user, model)) || !model) {
throwPermissionError('add a derivation group to the plan');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "...to the model"

Comment thread src/utilities/effects.ts Outdated
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}).`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "...to model..."?

const derivationGroupsToDissociate = initialModelDerivationGroupAssociations.filter(
derivationGroup => !selectedDerivationGroups.includes(derivationGroup),
);
derivationGroupsToDissociate.forEach(derivationGroup =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use a for loop here and below when iterating over these promises and we should await the effect promises

Comment thread src/utilities/effects.ts
if (model) {
derivationGroupModelLinkErrorStore.set(null);
if (plan !== null) {
const data = await reqHasura<{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this return

modelDerivationGroupLink: {
    returning: { derivation_group_name: string; model_id: number; }[];
  };

Comment thread src/utilities/effects.ts Outdated
},
user,
);
const { planExternalSourceLink: sourceAssociation } = data;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be modelDerivationGroupLink

Comment thread src/utilities/effects.ts Outdated
if (plan !== null) {
const data = await reqHasura<ModelDerivationGroup>(
gql.CREATE_MODEL_DERIVATION_GROUP,
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be:

{
  modelDerivationGroupLink: { derivation_group_name: string } | null;
}

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants