Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timelist centering algorithm change and duration formatting change #7194

Merged
merged 25 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d2df694
Change the centering algorithm for timelist
shefalijoshi Nov 1, 2023
c5713f3
test: add time list countdown and countup test
ozyx Nov 7, 2023
cc824e0
test: respond to comments
ozyx Nov 8, 2023
755054e
chore: lint fix
ozyx Nov 8, 2023
c13b375
fix: lint errors and visual suite failures
ozyx Nov 8, 2023
cc7223b
Change parameters to options object
shefalijoshi Nov 20, 2023
2bb696a
Fix regression with auto scroll. Improve code readability
shefalijoshi Nov 20, 2023
3e47056
Merge branch 'master' into timelist-7130-7161
shefalijoshi Nov 20, 2023
e591933
Merge branch 'master' into timelist-7130-7161
shefalijoshi Nov 27, 2023
c467e85
Merge branch 'master' into timelist-7130-7161
shefalijoshi Dec 5, 2023
0c77e99
Add defaults for getPreciseDuration options object
shefalijoshi Dec 5, 2023
6382172
Merge branch 'timelist-7130-7161' of https://github.com/nasa/openmct …
shefalijoshi Dec 5, 2023
a99e522
Defaults for options
shefalijoshi Dec 5, 2023
031799a
Merge branch 'master' into timelist-7130-7161
shefalijoshi Dec 5, 2023
3411565
Merge branch 'master' into timelist-7130-7161
shefalijoshi Dec 11, 2023
f309749
Add missing await
shefalijoshi Dec 11, 2023
ef6b39d
Merge branch 'timelist-7130-7161' of https://github.com/nasa/openmct …
shefalijoshi Dec 11, 2023
6ba1052
Merge branch 'master' into timelist-7130-7161
shefalijoshi Dec 13, 2023
5bf252a
Merge branch 'master' of https://github.com/nasa/openmct into timelis…
shefalijoshi Jan 3, 2024
c79741b
Merge branch 'timelist-7130-7161' of https://github.com/nasa/openmct …
shefalijoshi Jan 3, 2024
69a6e2f
Fix imports for ESM
shefalijoshi Jan 3, 2024
75ba7f9
Fix json import
shefalijoshi Jan 3, 2024
1327b88
Fix broken test
shefalijoshi Jan 3, 2024
cdc3b9a
Merge branch 'master' into timelist-7130-7161
shefalijoshi Jan 4, 2024
c9000cf
Merge branch 'master' into timelist-7130-7161
shefalijoshi Jan 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions e2e/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ export const MISSION_TIME = 1732413600000; // Saturday, November 23, 2024 6:00:0

/**
* URL Constants
* - This is the URL that the browser will be directed to when running visual tests. This URL
* - This is the URL that the browser will be directed to when running visual tests. This URL
* - hides the tree and inspector to prevent visual noise
* - sets the time bounds to a fixed range
*/
export const VISUAL_URL = './#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
export const VISUAL_URL =
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
24 changes: 21 additions & 3 deletions e2e/helper/planningUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,35 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
* @param {string} planObjectUrl
*/
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
const activities = Object.values(planJson).flat();
// Get the earliest start value
const start = Math.min(...activities.map((activity) => activity.start));
const start = getEarliestStartTime(planJson);
// Get the latest end value
const end = Math.max(...activities.map((activity) => activity.end));
const end = getLatestEndTime(planJson);
// Set the start and end bounds to the earliest start and latest end
await page.goto(
`${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view`
);
}

/**
* @param {object} planJson
* @returns {number}
*/
export function getEarliestStartTime(planJson) {
const activities = Object.values(planJson).flat();
return Math.min(...activities.map((activity) => activity.start));
}

/**
*
* @param {object} planJson
* @returns {number}
*/
export function getLatestEndTime(planJson) {
const activities = Object.values(planJson).flat();
return Math.max(...activities.map((activity) => activity.end));
}

