Skip to content

Commit

Permalink
Generation / Executions UI with many data sources
Browse files Browse the repository at this point in the history
Fixes #2776

All Generation/Execution sources lists now work as datatable:
-- user can filter sources by name
-- datatables display items (sources) count and add pagination when necessary
Added checkbox to hide sources without generation/execution results
Two types of generation/execution tables
-- for Characterizations, Pathways, PLP and PLE (with results versioning)
-- for Cohorts and Incidence Rates (without versioning, displaying results in the table)
  • Loading branch information
anton-abushkevich authored Jan 25, 2023
1 parent 677e1b8 commit 9649e95
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 82 deletions.
24 changes: 13 additions & 11 deletions js/components/analysisExecution/analysis-execution-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
<loading data-bind="css: classes('loading-panel'), visible: loading" params="status: ko.i18n('common.loadingWithDots', 'Loading...')"></loading>
<div data-bind="visible: !loading()">
<h2 data-bind="css: classes('title'), text: ko.i18n('components.analysisExecution.title', 'Executions')"></h2>
<label data-bind="css: classes('only-results-checkbox')"><input type="checkbox" data-bind="checked: showOnlySourcesWithResults"> Show only sources with results</label>
<div data-bind="css: classes('content')">
<!-- ko foreach: executionGroups -->
<div data-bind="template: {
name: 'execution-group',
data: Object.assign({}, $data, {
classes: $parent.classes,
execColumns: $parent.execColumns,
isExpanded: $parent.expandedSection() === $index(),
toggleSection: () => $parent.toggleSection($index()),
})
}"></div>
<!-- /ko -->
<table data-bind="
css: classes('execution-groups-table'),
dataTable: {
data: filteredExecutionGroups,
options: {
columns: sourcesColumn,
order: [[ 0, 'asc' ]],
pageLength: tableOptions.pageLength,
language: ko.i18n('datatable.language')
}
}
"></table>
</div>
</div>
</div>
Expand Down
47 changes: 27 additions & 20 deletions js/components/analysisExecution/analysis-execution-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ define([
this.pollId = null;
this.PollService = PollService;
this.selectedSourceId = selectedSourceId;
this.selectedSourceId.subscribe(() => this.expandSelectedSource());
this.tableOptions = tableOptions || CommonUtils.getTableOptions('S');
this.loading = ko.observable(false);
this.downloading = ko.observableArray();
Expand All @@ -75,17 +74,37 @@ define([
this.executionStatuses.RUNNING,
this.executionStatuses.STARTED,
];
this.expandedSection = ko.observable();

this.isExitMessageShown = ko.observable(false);
this.exitMessage = ko.observable();

this.executionResultMode = executionResultMode;
this.executionGroups = ko.observableArray([]);
this.showOnlySourcesWithResults = ko.observable(false);
this.filteredExecutionGroups = ko.pureComputed(() => {
return this.showOnlySourcesWithResults()
? this.executionGroups().filter(eg => eg.submissions().length > 0)
: this.executionGroups();
});
this.executionResultModes = consts.executionResultModes;
this.isExecutionDesignShown = ko.observable(false);
this.executionDesign = ko.observable(null);

this.sourcesColumn = [{
render: (s, p, d) => {
return `<span>${d.sourceName}</span><span data-bind="template: {
name: 'execution-group',
data: {
...$data,
classes: $parent.classes,
execColumns: $parent.execColumns,
isExpanded: $parent.selectedSourceId() == $data.sourceId,
toggleSection: () => $parent.toggleSection($data.sourceId)
}
}"></span>`;
}
}];

this.stopping = ko.observable({});
this.isSourceStopping = (source) => this.stopping()[source.sourceKey];

Expand Down Expand Up @@ -172,24 +191,15 @@ define([

group.submissions(executionList.filter(({ sourceKey: exSourceKey }) => exSourceKey === sourceKey));
this.setExecutionGroupStatus(group);
})
this.expandSelectedSource();
});
this.executionGroups.valueHasMutated();
} catch (err) {
console.error(err);
} finally {
this.loading(false);
}
}

