Skip to content

Commit

Permalink
[OGUI-420] Improve task table (#402)
Browse files Browse the repository at this point in the history
* [OGUI-420] Add className to taskInfo

* [OGUI-420] Parse name of task

* [OGUI-420] Request tasks on load of page

* [OGUI-420] Left algin name and args columns

* [OGUI-420] Notify in case of error

* [OGUI-420] Add test for regex match
  • Loading branch information
graduta authored Nov 11, 2019
1 parent 59fe9e5 commit e13d5da
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 95 deletions.
2 changes: 0 additions & 2 deletions Control/public/common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ export default function parseObject(item, key) {
return item.length;
case 'version':
return item.productName + ' v' + item.versionStr + '(revision ' + item.build + ')';
case 'deploymentInfo':
return item.hostname;
default:
return JSON.stringify(item);
}
Expand Down
22 changes: 21 additions & 1 deletion Control/public/environment/Environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,31 @@ export default class Environment extends Observable {
this.notify();
return;
}
this.item = RemoteData.success(result);
await result.environment.tasks.forEach((task) => {
this.task.getTaskById({taskId: task.taskId});
});
this.item = RemoteData.success(this.parseEnvResult(result));
this.itemControl = RemoteData.notAsked(); // because item has changed
this.notify();
}

/**
* Method to remove and parse fields from environment result
* @param {JSON} result
* @return {JSON}
*/
parseEnvResult(result) {
result.environment.tasks.forEach((task) => {
const regex = new RegExp(`tasks/.*@`);
const tasksAndName = task.name.match(regex);
if (tasksAndName) {
task.name = tasksAndName[0].replace('tasks/', '').replace('@', '');
}
});

return result;
}