/**
* Uses the Open MCT API to set the status of a plan to 'draft'.
* @param {import('@playwright/test').Page} page
Expand Down
38 changes: 38 additions & 0 deletions e2e/test-data/examplePlans/ExamplePlan_Small3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"Group 1": [
{
"name": "Time until birthday",
"start": 1650320402000,
"end": 1660343797000,
"type": "Group 1",
"color": "orange",
"textColor": "white"
},
{
"name": "Time until supper",
"start": 1650320402000,
"end": 1650420410000,
"type": "Group 2",
"color": "blue",
"textColor": "white"
}
],
"Group 2": [
{
"name": "Time since the last time I ate",
"start": 1650320102001,
"end": 1650320102001,
"type": "Group 2",
"color": "green",
"textColor": "white"
},
{
"name": "Time since last accident",
Copy link
Contributor

Choose a reason for hiding this comment

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

lol

"start": 1650320102002,
"end": 1650320102002,
"type": "Group 1",
"color": "yellow",
"textColor": "white"
}
]
}
184 changes: 175 additions & 9 deletions e2e/tests/functional/planning/timelist.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@

const { test, expect } = require('../../../pluginFixtures');
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
const { getEarliestStartTime } = require('../../../helper/planningUtils');
const examplePlanSmall3 = require('../../../test-data/examplePlans/ExamplePlan_Small3.json');
Copy link
Contributor

Choose a reason for hiding this comment

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

We're all ESM now, so we need to update these imports:

Suggested change
const { getEarliestStartTime } = require('../../../helper/planningUtils');
const examplePlanSmall3 = require('../../../test-data/examplePlans/ExamplePlan_Small3.json');
import { getEarliestStartTime } from '../../../helper/planningUtils.js';
const examplePlanSmall = JSON.parse(
fs.readFileSync(new URL('../../test-data/examplePlans/ExamplePlan_Small3.json', import.meta.url))
);


// eslint-disable-next-line no-unused-vars
const START_TIME_COLUMN = 0;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class

Unused variable START_TIME_COLUMN.
// eslint-disable-next-line no-unused-vars
const END_TIME_COLUMN = 1;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class

Unused variable END_TIME_COLUMN.
const TIME_TO_FROM_COLUMN = 2;
// eslint-disable-next-line no-unused-vars
const ACTIVITY_COLUMN = 3;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class

Unused variable ACTIVITY_COLUMN.
const HEADER_ROW = 0;
const NUM_COLUMNS = 4;

const testPlan = {
TEST_GROUP: [
Expand Down Expand Up @@ -84,22 +96,17 @@ test.describe('Time List', () => {
});

await test.step('Create a Plan and add it to the timelist', async () => {
const createdPlan = await createPlanFromJSON(page, {
await createPlanFromJSON(page, {
name: 'Test Plan',
json: testPlan
json: testPlan,
parent: timelist.uuid
});

await page.goto(timelist.url);
// Expand the tree to show the plan
await page.click("button[title='Show selected item in tree']");
await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view');
await page.click("button[title='Save']");
await page.click("li[title='Save and Finish Editing']");

const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;

await page.goto(timelist.url);

// Switch to fixed time mode with all plan events within the bounds
await page.goto(
`${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view`
Expand All @@ -122,3 +129,162 @@ test.describe('Time List', () => {
});
});
});

/**
* The regular expression used to parse the countdown string.
* Some examples of valid Countdown strings:
* ```
* '35D 02:03:04'
* '-1D 01:02:03'
* '01:02:03'
* '-05:06:07'
* ```
*/
const COUNTDOWN_REGEXP = /(-)?(\d+D\s)?(\d{2}):(\d{2}):(\d{2})/;

/**
* @typedef {Object} CountdownObject
* @property {string} sign - The sign of the countdown ('-' if the countdown is negative, otherwise undefined).
* @property {string} days - The number of days in the countdown (undefined if there are no days).
* @property {string} hours - The number of hours in the countdown.
* @property {string} minutes - The number of minutes in the countdown.
* @property {string} seconds - The number of seconds in the countdown.
* @property {string} toString - The countdown string.
*/

/**
* Object representing the indices of the capture groups in a countdown regex match.
*
* @typedef {{ SIGN: number, DAYS: number, HOURS: number, MINUTES: number, SECONDS: number, REGEXP: RegExp }}
* @property {number} SIGN - The index for the sign capture group (1 if a '-' sign is present, otherwise undefined).
* @property {number} DAYS - The index for the days capture group (2 for the number of days, otherwise undefined).
* @property {number} HOURS - The index for the hours capture group (3 for the hour part of the time).
* @property {number} MINUTES - The index for the minutes capture group (4 for the minute part of the time).
* @property {number} SECONDS - The index for the seconds capture group (5 for the second part of the time).
*/
const COUNTDOWN = Object.freeze({
SIGN: 1,
DAYS: 2,
HOURS: 3,
MINUTES: 4,
SECONDS: 5
});