expandSelectedSource() {
let idx = null;
if (this.selectedSourceId()) {
const sourceId = parseInt(this.selectedSourceId());
idx = this.executionGroups().findIndex(g => g.sourceId === sourceId);
}
this.expandedSection(idx);
}

async showExecutionDesign(executionId) {
try {
this.executionDesign(null);
Expand Down Expand Up @@ -217,16 +227,13 @@ define([
}
}

toggleSection(idx) {
if (this.expandedSection() === idx) {
this.expandedSection(null);
toggleSection(sourceId) {
if (parseInt(this.selectedSourceId()) === sourceId) {
this.selectedSourceId(null);
CommonUtils.routeTo(`${this.resultsPathPrefix}${this.analysisId()}/executions`);
} else {
this.expandedSection(idx);
const executionGroup = this.executionGroups()[idx];
this.selectedSourceId(executionGroup.sourceId);
CommonUtils.routeTo(`${this.resultsPathPrefix}${this.analysisId()}/executions/${executionGroup.sourceId}`);
this.selectedSourceId(sourceId);
CommonUtils.routeTo(`${this.resultsPathPrefix}${this.analysisId()}/executions/${sourceId}`);
}
}

Expand Down
24 changes: 22 additions & 2 deletions js/components/analysisExecution/analysis-execution-list.less
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,31 @@
}

&__content {
margin-top: 1.5rem;
margin-top: 1rem;
}

&__only-results-checkbox {
margin-top: 5px;
}

&__execution-groups-table {
&.dataTable.no-footer {
border: none !important;
thead {
display: none;
}
td {
padding: 0;

& > span:first-child {
display: none;
}
}
}
}

&__group {
margin-top: 1rem;
margin: .4rem 0;

&--expanded {
.analysis-execution-list {
Expand Down
24 changes: 24 additions & 0 deletions js/pages/cohort-definitions/cohort-definition-manager.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,28 @@
color: #265a88;
cursor: pointer;
}

.generation-container {
margin: 0 6px;
}
.generation-heading {
font-size: 1.8rem;
color: #333;
font-weight: 500;
margin: 12px 0 3px 0;
}
.only-results-checkbox {
}
table.sources-table td.generation-buttons-column {
text-align: right;
}
table.sources-table thead th, table.sources-table tbody tr:first-child td {
padding: 4px 10px;
}
table.sources-table tbody td {
padding: 0 10px 4px;
}
.generation-buttons {
white-space: nowrap;
}

129 changes: 86 additions & 43 deletions js/pages/cohort-definitions/cohort-definition-manager.html
Original file line number Diff line number Diff line change
Expand Up @@ -245,31 +245,47 @@ <h3 data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.a
</div>
</div>
<div role="tabpanel" data-bind="css: { active: $component.tabMode() == 'generation' }" class="tab-pane">
<div class="heading"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.availableCDMSources', 'Available CDM Sources')">
<div class="generation-container">
<div class="generation-heading"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generation', 'Generation')">
</div>
<label class="only-results-checkbox"><input type="checkbox" data-bind="checked: showOnlySourcesWithResults"> Show only sources with results</label>

<table class="sources-table" style="width: 100%" data-bind="
dataTable: {
data: showOnlySourcesWithResults() ? cohortDefinitionSourceInfo().filter(c => c.isValid()) : cohortDefinitionSourceInfo,
options: {
columns: sourcesColumns,
order: [[ 0, 'asc' ]],
pageLength: sourcesTableOptions.pageLength,
language: ko.i18n('datatable.language')
}
}
"></table>
</div>
<table class="cohort-generate-sources">

<table class="cohort-generate-sources" style="display: none">
<thead>
<th></th>
<th data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.sourceName', 'Source Name')"></th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generationStatus', 'Generation Status')"></th>
<th class="text-right nowrap" data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.people', 'People')">
</th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.records', 'Records')"></th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generated', 'Generated')"></th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generationDuration', 'Generation Duration')">
</th>
<th></th>
<th></th>
<th data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.sourceName', 'Source Name')"></th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generationStatus', 'Generation Status')"></th>
<th class="text-right nowrap" data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.people', 'People')">
</th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.records', 'Records')"></th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generated', 'Generated')"></th>
<th class="text-right nowrap"
data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generationDuration', 'Generation Duration')">
</th>
<th></th>
</thead>
<tbody
data-bind="foreach:cohortDefinitionSourceInfo, css: {overflow: cohortDefinitionSourceInfo().length > 3, 'max-height': $component.selectedReportSource()}">
<tr
data-bind="foreach:cohortDefinitionSourceInfo, css: {overflow: cohortDefinitionSourceInfo().length > 3, 'max-height': $component.selectedReportSource()}">
<tr
data-bind="css: { 'selected': $component.selectedReportSource() && $component.selectedReportSource().sourceKey == $data.sourceKey}">
<td>
<td>
<span data-bind="tooltip: $component.isNew() ? 'You must save the current cohort definition before you can perform generation' : !$component.hasAccessToGenerate($data.sourceKey) ? 'Not enough permissions to generate' : null" data-placement="right">
<span data-bind="tooltip: !$component.canGenerate() ? $component.generateDisabledReason() : null" data-placement="right">
<button class="btn btn-sm btn-primary"
Expand All @@ -280,34 +296,34 @@ <h3 data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.a
</button>
</span>
</span>
<a role="button" class="btn btn-sm btn-danger" data-bind="click:$component.cancelGenerate,
<a role="button" class="btn btn-sm btn-danger" data-bind="click:$component.cancelGenerate,
visible: $component.isSourceRunning($data),
attr: {disabled: $component.isCancelDisabled($data)}"><i class="fa fa-spinner fa-spin"></i> <span data-bind="text: ko.i18n('common.cancel', 'Cancel')"></span>
</a>
</td>
<td class="nowrap" data-bind="text:name"></td>
<td class="statusIndicator text-right nowrap">
</a>
</td>
<td class="nowrap" data-bind="text:name"></td>
<td class="statusIndicator text-right nowrap">
<span
data-bind="template: { name: $component.getStatusTemplate, data: { item: $data, status: $component.getStatusMessage($data)} }"></span>
</td>
<td class="text-right nowrap" data-bind="html: $component.renderCountColumn($data.personCount)">
</td>
<td class="text-right nowrap" data-bind="html: $component.renderCountColumn($data.recordCount)">
</td>
<td class="text-right nowrap" data-bind="text: startTime">
</td>
<td class="text-right nowrap" data-bind="text: executionDuration">
</td>
<td>
<div class="btn btn-sm btn-primary"
data-bind="visible:!$component.isSourceRunning($data) && isValid(), click:$component.toggleCohortReport">
<i class="fa fa-eye"></i>
<span data-bind="text: (($component.selectedReportSource() && $component.selectedReportSource().sourceKey === $data.sourceKey)
data-bind="template: { name: $component.getStatusTemplate, data: { item: $data, status: $component.getStatusMessage($data)} }"></span>
</td>
<td class="text-right nowrap" data-bind="html: $component.renderCountColumn($data.personCount)">
</td>
<td class="text-right nowrap" data-bind="html: $component.renderCountColumn($data.recordCount)">
</td>
<td class="text-right nowrap" data-bind="text: startTime">
</td>
<td class="text-right nowrap" data-bind="text: executionDuration">
</td>
<td>
<div class="btn btn-sm btn-primary"
data-bind="visible:!$component.isSourceRunning($data) && isValid(), click:$component.toggleCohortReport">
<i class="fa fa-eye"></i>
<span data-bind="text: (($component.selectedReportSource() && $component.selectedReportSource().sourceKey === $data.sourceKey)
? ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.hideReports', 'Hide Reports')
: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.viewReports', 'View Reports'))"></span>
</div>
</td>
</tr>
</div>
</td>
</tr>
</tbody>
</table>
<!-- ko if: selectedReportSource() -->
Expand Down Expand Up @@ -747,4 +763,31 @@ <h3 data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.a
<a href='#' data-bind="css: $component.classes('status-link'), click: () => $component.showExitMessage($data.item.sourceKey), text: $component.getStatusMessageTranslated($data.status)"></a>
</script>

<script type="text/html" id="generation-buttons">
<div class="generation-buttons">
<span data-bind="tooltip: $component.isNew() ? 'You must save the current cohort definition before you can perform generation' : !$component.hasAccessToGenerate(sourceKey) ? 'Not enough permissions to generate' : null" data-placement="left">
<span data-bind="tooltip: !$component.canGenerate() ? $component.generateDisabledReason() : null" data-placement="left">
<button class="btn btn-sm btn-primary"
data-bind="attr: {'disabled':!$component.canGenerate() || !$component.hasAccessToGenerate(sourceKey)},
visible: !$component.isSourceRunning($data),
click: (data, event) => $component.generateCohort(data)">
<i class="fa fa-play"></i>&nbsp;<span data-bind="text: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generate', 'Generate')"></span>
</button>
</span>
</span>
<a role="button" class="btn btn-sm btn-danger"
data-bind="click:$component.cancelGenerate,
visible: $component.isSourceRunning($data),
attr: {disabled: $component.isCancelDisabled($data)}"><i class="fa fa-spinner fa-spin"></i> <span data-bind="text: ko.i18n('common.cancel', 'Cancel')"></span>
</a>
<button class="btn btn-sm btn-primary"
data-bind="attr: {disabled: $component.isSourceRunning($data) || !isValid()}, click:$component.toggleCohortReport">
<i class="fa fa-eye"></i>
<span data-bind="text: (($component.selectedReportSource() && $component.selectedReportSource().sourceKey === sourceKey)
? ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.hideReports', 'Hide Reports')
: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.viewReports', 'View Reports'))"></span>
</button>
</div>
</script>

<!-- /ko -->
27 changes: 27 additions & 0 deletions js/pages/cohort-definitions/cohort-definition-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,32 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
]
};


