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

Generation / Executions UI with many data sources #2776 #2789

Merged
merged 3 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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