test.describe('Time List with controlled clock', () => {
test.use({
clockOptions: {
now: getEarliestStartTime(examplePlanSmall3),
shouldAdvanceTime: true
}
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Time List shows current events and counts down correctly in real-time mode', async ({
page
}) => {
await test.step('Create a Time List, add a Plan to it, and switch to real-time mode', async () => {
// Create Time List
const timelist = await createDomainObjectWithDefaults(page, {
type: 'Time List'
});

// Create a Plan with events that count down and up.
// Add it as a child to the Time List.
await createPlanFromJSON(page, {
json: examplePlanSmall3,
parent: timelist.uuid
});

// Navigate to the Time List in real-time mode
await page.goto(
`${timelist.url}?tc.mode=local&tc.startDelta=900000&tc.endDelta=1800000&tc.timeSystem=utc&view=grid`
);
});

const countUpCells = [
getCellByIndex(page, 1, TIME_TO_FROM_COLUMN),
getCellByIndex(page, 2, TIME_TO_FROM_COLUMN)
];
const countdownCells = [
getCellByIndex(page, 3, TIME_TO_FROM_COLUMN),
getCellByIndex(page, 4, TIME_TO_FROM_COLUMN)
];

// Verify that the countdown cells are counting down
for (let i = 0; i < countdownCells.length; i++) {
await test.step(`Countdown cell ${i + 1} counts down`, async () => {
const countdownCell = countdownCells[i];
// Get the initial countdown timestamp object
const beforeCountdown = await getAndAssertCountdownObject(page, i + 3);
// Wait until it changes
await expect(countdownCell).not.toHaveText(beforeCountdown.toString());
// Get the new countdown timestamp object
const afterCountdown = await getAndAssertCountdownObject(page, i + 3);
// Verify that the new countdown timestamp object is less than the old one
expect(Number(afterCountdown.seconds)).toBeLessThan(Number(beforeCountdown.seconds));
});
}

// Verify that the count-up cells are counting up
for (let i = 0; i < countUpCells.length; i++) {
await test.step(`Count-up cell ${i + 1} counts up`, async () => {
const countdownCell = countUpCells[i];
// Get the initial count-up timestamp object
const beforeCountdown = await getAndAssertCountdownObject(page, i + 1);
// Wait until it changes
await expect(countdownCell).not.toHaveText(beforeCountdown.toString());
// Get the new count-up timestamp object
const afterCountdown = await getAndAssertCountdownObject(page, i + 1);
// Verify that the new count-up timestamp object is greater than the old one
expect(Number(afterCountdown.seconds)).toBeGreaterThan(Number(beforeCountdown.seconds));
});
}
});
});

/**
* Get the cell at the given row and column indices.
* @param {import('@playwright/test').Page} page
* @param {number} rowIndex
* @param {number} columnIndex
* @returns {import('@playwright/test').Locator} cell
*/
function getCellByIndex(page, rowIndex, columnIndex) {
return page.getByRole('cell').nth(rowIndex * NUM_COLUMNS + columnIndex);
}

/**
* Return the innerText of the cell at the given row and column indices.
* @param {import('@playwright/test').Page} page
* @param {number} rowIndex
* @param {number} columnIndex
* @returns {Promise<string>} text
*/
async function getCellTextByIndex(page, rowIndex, columnIndex) {
const text = await getCellByIndex(page, rowIndex, columnIndex).innerText();
return text;
}

/**
* Get the text from the countdown cell in the given row, assert that it matches the countdown
* regex, and return an object representing the countdown.
* @param {import('@playwright/test').Page} page
* @param {number} rowIndex the row index
* @returns {Promise<CountdownObject>} countdownObject
*/
async function getAndAssertCountdownObject(page, rowIndex) {
const timeToFrom = await getCellTextByIndex(page, HEADER_ROW + rowIndex, TIME_TO_FROM_COLUMN);

expect(timeToFrom).toMatch(COUNTDOWN_REGEXP);
const match = timeToFrom.match(COUNTDOWN_REGEXP);

return {
sign: match[COUNTDOWN.SIGN],
days: match[COUNTDOWN.DAYS],
hours: match[COUNTDOWN.HOURS],
minutes: match[COUNTDOWN.MINUTES],
seconds: match[COUNTDOWN.SECONDS],
toString: () => timeToFrom
};
}
Loading