mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 21:27:52 +00:00
Allow specification of swimlanes via configuration (#7200)
* Use specified group order for plans * Allow groupIds to be a function * Fix typo in if statement * Check that activities are present for a given group * Change refresh to emit the new model * Update domainobject on change * Revert changes for domainObject * Revert groupIds as functions. Check if groups are objects with names instead. * Add e2e test for plan swim lane order * Address review comments - improve if statement * Move function to right util helper * Fix path for imported code * Remove focused test * Change the name of the ordered group configuration
This commit is contained in:
parent
3520a929a9
commit
250db8d7f9
@ -81,6 +81,30 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the swim lanes / groups in the plan view matches the order of
|
||||
* groups in the plan data.
|
||||
* @param {import('@playwright/test').Page} page the page
|
||||
* @param {object} plan The raw plan json to assert against
|
||||
* @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
|
||||
*/
|
||||
export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
|
||||
// Switch to the plan view
|
||||
await page.goto(`${objectUrl}?view=plan.view`);
|
||||
const planGroups = await page
|
||||
.locator('.c-plan__contents > div > .c-swimlane__lane-label .c-object-label__name')
|
||||
.all();
|
||||
|
||||
const groups = plan.Groups;
|
||||
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
// Assert that the order of groups in the plan view matches the order of
|
||||
// groups in the plan data
|
||||
const groupName = await planGroups[i].innerText();
|
||||
expect(groupName).toEqual(groups[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the plan view, switch to fixed time mode,
|
||||
* and set the bounds to span all activities.
|
||||
@ -110,3 +134,23 @@ export async function setDraftStatusForPlan(page, plan) {
|
||||
await window.openmct.status.set(planObject.uuid, 'draft');
|
||||
}, plan);
|
||||
}
|
||||
|
||||
export async function addPlanGetInterceptor(page) {
|
||||
await page.waitForLoadState('load');
|
||||
await page.evaluate(async () => {
|
||||
await window.openmct.objects.addGetInterceptor({
|
||||
appliesTo: (identifier, domainObject) => {
|
||||
return domainObject && domainObject.type === 'plan';
|
||||
},
|
||||
invoke: (identifier, object) => {
|
||||
if (object) {
|
||||
object.sourceMap = {
|
||||
orderedGroups: 'Groups'
|
||||
};
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
54
e2e/test-data/examplePlans/ExamplePlanWithOrderedLanes.json
Normal file
54
e2e/test-data/examplePlans/ExamplePlanWithOrderedLanes.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"Groups": [
|
||||
{
|
||||
"name": "Group 1"
|
||||
},
|
||||
{
|
||||
"name": "Group 2"
|
||||
}
|
||||
],
|
||||
"Group 2": [
|
||||
{
|
||||
"name": "Past event 3",
|
||||
"start": 1660493208000,
|
||||
"end": 1660503981000,
|
||||
"type": "Group 2",
|
||||
"color": "orange",
|
||||
"textColor": "white"
|
||||
},
|
||||
{
|
||||
"name": "Past event 4",
|
||||
"start": 1660579608000,
|
||||
"end": 1660624108000,
|
||||
"type": "Group 2",
|
||||
"color": "orange",
|
||||
"textColor": "white"
|
||||
},
|
||||
{
|
||||
"name": "Past event 5",
|
||||
"start": 1660666008000,
|
||||
"end": 1660681529000,
|
||||
"type": "Group 2",
|
||||
"color": "orange",
|
||||
"textColor": "white"
|
||||
}
|
||||
],
|
||||
"Group 1": [
|
||||
{
|
||||
"name": "Past event 1",
|
||||
"start": 1660320408000,
|
||||
"end": 1660343797000,
|
||||
"type": "Group 1",
|
||||
"color": "orange",
|
||||
"textColor": "white"
|
||||
},
|
||||
{
|
||||
"name": "Past event 2",
|
||||
"start": 1660406808000,
|
||||
"end": 1660429160000,
|
||||
"type": "Group 1",
|
||||
"color": "orange",
|
||||
"textColor": "white"
|
||||
}
|
||||
]
|
||||
}
|
@ -21,8 +21,13 @@
|
||||
*****************************************************************************/
|
||||
const { test } = require('../../../pluginFixtures');
|
||||
const { createPlanFromJSON } = require('../../../appActions');
|
||||
const { addPlanGetInterceptor } = require('../../../helper/planningUtils.js');
|
||||
const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json');
|
||||
const { assertPlanActivities } = require('../../../helper/planningUtils');
|
||||
const testPlanWithOrderedLanes = require('../../../test-data/examplePlans/ExamplePlanWithOrderedLanes.json');
|
||||
const {
|
||||
assertPlanActivities,
|
||||
assertPlanOrderedSwimLanes
|
||||
} = require('../../../helper/planningUtils');
|
||||
|
||||
test.describe('Plan', () => {
|
||||
let plan;
|
||||
@ -36,4 +41,14 @@ test.describe('Plan', () => {
|
||||
test('Displays all plan events', async ({ page }) => {
|
||||
await assertPlanActivities(page, testPlan1, plan.url);
|
||||
});
|
||||
|
||||
test('Displays plans with ordered swim lanes configuration', async ({ page }) => {
|
||||
// Add configuration for swim lanes
|
||||
await addPlanGetInterceptor(page);
|
||||
// Create the plan
|
||||
const planWithSwimLanes = await createPlanFromJSON(page, {
|
||||
json: testPlanWithOrderedLanes
|
||||
});
|
||||
await assertPlanOrderedSwimLanes(page, testPlanWithOrderedLanes, planWithSwimLanes.url);
|
||||
});
|
||||
});
|
||||
|
@ -59,7 +59,7 @@ import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
||||
|
||||
import TimelineAxis from '../../../ui/components/TimeSystemAxis.vue';
|
||||
import PlanViewConfiguration from '../PlanViewConfiguration';
|
||||
import { getContrastingColor, getValidatedData } from '../util';
|
||||
import { getContrastingColor, getValidatedData, getValidatedGroups } from '../util';
|
||||
import ActivityTimeline from './ActivityTimeline.vue';
|
||||
|
||||
const PADDING = 1;
|
||||
@ -416,7 +416,7 @@ export default {
|
||||
return currentRow || SWIMLANE_PADDING;
|
||||
},
|
||||
generateActivities() {
|
||||
const groupNames = Object.keys(this.planData);
|
||||
const groupNames = getValidatedGroups(this.domainObject, this.planData);
|
||||
|
||||
if (!groupNames.length) {
|
||||
return;
|
||||
@ -430,6 +430,10 @@ export default {
|
||||
let currentRow = 0;
|
||||
|
||||
const rawActivities = this.planData[groupName];
|
||||
if (rawActivities === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
rawActivities.forEach((rawActivity) => {
|
||||
if (!this.isActivityInBounds(rawActivity)) {
|
||||
return;
|
||||
|
@ -22,17 +22,7 @@
|
||||
|
||||
export function getValidatedData(domainObject) {
|
||||
const sourceMap = domainObject.sourceMap;
|
||||
const body = domainObject.selectFile?.body;
|
||||
let json = {};
|
||||
if (typeof body === 'string') {
|
||||
try {
|
||||
json = JSON.parse(body);
|
||||
} catch (e) {
|
||||
return json;
|
||||
}
|
||||
} else if (body !== undefined) {
|
||||
json = body;
|
||||
}
|
||||
const json = getObjectJson(domainObject);
|
||||
|
||||
if (
|
||||
sourceMap !== undefined &&
|
||||
@ -69,6 +59,47 @@ export function getValidatedData(domainObject) {
|
||||
}
|
||||
}
|
||||
|
||||
function getObjectJson(domainObject) {
|
||||
const body = domainObject.selectFile?.body;
|
||||
let json = {};
|
||||
if (typeof body === 'string') {
|
||||
try {
|
||||
json = JSON.parse(body);
|
||||
} catch (e) {
|
||||
return json;
|
||||
}
|
||||
} else if (body !== undefined) {
|
||||
json = body;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
export function getValidatedGroups(domainObject, planData) {
|
||||
let orderedGroupNames;
|
||||
const sourceMap = domainObject.sourceMap;
|
||||
const json = getObjectJson(domainObject);
|
||||
if (sourceMap?.orderedGroups) {
|
||||
const groups = json[sourceMap.orderedGroups];
|
||||
if (groups.length && typeof groups[0] === 'object') {
|
||||
//if groups is a list of objects, then get the name property from each group object.
|
||||
const groupsWithNames = groups.filter(
|
||||
(groupObj) => groupObj.name !== undefined && groupObj.name !== ''
|
||||
);
|
||||
orderedGroupNames = groupsWithNames.map((groupObj) => groupObj.name);
|
||||
} else {
|
||||
// Otherwise, groups is likely a list of names, so use that.
|
||||
orderedGroupNames = groups;
|
||||
}
|
||||
}
|
||||
|
||||
if (orderedGroupNames === undefined) {
|
||||
orderedGroupNames = Object.keys(planData);
|
||||
}
|
||||
|
||||
return orderedGroupNames;
|
||||
}
|
||||
|
||||
export function getContrastingColor(hexColor) {
|
||||
function cutHex(h, start, end) {
|
||||
const hStr = h.charAt(0) === '#' ? h.substring(1, 7) : h;
|
||||
|
@ -53,7 +53,7 @@ import _ from 'lodash';
|
||||
import SwimLane from '@/ui/components/swim-lane/SwimLane.vue';
|
||||
|
||||
import TimelineAxis from '../../ui/components/TimeSystemAxis.vue';
|
||||
import { getValidatedData } from '../plan/util';
|
||||
import { getValidatedData, getValidatedGroups } from '../plan/util';
|
||||
import TimelineObjectView from './TimelineObjectView.vue';
|
||||
|
||||
const unknownObjectType = {
|
||||
@ -108,7 +108,8 @@ export default {
|
||||
let objectPath = [domainObject].concat(this.objectPath.slice());
|
||||
let rowCount = 0;
|
||||
if (domainObject.type === 'plan') {
|
||||
rowCount = Object.keys(getValidatedData(domainObject)).length;
|
||||
const planData = getValidatedData(domainObject);
|
||||
rowCount = getValidatedGroups(domainObject, planData).length;
|
||||
} else if (domainObject.type === 'gantt-chart') {
|
||||
rowCount = Object.keys(domainObject.configuration.swimlaneVisibility).length;
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ import { v4 as uuid } from 'uuid';
|
||||
import { TIME_CONTEXT_EVENTS } from '../../api/time/constants';
|
||||
import ListView from '../../ui/components/List/ListView.vue';
|
||||
import { getPreciseDuration } from '../../utils/duration';
|
||||
import { getValidatedData } from '../plan/util';
|
||||
import { getValidatedData, getValidatedGroups } from '../plan/util';
|
||||
import { SORT_ORDER_OPTIONS } from './constants';
|
||||
|
||||
const SCROLL_TIMEOUT = 10000;
|
||||
@ -283,10 +283,13 @@ export default {
|
||||
this.planData = getValidatedData(domainObject);
|
||||
},
|
||||
listActivities() {
|
||||
let groups = Object.keys(this.planData);
|
||||
let groups = getValidatedGroups(this.domainObject, this.planData);
|
||||
let activities = [];
|
||||
|
||||
groups.forEach((key) => {
|
||||
if (this.planData[key] === undefined) {
|
||||
return;
|
||||
}
|
||||
// Create new objects so Vue 3 can detect any changes
|
||||
activities = activities.concat(JSON.parse(JSON.stringify(this.planData[key])));
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user