diff --git a/Control/public/common/task/TaskTableModel.js b/Control/public/common/task/TaskTableModel.js index cd93340c2..66b06889e 100644 --- a/Control/public/common/task/TaskTableModel.js +++ b/Control/public/common/task/TaskTableModel.js @@ -37,6 +37,16 @@ export class TaskTableModel extends Observable { }; } + /** + * Read the state parameter from the URL and set the filter state accordingly + */ + readUrlState = () =>{ + //Read the current state from the URL + const urlParams = new URLSearchParams(window.location.search); + const states = urlParams.get('state') ? JSON.parse(urlParams.get('state')) : []; + this.setFilterState(decodeURIComponent(states)); + } + /** * Checks whether the filter for a specified state is enabled * @param {string} state - state to check diff --git a/Control/public/common/task/tasksPerHostPanel.js b/Control/public/common/task/tasksPerHostPanel.js index 6a873270b..578dc0d64 100644 --- a/Control/public/common/task/tasksPerHostPanel.js +++ b/Control/public/common/task/tasksPerHostPanel.js @@ -52,12 +52,32 @@ export const tasksPerHostPanel = ( return h('.m5.text-center', 'Failed to load tasks'); } tasks = tasks.payload; + taskTableModel.readUrlState(); + tasks = tasks.filter(taskTableModel.doesTaskMatchFilter.bind(taskTableModel)); const tasksByHosts = source === FLP ? getTasksByFlp(tasks) : getTasksByEpn(tasks); const infoLoggerButtonTitle = source === FLP ? 'InfoLogger FLP' : 'InfoLogger EPN'; const infoLoggerButtonUrl = source === FLP ? COG.ILG_URL : COG.ILG_EPN_URL; - + /** + * + * @param {*} state + */ + const updateUrlWithStates = (state) => { + //First we toggle the state + taskTableModel.toggleFilterState(state); + const urlParams = new URLSearchParams(window.location.search); + const states = urlParams.get('state') ? JSON.parse(urlParams.get('state')) : []; + if(taskTableModel._filterBy.state.length > 0){ + urlParams.set('state', JSON.stringify(taskTableModel._filterBy.state)); + } + else { + urlParams.delete('state'); + } + console.log('Current states', states,state); + console.log(decodeURIComponent(urlParams.toString())); + taskTableModel._model.router.go(`?${decodeURIComponent(urlParams.toString())}`, true, true); + }; return h('.flex-column.g2', [ h('.flex-row.g1', [ h('.flex-row.g1', [ @@ -73,7 +93,9 @@ export const tasksPerHostPanel = ( h('.flex-row.g1.flex-wrap.flex-grow-3', [ TASK_STATES.map((state) => h(`button.btn${getTaskStateClassAssociation(state)}`, { - onclick: () => taskTableModel.toggleFilterState(state), + onclick: () => { + updateUrlWithStates(state); + }, class: taskTableModel.isFilterStateEnabled(state) ? 'active' : '', }, state) ), diff --git a/Control/public/pages/Environment/Environment.page.js b/Control/public/pages/Environment/Environment.page.js index 8a941012b..75f675392 100644 --- a/Control/public/pages/Environment/Environment.page.js +++ b/Control/public/pages/Environment/Environment.page.js @@ -57,7 +57,7 @@ const showEnvironmentPage = (model, environmentInfo) => { const { id, state, currentTransition = undefined, includedDetectors, userVars } = environmentInfo; const isDcsEnabled = userVars?.['dcs_enabled'] === 'true'; const isRunningStable = !currentTransition && state === EnvironmentState.RUNNING; - const { services: { detectors: { availability = {} } = {} } } = model; + const { services: { detectors: { availability = {} } = {} } } = model; /** * Given a component and a state, navigate silently to the environment page with the component as the panel @@ -66,8 +66,22 @@ const showEnvironmentPage = (model, environmentInfo) => { * @return {Promise} - promise to navigate to the page */ const onRowClick = async (component, state) => { - model.router.go(`?page=environment&id=${environmentInfo.id}&panel=${component}`, true, true); - model.environment.taskTableModel.setFilterState(state); + + //Read the current state from the URL + const urlParams = new URLSearchParams(window.location.search); + const states = urlParams.get('state') ? JSON.parse(urlParams.get('state')) : []; + states.splice(0,states.length); //Clear the array + + if (!states.includes(state)) { + // Add state to the list of states to filter by + states.push(state); + } + urlParams.set('state', JSON.stringify(states)); + const stateParam = urlParams.get('state') ? `&state=${decodeURIComponent(urlParams.get('state'))}` : ''; + + model.router.go(` + ?page=environment&id=${environmentInfo.id}&panel=${component}${stateParam}`,true, true); + model.environment.taskTableModel.setFilterState(stateParam); document.getElementById('environment-tabs-navigation-header').scrollIntoView({ behavior: 'auto', block: 'center' }); await model.environment.getEnvironment({ id: environmentInfo.id }, false, component); diff --git a/Control/test/mocha-index.js b/Control/test/mocha-index.js index 239827c4f..0d92106c9 100644 --- a/Control/test/mocha-index.js +++ b/Control/test/mocha-index.js @@ -59,7 +59,11 @@ describe('Control', function() { this.ok = true; // Start browser to test UI - browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox'], headless: true}); + browser = await puppeteer.launch({args: [ + '--no-sandbox', '--disable-setuid-sandbox', + '--disable-accelerated-2d-canvas', + '--disable-gpu' + ], headless: true}); page = await browser.newPage(); // Listen to browser diff --git a/Control/test/public/page-environment-mocha.js b/Control/test/public/page-environment-mocha.js index d397cc843..09212da69 100644 --- a/Control/test/public/page-environment-mocha.js +++ b/Control/test/public/page-environment-mocha.js @@ -216,4 +216,77 @@ describe('`pageEnvironment` test-suite', async () => { assert.ok(calls['destroyEnvironment']); }); }); + + describe('Tasks Per Host Panel', function() { + + describe('Check URL updates with state parameter', function() { + + afterEach(() => { + url = test.helpers.url; + }); + it('should update URL with state=["ERROR"] when clicking on ERROR column', async ()=> { + await page.goto(url + '?page=environment&id=6f6d6387-6577-11e8-993a-f07959157220&panel=flp', {waitUntil: 'networkidle0'}); + + // Simulate clicking on a number in the ERROR column + await page.click('.flex-row.g1.flex-wrap.flex-grow-3 .btn.danger:nth-child(2)'); + + // Verify URL contains state=["ERROR"] + url = page.url(); + assert.ok(url.includes('state=[%22ERROR%22]')); // Encoded value for ["ERROR"] + }); + + it('should update URL with state=["CONFIGURED","ERROR"] when clicking on state buttons', async ()=> { + await page.goto(url + '?page=environment&id=6f6d6387-6577-11e8-993a-f07959157220&panel=flp', { waitUntil: 'networkidle0' }); + + await page.waitForSelector('.flex-row.g1.flex-wrap.flex-grow-3 .btn.primary '); + await page.waitForSelector('.flex-row.g1.flex-wrap.flex-grow-3 .btn.danger:nth-child(2)'); + + // Simulate clicking on Configured and Error state buttons + await page.click('.flex-row.g1.flex-wrap.flex-grow-3 .btn.primary '); + await page.click('.flex-row.g1.flex-wrap.flex-grow-3 .btn.danger:nth-child(2)'); + + + // Verify URL contains state=["CONFIGURED","ERROR"] + url = page.url(); + assert.ok(url.includes('&state=[%22CONFIGURED%22,%22ERROR%22]')); // Encoded value for ["CONFIGURED","ERROR"] + }); + + it('should remove state parameter when all states are selected', async function() { + await page.goto(url + '?page=environment&id=6f6d6387-6577-11e8-993a-f07959157220&panel=flp', { waitUntil: 'networkidle0' }); + await page.waitForSelector('.flex-row.g1.flex-wrap.flex-grow-3 .btn.primary'); + + const stateButtons = await page.$$('.flex-row.g1.flex-wrap.flex-grow-3 .btn'); + for (const button of stateButtons) { + await button.click(); + } + url = page.url(); + // Verify URL does not contain state parameter + assert.ok(url.includes('&state=[%22ERROR_CRITICAL%22,%22ERROR%22,%22RUNNING%22,%22CONFIGURED%22,%22STANDBY%22,%22DONE%22,%22INVARIANT%22,%22MIXED%22]')); + + + await page.waitForSelector('.flex-row.g1.flex-wrap.flex-grow-3 .btn.active'); + + const stateActiveButtons = await page.$$('.flex-row.g1.flex-wrap.flex-grow-3 .btn.active'); + for (const button of stateActiveButtons) { + await button.click(); + } + assert.ok(!url.includes('&state=["UNKNOWN"]')); + }); + + it('should only display state parameter in task panels', async function() { + await page.goto(url + '?page=environment&id=6f6d6387-6577-11e8-993a-f07959157220&panel=flp', { waitUntil: 'networkidle0' }); + + // Verify URL does not contain state parameter + url = page.url(); + assert.ok(!url.includes('state=')); + + // Simulate navigating to a task panel (e.g., FLP) + await page.click('#flp-pane'); + + // Verify URL contains state parameter + const urlWithState = page.url(); + assert.ok(urlWithState.includes('state=')); + }); + }); + }); });