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

Use prototype table component on Environments overview #1254

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const parseColumnsConfiguration = (columns, currentProfile) => {
* @param {TableModels|null} [models] the models that handle the table state
* @returns {vnode} Return the total view of the table to rendered
*/
export const table = (
export const activeColumnsTable = (
data,
columnsConfiguration,
rowsConfiguration = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Inteface for column models. They should have a method that takes
* a row of the table, and returns the data to be displayed in this column
*
* @interface ColumnModel
*/

/**
* Gets the data to display for this column based on a row value
*
* @template T, V
*
* @method
* @name ColumnModel#getValue
* @param {T} row the data element being displayed in this row
* @returns {V} the data corresponding to the value of the row in this column
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* A column that does not modify whatever value is in row[key]
*
* @class
* @implements {ColumnModel}
*/
export class PassThroughColumnmodel {
/**
* Constructor
* @param {string} key the key of the value in the row to access
*/
constructor(key) {
this.key = key;
}

/**
* Gets the value of the row in this column, without modifying it.
* @param {T} row the row of the data to access
* @returns {V} the value of the row in this column
*/
getValue(row) {
return row[this.key];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @template V
* @typedef {(V) => vnode} CellRenderer
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/**
* @typedef {Map<String, CellRenderer} CellRenderers
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @typedef {Object} TableColumn
* @property {String} header
* @property {TableModel} model
* @property {CellRenderer} renderer
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/**
* @typedef {Map<String, ColumnModel>} TableModel
*/
88 changes: 88 additions & 0 deletions lib/public/components/common/newTable/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import spinner from '../spinner.js';
import errorAlert from '../errorAlert.js';
import { noDataRow } from '../activeColumnsTable/noDataRow.js';
import { h, RemoteData } from '/js/src/index.js';

/**
* Renders a row of the table as vnodes
* @param {T} data the data corresponding to this row
* @param {Map<String, TableColumn>} columns A map from a string used as the vnode key for this column, a TableColumn model
* @param {(T) => String} getRowKey A function that takes a row of the data and returns a string used for the vnode key of this row.
* @param {null|(T) => String} getRowClasses Optionally a function that takes a row of the data and returns css classes for this row
* @param {null|(T) => Object} getRowConfig Optionally a function that takes a row of the data and returns a config object passed to h()
* @returns {vnode} a vnode corresponding to a row of this table
*/
const tableRow = (row, columns, getRowKey, getRowClasses, getRowConfig) => {
const rowKey = getRowKey(row);
const rowClasses = getRowClasses ? getRowClasses(row) : '';
const rowConfig = getRowConfig ? getRowConfig(row) : {};

return h(`tr.${rowClasses}`, { key: rowKey, ...rowConfig }, Object.entries(columns).map(([columnKey, column]) => {
const cellData = column.model.getValue(row);
const cellKey = `${rowKey}-${columnKey}`;
return h('td', { key: cellKey }, column.renderer(cellData));
}));
};

/**
* Renders the headers of the table.
* @param {Map<String, TableColumn>} columns A map from a string used as the vnode key for this column, a TableColumn model
* @returns {vnode} a table row containing the headers of the table.
*/
const tableHeaders = (columns) => h('thead', h('tr', Object.values(columns).map(({ header }) => h('th', header))));

/**
* Renders a table body using an Array
* @param {T[]} data array of data to display or RemoteData of the same type
* @param {Map<String, TableColumn>} columns A map from a string used as the vnode key for this column, a TableColumn model
* @param {(T) => String} getRowKey A function that takes a row of the data and returns a string used for the vnode key of this row.
* @param {null|(T) => String} getRowClasses Optionally a function that takes a row of the data and returns css classes for this row
* @param {null|(T) => Object} getRowConfig Optionally a function that takes a row of the data and returns a config object passed to h()
* @returns {vnode} the vnode corresponding to the body of the table displaying this data
*/
const tableBodyArray =
(data, columns, getRowKey, getRowClasses, getRowConfig) => data.map((row) => tableRow(row, columns, getRowKey, getRowClasses, getRowConfig));

/**
* Renders a table body using RemoteData
* @param {RemoteData<T[]>} remoteData a remote data object for the array of data
* @param {Map<String, TableColumn>} columns A map from a string used as the vnode key for this column, a TableColumn model
* @param {(T) => String} getRowKey A function that takes a row of the data and returns a string used for the vnode key of this row.
* @param {null|(T) => String} getRowClasses Optionally a function that takes a row of the data and returns css classes for this row
* @param {null|(T) => Object} getRowConfig Optionally a function that takes a row of the data and returns a config object passed to h()
* @returns {vnode} the vnode corresponding to the body of the table displaying this data
*/
const tableBodyRemoteData = (remoteData, columns, getRowKey, getRowClasses, getRowConfig) => {
const columnsCount = Object.keys(columns).length;

return remoteData.match({
NotAsked: () => noDataRow(columnsCount),
Loading: () => h(
'tr',
h(
'td',
{ colSpan: columnsCount },
spinner({ size: 5, absolute: false }),
),
),
Success: (payload) => tableBodyArray(payload, columns, getRowKey, getRowClasses, getRowConfig),
Failure: (errors) => h('tr', h('td', { colSpan: columnsCount }, errors.map(errorAlert))),
});
};

/**
* Renders a table containing data
* @param {T[]|RemoteData<T[]>} data array of data to display or RemoteData of the same type
* @param {Map<String, TableColumn>} columns A map from a string used as the vnode key for this column, a TableColumn model
* @param {(T) => String} getRowKey A function that takes a row of the data and returns a string used for the vnode key of this row.
* @param {null|(T) => String} getRowClasses Optionally a function that takes a row of the data and returns css classes for this row
* @param {null|(T) => Object} getRowConfig Optionally a function that takes a row of the data and returns a config object passed to h()
* @returns {vnode} the vnode corresponding to the table displaying this data
*/
export const table = (data, columns, getRowKey, getRowClasses = null, getRowConfig = null) => {
const tableBody = data instanceof RemoteData ? tableBodyRemoteData : tableBodyArray;
return h('table.table', [
tableHeaders(columns),
tableBody(data, columns, getRowKey, getRowClasses, getRowConfig),
]);
};
2 changes: 1 addition & 1 deletion lib/public/utilities/applyProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* or submit itself to any jurisdiction.
*/

import { profiles } from '../components/common/table/profiles.js';
import { profiles } from '../components/common/activeColumnsTable/profiles.js';

/**
* Apply the given profile on the given subject, and return the resulting subject or null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { h } from '/js/src/index.js';
import spinner from '../../../components/common/spinner.js';
import errorAlert from '../../../components/common/errorAlert.js';
import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js';
import { table } from '../../../components/common/table/table.js';
import { activeColumnsTable } from '../../../components/common/activeColumnsTable/activeColumnsTable.js';
import { runsActiveColumns } from '../../Runs/ActiveColumns/runsActiveColumns.js';
import { coloredEnvironmentStatusComponent } from '../ColoredEnvironmentStatusComponent.js';
import { frontLink } from '../../../components/common/navigation/frontLink.js';
Expand Down Expand Up @@ -83,8 +83,8 @@ export const EnvironmentDetailsPage = ({ envs: { detailsModel } }) => detailsMod
[ENVIRONMENT_DETAILS_PANELS_KEYS.LOGS]: 'Logs',
},
{
[ENVIRONMENT_DETAILS_PANELS_KEYS.RUNS]: () => table(runs, runsActiveColumns, null, { profile: 'environment' }),
[LHC_FILL_DETAILS_PANELS_KEYS.LOGS]: (logs) => table(logs, logsActiveColumns, null, { profile: 'embeded' }),
[ENVIRONMENT_DETAILS_PANELS_KEYS.RUNS]: () => activeColumnsTable(runs, runsActiveColumns, null, { profile: 'environment' }),
[LHC_FILL_DETAILS_PANELS_KEYS.LOGS]: (logs) => activeColumnsTable(logs, logsActiveColumns, null, { profile: 'embeded' }),
},
),
]);
Expand Down
123 changes: 120 additions & 3 deletions lib/public/views/Environments/Overview/EnvironmentOverviewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import { PaginationModel } from '../../../components/Pagination/PaginationModel.js';
import { Observable, RemoteData } from '/js/src/index.js';
import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice.js';
import { PassThroughColumnmodel } from '../../../components/common/newTable/columnModels/passThroughColumnModel.js';

/**
* Environment overview page model
Expand All @@ -30,6 +31,8 @@ export class EnvironmentOverviewModel extends Observable {
this._pagination.itemsPerPageSelector$.observe(() => this.notify());

this._environments = RemoteData.NotAsked();

this._environmentTableModel = new EnvironmentTableModel();
}

/**
Expand Down Expand Up @@ -68,7 +71,7 @@ export class EnvironmentOverviewModel extends Observable {
/**
* Reste the model state to its default values
*
* @return {void}
* @returns {void}
*/
reset() {
this._environments = RemoteData.notAsked();
Expand All @@ -77,7 +80,7 @@ export class EnvironmentOverviewModel extends Observable {
/**
* Returns the pagination model
*
* @return {PaginationModel} the pagination model
* @returns {PaginationModel} the pagination model
*/
get pagination() {
return this._pagination;
Expand All @@ -86,9 +89,123 @@ export class EnvironmentOverviewModel extends Observable {
/**
* Returns the current environments list as remote data
*
* @return {RemoteData} the environments list
* @returns {RemoteData} the environments list
*/
get environments() {
return this._environments;
}

/**
* Returns the environments table model
*
* @returns {EnvironmentTableModel} the environments table model
*/
get environmentTableModel() {
return this._environmentTableModel;
}
}

/**
* The Environment Table Model controls how environments data is modified
* for use in a data table component.
*/
class EnvironmentTableModel {
/**
* Constructor which defines models for each column to display
*/
constructor() {
this._id = new PassThroughColumnmodel('id');
this._updatedAt = new PassThroughColumnmodel('updatedAt');
this._createdAt = new PassThroughColumnmodel('createdAt');
this._status = new EnvironmentStatusColumnModel('status');
this._statusMessage = new PassThroughColumnmodel('statusMessage');
this._runs = new PassThroughColumnmodel('runs');
}

/**
* Returns the column model for the environment id
* @returns {ColumnModel} the column model for the environment id
*/
get id() {
return this._id;
}

/**
* Returns the column model for the environment updatedAt time
* @returns {ColumnModel} the column model for the environment updatedAt time
*/
get updatedAt() {
return this._updatedAt;
}

/**
* Returns the column model for the environment createdAt time
* @returns {ColumnModel} the column model for the environment createdAt time
*/
get createdAt() {
return this._createdAt;
}

/**
* Returns the column model for the environment status
* @returns {ColumnModel} the column model for the environment status
*/
get status() {
return this._status;
}

/**
* Returns the column model for the environment status message
* @returns {ColumnModel} the column model for the environment status message
*/
get statusMessage() {
return this._statusMessage;
}

/**
* Returns the column model for the environment runs
* @returns {ColumnModel} the column model for the environment runs
*/
get runs() {
return this._runs;
}
}

/**
* A custom column model for environments which determines the data contained in the env
* in the environment status column.
*
* @class
* @implements {ColumnModel}
*/
class EnvironmentStatusColumnModel {
/**
* Constructor
*/
constructor() {}

/**
* Returns a message to be displayed in a tooltip based on the environment status
* @param {V} status the status of the environment
* @returns {string} a tooltip message dependent on the status
*/
_getTooltipMessage(status) {
if (status === 'ERROR') {
return 'Environment has been in ERROR state';
}
if (status === 'DANGER') {
return 'Environment has been in ERROR state';
}
return null;
}

/**
* Gets the status and the history items used to display this column
* @param {T} row the environment being displayed in this row
* @returns {Object} an object containing the envrionment status and history items.
*/
getValue(row) {
const { status, historyItems } = row;
return { status, historyItems };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ export const EnvironmentOverviewPage = ({ envs }) => h(
{
onremove: () => envs.clearOverview(),
},
environmentOverviewComponent(envs.overviewModel.pagination, envs.overviewModel.environments),
environmentOverviewComponent(envs.overviewModel.pagination, envs.overviewModel.environments, envs.overviewModel.environmentTableModel),
);
Loading