From d7b087fb76e0b8e3a66d31cdd79f75fba57304a3 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 13 Oct 2022 14:01:15 -0700 Subject: [PATCH] refactor: activity queries (#184) refactor activity queries * Consolidate activity subscription transformation into single function * Rename activity start_time to start_time_doy for clarity * Remove obsolete transform functions * Order activities and simulation datasets by start_offset ascending * Add uniqueId field to Activity type --- .../ActivityDecomposition.svelte.test.ts | 21 ++- .../activity/ActivityFormPanel.svelte | 18 +-- .../activity/ActivityTablePanel.svelte | 4 +- src/components/timeline/LayerActivity.svelte | 8 +- src/stores/activities.ts | 110 ++++++++++++---- src/types/activity.d.ts | 6 +- src/utilities/activities.ts | 120 +----------------- src/utilities/effects.ts | 22 ++-- src/utilities/gql.ts | 11 +- 9 files changed, 146 insertions(+), 174 deletions(-) diff --git a/src/components/activity/ActivityDecomposition.svelte.test.ts b/src/components/activity/ActivityDecomposition.svelte.test.ts index 28e788009c..10f633b18a 100644 --- a/src/components/activity/ActivityDecomposition.svelte.test.ts +++ b/src/components/activity/ActivityDecomposition.svelte.test.ts @@ -22,10 +22,11 @@ const activities: Activity[] = [ simulated_activity_id: 29, simulation_dataset_id: 6, source_scheduling_goal_id: null, - start_time: '2022-062T10:00:00.000', + start_time_doy: '2022-062T10:00:00.000', tags: [], type: 'parent', unfinished: false, + uniqueId: 'directive_12', }, { arguments: {}, @@ -46,10 +47,11 @@ const activities: Activity[] = [ simulated_activity_id: 68, simulation_dataset_id: 21, source_scheduling_goal_id: null, - start_time: '2020-129T03:38:49.856', + start_time_doy: '2020-129T03:38:49.856', tags: [], type: 'grandchild1', unfinished: false, + uniqueId: 'simulated_68', }, { arguments: {}, @@ -70,10 +72,11 @@ const activities: Activity[] = [ simulated_activity_id: 69, simulation_dataset_id: 21, source_scheduling_goal_id: null, - start_time: '2020-072T03:38:49.856', + start_time_doy: '2020-072T03:38:49.856', tags: [], type: 'grandchild2', unfinished: false, + uniqueId: 'simulated_69', }, { arguments: {}, @@ -94,10 +97,11 @@ const activities: Activity[] = [ simulated_activity_id: 70, simulation_dataset_id: 21, source_scheduling_goal_id: null, - start_time: '2020-072T03:38:49.856', + start_time_doy: '2020-072T03:38:49.856', tags: [], type: 'child1', unfinished: false, + uniqueId: 'simulated_70', }, { arguments: {}, @@ -118,10 +122,11 @@ const activities: Activity[] = [ simulated_activity_id: 71, simulation_dataset_id: 21, source_scheduling_goal_id: null, - start_time: '2020-093T03:38:49.856', + start_time_doy: '2020-093T03:38:49.856', tags: [], type: 'grandchild3', unfinished: false, + uniqueId: 'simulated_71', }, { arguments: {}, @@ -142,10 +147,11 @@ const activities: Activity[] = [ simulated_activity_id: 72, simulation_dataset_id: 21, source_scheduling_goal_id: null, - start_time: '2020-129T03:38:49.856', + start_time_doy: '2020-129T03:38:49.856', tags: [], type: 'child2', unfinished: false, + uniqueId: 'simulated_72', }, { arguments: {}, @@ -166,10 +172,11 @@ const activities: Activity[] = [ simulated_activity_id: 73, simulation_dataset_id: 21, source_scheduling_goal_id: null, - start_time: '2020-150T03:38:49.856', + start_time_doy: '2020-150T03:38:49.856', tags: [], type: 'grandchild4', unfinished: false, + uniqueId: 'simulated_73', }, ]; diff --git a/src/components/activity/ActivityFormPanel.svelte b/src/components/activity/ActivityFormPanel.svelte index a950f55a4a..24ab133dd2 100644 --- a/src/components/activity/ActivityFormPanel.svelte +++ b/src/components/activity/ActivityFormPanel.svelte @@ -48,7 +48,7 @@ let seq_id: string | null = null; let simulated_activity_id: number | null = null; let sourceSchedulingGoalId: number | null = null; - let startTime: string | null = null; + let startTimeDoy: string | null = null; let tags: string[] = []; let type: string | null = null; let unfinished: boolean | null = null; @@ -63,7 +63,7 @@ let parametersWithErrorsCount: number = 0; let parameterErrorMap: Record = {}; let rootActivityHasChildren: boolean; - let startTimeField: FieldStore; + let startTimeDoyField: FieldStore; $: if ($selectedActivity) { activityName = $selectedActivity.name; @@ -77,7 +77,7 @@ root_activity = getActivityRootParent($activitiesMap, id); simulated_activity_id = $selectedActivity.simulated_activity_id; sourceSchedulingGoalId = $selectedActivity.source_scheduling_goal_id; - startTime = $selectedActivity.start_time; + startTimeDoy = $selectedActivity.start_time_doy; tags = $selectedActivity.tags; type = $selectedActivity.type; unfinished = $selectedActivity.unfinished; @@ -96,7 +96,7 @@ seq_id = null; simulated_activity_id = null; sourceSchedulingGoalId = null; - startTime = null; + startTimeDoy = null; type = null; unfinished = null; tags = []; @@ -115,10 +115,10 @@ $: activityType = $activityTypesMap[type] || null; $: rootActivityHasChildren = root_activity?.child_ids ? root_activity.child_ids.length > 0 : false; $: isChild = parent_id !== null; - $: startTimeField = field(startTime, [required, timestamp]); + $: startTimeDoyField = field(startTimeDoy, [required, timestamp]); $: activityNameField = field(activityName); $: if (duration) { - const startTimeISO = new Date(getUnixEpochTime(startTime)).toISOString(); + const startTimeISO = new Date(getUnixEpochTime(startTimeDoy)).toISOString(); endTime = `${getDoyTimeFromDuration(startTimeISO, duration)}`; } else { endTime = null; @@ -204,8 +204,8 @@ } function onUpdateStartTime() { - if ($startTimeField.valid && startTime !== $startTimeField.value) { - effects.updateActivityDirective(id, { start_time: $startTimeField.value }); + if ($startTimeDoyField.valid && startTimeDoy !== $startTimeDoyField.value) { + effects.updateActivityDirective(id, { start_time_doy: $startTimeDoyField.value }); } } @@ -375,7 +375,7 @@ ( { planId, simulationDatasetId }, {}, (data: SubActivitiesResponse) => { - const { activity_directives, simulations, start_time } = data; + const { start_time: plan_start_time } = get(plan); + const { activity_directives, simulations } = data; const [{ simulation_datasets } = { simulation_datasets: [] }] = simulations; const [{ simulated_activities } = { simulated_activities: [] }] = simulation_datasets; - const getParentId = getParentIdFn(activity_directives); - const getChildIds = getChildIdsFn(simulated_activities); - - const newActivities: Activity[] = [ - ...activity_directives.map((activityDirective: ActivityDirective) => - activityDirectiveToActivity(start_time, activityDirective, getChildIds), - ), - ...simulated_activities.map((activitySimulated: ActivitySimulated) => - activitySimulatedToActivity(start_time, activitySimulated, getChildIds, getParentId), - ), - ]; - - return keyBy(newActivities, 'id'); + // For each directive, map each simulated activity to it's directive activity id. + const simulatedIdToDirectiveId = activity_directives.reduce( + (map: Record, activityDirective: ActivityDirective) => { + const { simulated_activities } = activityDirective; + const activitySimulated = simulated_activities[0]; + + if (activitySimulated) { + map[activitySimulated.id] = activityDirective.id; + } + + return map; + }, + {}, + ); + + // Map each simulated activity id to it's list of simulated activity child ids. + const parentIdToChildIds = simulated_activities.reduce( + (map: Record, activitySimulated: ActivitySimulated) => { + if (map[activitySimulated.parent_id] === undefined) { + map[activitySimulated.parent_id] = [activitySimulated.id]; + } else { + map[activitySimulated.parent_id].push(activitySimulated.id); + } + return map; + }, + {}, + ); + + const activitiesMap: ActivitiesMap = {}; + + for (const activityDirective of activity_directives) { + const { simulated_activities } = activityDirective; + const activitySimulated = simulated_activities[0]; + + activitiesMap[activityDirective.id] = { + arguments: activityDirective.arguments, + attributes: activitySimulated?.attributes ?? null, + child_ids: parentIdToChildIds[activitySimulated?.id] ?? [], + created_at: activityDirective.created_at, + duration: activitySimulated?.duration ?? null, + id: activityDirective.id, + last_modified_at: activityDirective.last_modified_at, + metadata: activityDirective.metadata, + name: activityDirective.name, + parent_id: null, + simulated_activity_id: activitySimulated?.id ?? null, + simulation_dataset_id: activitySimulated?.simulation_dataset_id ?? null, + source_scheduling_goal_id: activityDirective.source_scheduling_goal_id, + start_time_doy: getDoyTimeFromDuration(plan_start_time, activityDirective.start_offset), + tags: activityDirective.tags, + type: activityDirective.type, + unfinished: activitySimulated?.duration === null, + uniqueId: `directive_${activityDirective.id}`, + }; + } + + for (const activitySimulated of simulated_activities) { + activitiesMap[activitySimulated.id] = { + arguments: {}, + attributes: activitySimulated.attributes, + child_ids: parentIdToChildIds[activitySimulated.id] ?? [], + created_at: '', + duration: activitySimulated.duration, + id: activitySimulated.id, + last_modified_at: '', + metadata: {}, + name: '', + parent_id: simulatedIdToDirectiveId[activitySimulated.parent_id] ?? activitySimulated.parent_id, + simulated_activity_id: activitySimulated.id, + simulation_dataset_id: activitySimulated.simulation_dataset_id, + source_scheduling_goal_id: null, + start_time_doy: getDoyTimeFromDuration(plan_start_time, activitySimulated.start_offset), + tags: [], + type: activitySimulated.activity_type_name, + unfinished: activitySimulated.duration === null, + uniqueId: `simulated_${activitySimulated.id}`, + }; + } + + return activitiesMap; }, ); diff --git a/src/types/activity.d.ts b/src/types/activity.d.ts index 9a6a695983..bcb3bb1f77 100644 --- a/src/types/activity.d.ts +++ b/src/types/activity.d.ts @@ -32,10 +32,11 @@ type Activity = { simulated_activity_id: ActivitySimulatedId | null; simulation_dataset_id: number | null; source_scheduling_goal_id: number; - start_time: string; + start_time_doy: string; tags: string[]; type: string; unfinished: boolean; + uniqueId: string; }; type ActivitiesMap = Record; @@ -44,9 +45,11 @@ type ActivityDirective = { arguments: ArgumentsMap; created_at: string; id: ActivityDirectiveId; + last_modified_arguments_at: string; last_modified_at: string; metadata: ActivityMetadata; name: string; + plan_id: number; simulated_activities: [ActivitySimulated]; source_scheduling_goal_id: number; start_offset: string; @@ -84,5 +87,4 @@ type ActivitySimulated = { type SubActivitiesResponse = { activity_directives: ActivityDirective[]; simulations: [{ simulation_datasets: [{ simulated_activities: ActivitySimulated[] }] }]; - start_time: string; }; diff --git a/src/utilities/activities.ts b/src/utilities/activities.ts index efbe93912b..71f2c34aed 100644 --- a/src/utilities/activities.ts +++ b/src/utilities/activities.ts @@ -1,6 +1,6 @@ import { omitBy } from 'lodash-es'; import { compare, isEmpty } from './generic'; -import { getDoyTimeFromDuration, getDurationInMs, getUnixEpochTime } from './time'; +import { getDurationInMs, getUnixEpochTime } from './time'; /** * Recursively converts an Activity to an ActivityPoint. @@ -18,8 +18,8 @@ export function activityToPoint( const b = activitiesMap[bId]; if (a && b) { - const aStartTime = getUnixEpochTime(a.start_time); - const bStartTime = getUnixEpochTime(b.start_time); + const aStartTime = getUnixEpochTime(a.start_time_doy); + const bStartTime = getUnixEpochTime(b.start_time_doy); return compare(aStartTime, bStartTime); } @@ -60,7 +60,7 @@ export function activityToPoint( selected: selectedActivityId === activity.id, type: 'activity', unfinished: activity.unfinished, - x: getUnixEpochTime(activity.start_time), + x: getUnixEpochTime(activity.start_time_doy), }; return point; @@ -77,75 +77,13 @@ export function activitiesToPoints( ): ActivityPoint[] { return [...activities] .sort((a: Activity, b: Activity) => { - const aStartTime = getUnixEpochTime(a.start_time); - const bStartTime = getUnixEpochTime(b.start_time); + const aStartTime = getUnixEpochTime(a.start_time_doy); + const bStartTime = getUnixEpochTime(b.start_time_doy); return compare(aStartTime, bStartTime); }) .map(activity => activityToPoint(activitiesMap, activity, selectedActivityId)); } -/** - * Transforms directive activities to activities consumable in the UI. - */ -export function activityDirectiveToActivity( - plan_start_time: string, - activityDirective: ActivityDirective, - getChildIds: (activitySimulated: ActivitySimulated) => ActivitySimulatedId[] = () => [], -): Activity { - const { simulated_activities } = activityDirective; - const activitySimulated = simulated_activities[0]; - - return { - arguments: activityDirective.arguments, - attributes: activitySimulated?.attributes ?? null, - child_ids: getChildIds(activitySimulated), - created_at: activityDirective.created_at, // TODO how much checking should we do for these new metadata fields? - duration: activitySimulated?.duration ?? null, - id: activityDirective.id, - last_modified_at: activityDirective.last_modified_at, - metadata: activityDirective.metadata, - name: activityDirective.name, - parent_id: null, - simulated_activity_id: activitySimulated?.id ?? null, - simulation_dataset_id: activitySimulated?.simulation_dataset_id ?? null, - source_scheduling_goal_id: activityDirective.source_scheduling_goal_id, - start_time: getDoyTimeFromDuration(plan_start_time, activityDirective.start_offset), - tags: activityDirective.tags, - type: activityDirective.type, - unfinished: activitySimulated?.duration === null, - }; -} - -/** - * Transforms simulated activities to activities consumable in the UI. - */ -export function activitySimulatedToActivity( - plan_start_time: string, - activitySimulated: ActivitySimulated, - getChildIds: (activitySimulated: ActivitySimulated) => ActivitySimulatedId[], - getParentId: (activitySimulated: ActivitySimulated) => ActivityDirectiveId | ActivitySimulatedId, -): Activity { - return { - arguments: {}, - attributes: activitySimulated.attributes, - child_ids: getChildIds(activitySimulated), - created_at: '', - duration: activitySimulated.duration, - id: activitySimulated.id, - last_modified_at: '', - metadata: {}, - name: '', - parent_id: getParentId(activitySimulated), - simulated_activity_id: activitySimulated.id, - simulation_dataset_id: activitySimulated.simulation_dataset_id, - source_scheduling_goal_id: null, - start_time: getDoyTimeFromDuration(plan_start_time, activitySimulated.start_offset), - tags: [], - type: activitySimulated.activity_type_name, - unfinished: activitySimulated.duration === null, - }; -} - export function getActivityMetadata( activityMetadata: ActivityMetadata, key: ActivityMetadataKey, @@ -168,49 +106,3 @@ export function getActivityRootParent(activitiesMap: ActivitiesMap, activityId: } return getActivityRootParent(activitiesMap, activity.parent_id); } - -/** - * Returns a function that maps each simulated activity id to it's list of simulated activity child ids if they exist. - */ -export function getChildIdsFn( - simulated_activities: ActivitySimulated[], -): (activitySimulated: ActivitySimulated) => ActivitySimulatedId[] { - const parentIdToChildIds = simulated_activities.reduce( - (map: Record, activitySimulated) => { - if (map[activitySimulated.parent_id] === undefined) { - map[activitySimulated.parent_id] = [activitySimulated.id]; - } else { - map[activitySimulated.parent_id].push(activitySimulated.id); - } - return map; - }, - {}, - ); - - return (activitySimulated: ActivitySimulated) => parentIdToChildIds[activitySimulated?.id] ?? []; -} - -/** - * Returns a function that maps each simulated activity id to it's directive activity id if one such directive activity exists. - * If a directive activity does not exists for a given simulated activity, we just return the simulated activities parent id. - */ -export function getParentIdFn( - directive_activities: ActivityDirective[], -): (activitySimulated: ActivitySimulated) => ActivityDirectiveId | ActivitySimulatedId { - const simulatedIdToDirectiveId = directive_activities.reduce( - (map: Record, activityDirective) => { - const { simulated_activities } = activityDirective; - const activitySimulated = simulated_activities[0]; - - if (activitySimulated) { - map[activitySimulated.id] = activityDirective.id; - } - - return map; - }, - {}, - ); - - return (activitySimulated: ActivitySimulated) => - simulatedIdToDirectiveId[activitySimulated.parent_id] ?? activitySimulated.parent_id; -} diff --git a/src/utilities/effects.ts b/src/utilities/effects.ts index 818879b407..fb8caacf02 100644 --- a/src/utilities/effects.ts +++ b/src/utilities/effects.ts @@ -66,28 +66,34 @@ const effects = { tags: tagsString, type, }; - const data = await reqHasura(gql.CREATE_ACTIVITY_DIRECTIVE, { activityDirectiveInsertInput }); + const data = await reqHasura>( + gql.CREATE_ACTIVITY_DIRECTIVE, + { + activityDirectiveInsertInput, + }, + ); const { createActivityDirective } = data; - const { id, name: newName } = createActivityDirective; + const { created_at, id, last_modified_at, name: newName } = createActivityDirective; const activity: Activity = { arguments: argumentsMap, attributes: null, child_ids: [], - created_at: null, + created_at, duration: null, id, - last_modified_at: null, + last_modified_at, metadata, name: newName, parent_id: null, simulated_activity_id: null, simulation_dataset_id: null, source_scheduling_goal_id: null, - start_time: start_time_doy, + start_time_doy, tags, type, unfinished: false, + uniqueId: `directive_${id}`, }; activitiesMap.updateValue((currentActivitiesMap: ActivitiesMap) => ({ ...currentActivitiesMap, [id]: activity })); @@ -1203,9 +1209,9 @@ const effects = { activityDirectiveSetInput.arguments = activity.arguments; } - if (activity.start_time) { - const planStartTime = get(plan).start_time_doy; - activityDirectiveSetInput.start_offset = getIntervalFromDoyRange(planStartTime, activity.start_time); + if (activity.start_time_doy) { + const planStartTimeDoy = get(plan).start_time_doy; + activityDirectiveSetInput.start_offset = getIntervalFromDoyRange(planStartTimeDoy, activity.start_time_doy); } if (activity.tags) { diff --git a/src/utilities/gql.ts b/src/utilities/gql.ts index 0dc78db137..8067953916 100644 --- a/src/utilities/gql.ts +++ b/src/utilities/gql.ts @@ -13,7 +13,9 @@ const gql = { CREATE_ACTIVITY_DIRECTIVE: `#graphql mutation CreateActivityDirective($activityDirectiveInsertInput: activity_directive_insert_input!) { createActivityDirective: insert_activity_directive_one(object: $activityDirectiveInsertInput) { + created_at id + last_modified_at name } } @@ -654,13 +656,15 @@ const gql = { SUB_ACTIVITIES: `#graphql subscription SubActivities($planId: Int!, $simulationDatasetId: Int!) { plan_by_pk(id: $planId) { - activity_directives { + activity_directives(order_by: { start_offset: asc }) { arguments created_at id + last_modified_arguments_at last_modified_at metadata name + plan_id simulated_activities(where: { simulation_dataset_id: { _eq: $simulationDatasetId } }, order_by: { id: desc }, limit: 1) { activity_type_name attributes @@ -688,7 +692,6 @@ const gql = { } } } - start_time } } `, @@ -826,7 +829,7 @@ const gql = { dataset { profiles { name - profile_segments { + profile_segments(order_by: { start_offset: asc }) { dynamics start_offset } @@ -911,7 +914,7 @@ const gql = { dataset { profiles { name - profile_segments { + profile_segments(order_by: { start_offset: asc }) { dynamics start_offset }