/**
* Control a remote environment, store action result into `itemControl` as RemoteData
* @param {Object} body - See protobuf definition for properties
Expand Down
45 changes: 13 additions & 32 deletions Control/public/environment/Task.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,56 +13,37 @@ export default class Task extends Observable {

this.model = model;
this.storage = new BrowserStorage('AliECS');
this.remoteTasks = RemoteData.notAsked();
this.openedTasks = [];
this.openedTasks = {};
this.list = {}; // map of (taskId, RemoteData)
}

/**
* Load Task into list of tasks
* @param {JSON} body {taskId: <string>}
* Toggle the view of a task by its id
* @param {string} taskId
*/
async updateOpenedTasks(body) {
const indexOfSelectedTask = this.getIndexOfTask(body.taskId);

if (indexOfSelectedTask >= 0) { // if Task is already opened then remove from list
this.openedTasks.splice(indexOfSelectedTask, 1);
} else {
const commandInfo = this.storage.getLocalItem(body.taskId);
if (commandInfo) {
this.openedTasks.push(commandInfo);
} else {
await this.getTaskById(body);
}
}
this.remoteTasks = RemoteData.success(this.openedTasks);
async toggleTaskView(taskId) {
this.openedTasks[taskId] = !this.openedTasks[taskId];
this.notify();
}

/**
* Method to retrieve index of a task in the existing opened task list
* @param {string} taskId
* @return {number}
*/
getIndexOfTask(taskId) {
return this.openedTasks.map((task) => task.taskId).indexOf(taskId);
}

/**
* Method to make an HTTP Request to get details about a task by its Id
* @param {JSON} body {taskId: string}
*/
async getTaskById(body) {
this.remoteTasks = RemoteData.loading();
this.list[body.taskId] = RemoteData.loading();
this.openedTasks[body.taskId] = false;
this.notify();

const {result, ok} = await this.model.loader.post(`/api/GetTask`, body);
if (!ok) {
this.openedTasks.push({taskId: body.taskId, message: result.message});
if (ok) {
this.list[body.taskId] = RemoteData.failure(result.message);
} else {
const commandInfo = this.parseTaskCommandInfo(result.task.commandInfo, body.taskId);
this.storage.setLocalItem(body.taskId, commandInfo);
this.openedTasks.push(commandInfo);
commandInfo.className = result.task.classInfo.name;
this.list[body.taskId] = RemoteData.success(commandInfo);
}
this.notify();
}

/**
Expand Down
120 changes: 60 additions & 60 deletions Control/public/environment/environmentPage.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {h} from '/js/src/index.js';
import {h, iconChevronBottom, iconChevronTop, iconCircleX} from '/js/src/index.js';
import pageLoading from '../common/pageLoading.js';
import pageError from '../common/pageError.js';
import parseObject from './../common/utils.js';
import showTableItem from '../common/showTableItem.js';
/**
* @file Page to show 1 environment (content and header)
Expand Down Expand Up @@ -48,11 +47,13 @@ const showContent = (environment, item) => [
h('.m2.flex-row',
{
style: 'height: 10em;'
}, [
},
[
h('.grafana-font.m1.flex-column',
{
style: 'width: 15%;'
}, [
},
[
h('', {style: 'height:40%'}, 'Run Number'),
h('',
h('.badge.bg-success.white',
Expand All @@ -72,22 +73,18 @@ const showContent = (environment, item) => [
]
),
showEnvDetailsTable(item),
h('.m2.p2', [
h('.m2', [
h('h4', 'Tasks'),
h('.flex-row.flex-grow',
h('.flex-grow', {},
displayTableOfTasks(environment, item.tasks, [
(event, item) => {
environment.task.updateOpenedTasks({taskId: item.taskId});
}]
)
h('.flex-grow',
showEnvTasksTable(environment, item.tasks)
)
)
),
]),
];

/**
* Method to display pltos from Graphana
* Method to display plots from Grafana
* @param {Array<String>} data
* @return {vnode}
*/
Expand Down Expand Up @@ -124,7 +121,7 @@ const showEmbeddedGraphs = (data) =>
* @return {vnode} table view
*/
const showEnvDetailsTable = (item) =>
h('.pv3.m2',
h('.m2.mv4.shadow-level1',
h('table.table', [
h('tbody', [
h('tr', [
Expand Down Expand Up @@ -207,61 +204,64 @@ const controlButton = (buttonType, environment, item, label, type, stateToHide)
);

/**
* Method to create and display a table with tasks
* Method to create and display a table with tasks details
* @param {Object} environment
* @param {Array<Object>} list
* @param {Array<Actions>} actions
* @param {Array<Object>} tasks
* @return {vnode}
*/
const displayTableOfTasks = (environment, list, actions) => h('.scroll-auto', [
h('table.table.table-sm', [
h('thead', [
const showEnvTasksTable = (environment, tasks) => h('.scroll-auto.shadow-level1', [
h('table.table.table-sm', {style: 'margin:0'}, [
h('thead',
h('tr',
[
list.length > 0 && Object.keys(list[0]).map(
(columnName) => h('th', {style: 'text-align:center'}, columnName)),
actions && h('th.text-center', {style: 'text-align:center'}, 'actions')
['Name', 'Locked', 'Status', 'State', 'Host Name', 'Args', 'More']
.map((header) => h('th', {style: 'text-align: center'}, header))
]
)
]),
h('tbody', list.map((item) => [h('tr', [
Object.keys(item).map(
(columnName) => typeof item[columnName] === 'object'
? h('td', parseObject(item[columnName], columnName))
: h('td',
columnName === 'state' && {
class: (item[columnName] === 'RUNNING' ?
'success' : (item[columnName] === 'CONFIGURED' ? 'warning' : '')),
style: 'font-weight: bold;'
},
item[columnName]
)
),
actions && h('td',
h('button.btn.btn-primary',
{
onclick: (event) => actions[0](event, item)
},
environment.task.getIndexOfTask(item.taskId) >= 0 ? 'Close' : 'More')),
]),
environment.task.remoteTasks.match({
NotAsked: () => null,
Loading: () => null,
Success: (data) => environment.task.getIndexOfTask(item.taskId) >= 0
&& displayTaskDetails(data.filter((task) => task.taskId === item.taskId)[0], Object.keys(list[0]).length),
Failure: (error) => pageError(error),
})])),
]
)
),
h('tbody', [
tasks.map((task) => [h('tr', [
h('td', {style: 'text-align:left'}, task.name),
h('td', {style: 'text-align:center'}, task.locked),
h('td', {style: 'text-align:center'}, task.status),
h('td', {
class: (task.state === 'RUNNING' ?
'success' : (task.state === 'CONFIGURED' ? 'warning' : '')),
style: 'font-weight: bold; text-align:center'
}, task.state),
h('td', {style: 'text-align:center'}, task.deploymentInfo.hostname),
environment.task.list[task.taskId] && environment.task.list[task.taskId].match({
NotAsked: () => null,
Loading: () => h('td', {style: 'font-size: 0.25em;text-align:center'}, pageLoading()),
Success: (data) => h('td', {style: 'text-align:left'}, data.arguments),
Failure: (_error) => h('td', {style: 'text-align:center', title: 'Could not load arguments'}, iconCircleX()),
}),
h('td', {style: 'text-align:center'},
h('button.btn.btn-default', {
title: 'More Details',
onclick: () => environment.task.toggleTaskView(task.taskId),
}, environment.task.openedTasks[task.taskId] ? iconChevronTop() : iconChevronBottom())
),
]),
environment.task.openedTasks[task.taskId] && environment.task.list[task.taskId] &&
addTaskDetailsTable(environment, task),
]),
])
])
]);

/**
* Method to display an expandable table with details about a selected task
* @param {Object} task
* @param {number} colSpan
* Method to display an expandable table with details about a selected task if request was successful
* Otherwise display loading or error message
* @param {Object} environment
* @param {JSON} task
* @return {vnode}
*/
const displayTaskDetails = (task, colSpan) =>
h('tr.m5',
h('td', {colspan: ++colSpan}, showTableItem(task))
);
const addTaskDetailsTable = (environment, task) => h('tr', environment.task.list[task.taskId].match({
NotAsked: () => null,
Loading: () => h('td.shadow-level3.m5', {style: 'font-size: 0.25em; text-align: center;', colspan: 7}, pageLoading()),
Success: (data) => h('td', {colspan: 7}, showTableItem(data)),
Failure: (_error) => h('td.shadow-level3.m5',
{style: 'text-align: center;', colspan: 7, title: 'Could not load arguments'},
[iconCircleX(), ' ', _error]),
}));
31 changes: 31 additions & 0 deletions Control/test/public/page-environment-mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,35 @@ describe('`pageEnvironment` test-suite', async () => {
const lockButton = await page.evaluate(() => document.querySelector('body > div:nth-child(2) > div > div > button').title);
assert.deepStrictEqual(lockButton, 'Lock is free');
});

describe('Utils within Environment class', async () => {
it('should replace task name if regex is matched', async () => {
const tagModified = await page.evaluate(() => {
const result = {};
result['environment'] = {};
result.environment.tasks = [{name: 'github.com/AliceO2Group/ControlWorkflows/tasks/readout@4726d80d4bf43fe65133d20d83831752049c8dbe#54c7c9b0-ffbe-11e9-97fb-02163e018d4a'}];
return window.model.environment.parseEnvResult(result).environment.tasks[0].name;
});
assert.strictEqual(tagModified, 'readout');
});
it('should not replace task name due to regex not matching the name (missing tasks/ group)', async () => {
const tagModified = await page.evaluate(() => {
const result = {};
result['environment'] = {};
result.environment.tasks = [{name: 'github.com/AliceO2Group/ControlWorkflows/readout@4726d80d4bf43fe65133d20d83831752049c8dbe#54c7c9b0-ffbe-11e9-97fb-02163e018d4a'}];
return window.model.environment.parseEnvResult(result).environment.tasks[0].name;
});
assert.strictEqual(tagModified, 'github.com/AliceO2Group/ControlWorkflows/readout@4726d80d4bf43fe65133d20d83831752049c8dbe#54c7c9b0-ffbe-11e9-97fb-02163e018d4a');
});

it('should not replace task name due to regex not matching the name (missing @ character)', async () => {
const tagModified = await page.evaluate(() => {
const result = {};
result['environment'] = {};
result.environment.tasks = [{name: 'github.com/AliceO2Group/ControlWorkflows/tasks/readout4726d80d4bf43fe65133d20d83831752049c8dbe#54c7c9b0-ffbe-11e9-97fb-02163e018d4a'}];
return window.model.environment.parseEnvResult(result).environment.tasks[0].name;
});
assert.strictEqual(tagModified, 'github.com/AliceO2Group/ControlWorkflows/tasks/readout4726d80d4bf43fe65133d20d83831752049c8dbe#54c7c9b0-ffbe-11e9-97fb-02163e018d4a');
});
});
});

0 comments on commit e13d5da

Please sign in to comment.