this.sourcesTableOptions = commonUtils.getTableOptions('S');
this.sourcesColumns = [{
title: `<span>${ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.sourceName', 'Source Name')()}</span>`,
data: 'name'
}, {
title: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generationStatus', 'Generation Status'),
data: 'status'
}, {
title: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.people', 'People'),
data: 'personCount'
}, {
title: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.records', 'Records'),
data: 'recordCount'
}, {
title: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generated', 'Generated'),
data: 'startTime'
}, {
title: ko.i18n('cohortDefinitions.cohortDefinitionManager.panels.generationDuration', 'Generation Duration'),
data: 'executionDuration'
}, {
sortable: false,
className: 'generation-buttons-column',
render: () => `<span data-bind="template: { name: 'generation-buttons', data: $data }"></span>`
}];

this.stopping = ko.pureComputed(() => this.cohortDefinitionSourceInfo().reduce((acc, target) => ({...acc, [target.sourceKey]: ko.observable(false)}), {}));
this.isSourceStopping = (source) => this.stopping()[source.sourceKey];

Expand Down Expand Up @@ -654,6 +680,7 @@ define(['jquery', 'knockout', 'text!./cohort-definition-manager.html',
this.currentJob = ko.observable();
this.reportingSourceStatusAvailable = ko.observable(false);
this.reportingSourceStatusLoading = ko.observable(false);
this.showOnlySourcesWithResults = ko.observable(false);
this.isGenerated = ko.observable(false);
this.reportOptionCaption = ko.pureComputed(() => {
return this.reportingSourceStatusLoading()
Expand Down
Loading

0 comments on commit 9649e95

Please sign in to comment.