Skip to content
Open
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
8 changes: 7 additions & 1 deletion js/components/authorship.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
<span data-bind="visible: modifiedDate,
text: ko.i18nformat('components.authorship.modifiedLabel',
'modified by <%=modifiedBy%> on <%=modifiedDate%>',
{modifiedBy: modifiedBy, modifiedDate: modifiedDate})"></span>
{modifiedBy: modifiedBy, modifiedDate: modifiedDate})"></span></span><span data-bind="visible: validatedBy">,</span>
<span data-bind="visible: validatedBy">
<span data-bind="text: ko.i18n('components.authorship.validated', 'validated')"></span>
<span data-bind="visible: validatedBy, text: ko.i18nformat('components.authorship.byValidated', 'by <%=validatedBy%>', {validatedBy: validatedBy})"></span>
<span data-bind="text: ko.i18n('components.authorship.on', 'on')"></span>
<span data-bind="text: validatedDate"></span>
</span>
</div>
<!-- /ko -->
2 changes: 2 additions & 0 deletions js/components/authorship.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ define([
this.createdDate = params.createdDate;
this.modifiedBy = params.modifiedBy;
this.modifiedDate = params.modifiedDate;
this.validatedBy = params.validatedBy;
this.validatedDate = params.validatedDate;
}
}

Expand Down
126 changes: 119 additions & 7 deletions js/components/circe/components/ConceptSetBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ define([
'services/AuthAPI',
'utils/DatatableUtils',
'utils/CommonUtils',
'services/ConceptSet',
'components/ac-access-denied',
'databindings',
'css!./style.css'
], function (ko, template, VocabularyProvider, appConfig, ConceptSet, authApi, datatableUtils, commonUtils) {
], function (ko, template, VocabularyProvider, appConfig, ConceptSet, authApi, datatableUtils, commonUtils, conceptSetService) {
function CohortConceptSetBrowser(params) {
var self = this;

Expand Down Expand Up @@ -59,7 +60,6 @@ define([
});
}


function setDisabledConceptSetButton(action) {
if (action && action()) {
return action()
Expand All @@ -68,6 +68,30 @@ define([
}
}

// Helper function to get the latest version approval for a concept set
// approvalsByVersion structure: { version: ReviewActionDTO }
function getLatestVersionApproval(approvalsByVersion) {
if (!approvalsByVersion || typeof approvalsByVersion !== 'object') {
return null;
}

// Get all version numbers and find the maximum
const versions = Object.keys(approvalsByVersion).map(v => parseInt(v));
if (versions.length === 0) {
return null;
}

const maxVersion = Math.max(...versions);
const latestVersionApproval = approvalsByVersion[maxVersion];

if (!latestVersionApproval) {
return null;
}

// Only return if it's an APPROVE type (not REVOKE)
return latestVersionApproval.type === 'APPROVE' ? latestVersionApproval : null;
}

self.datatableUtils = datatableUtils;
self.criteriaContext = params.criteriaContext;
self.cohortConceptSets = params.cohortConceptSets;
Expand Down Expand Up @@ -101,12 +125,51 @@ define([
.done(function (results) {
datatableUtils.coalesceField(results, 'modifiedDate', 'createdDate');
datatableUtils.addTagGroupsToFacets(results, self.options.Facets);
datatableUtils.addTagGroupsToColumns(results, self.columns);
self.repositoryConceptSets(results);
self.loading(false);

const conceptSetIds = results.map(cs => cs.id);

// Enrich with approval status and additional fields for the table
conceptSetService.getApprovalInfoBatch(conceptSetIds)
.then(approvalMap => {
// approvalMap structure: { conceptSetId: { version: ReviewActionDTO } }
results.forEach(conceptSet => {
try {
// Get all approvals for this concept set (by version)
const approvalsByVersion = approvalMap[conceptSet.id] || {};

// Get the approval for the LATEST VERSION
const latestVersionApproval = getLatestVersionApproval(approvalsByVersion);

conceptSet.isApproved = !!latestVersionApproval;
conceptSet.approver = latestVersionApproval ? latestVersionApproval.user.name : null;
conceptSet.approvalDate = latestVersionApproval ? latestVersionApproval.timestamp : null;
} catch (error) {
console.error(`Error processing approval info for concept set ${conceptSet.id}:`, error);
// Set default values if there's an error
conceptSet.isApproved = false;
conceptSet.approver = null;
conceptSet.approvalDate = null;
}
});

self.repositoryConceptSets(results);
datatableUtils.addTagGroupsToColumns(results, self.columns);
self.loading(false);
}).catch(error => {
console.error('Error while batch-fetching approval info for concept sets', error);
results.forEach(conceptSet => {
conceptSet.isApproved = false;
conceptSet.approver = null;
conceptSet.approvalDate = null;
});
self.repositoryConceptSets(results);
datatableUtils.addTagGroupsToColumns(results, self.columns);
self.loading(false);
});
})
.fail(function (err) {
console.log(err);
console.log('Error fetching concept sets:', err);
self.loading(false);
});
}

Expand Down Expand Up @@ -146,10 +209,45 @@ define([
'caption': ko.i18n('facets.caption.designs', 'Designs'),
'binding': datatableUtils.getFacetForDesign,
},
{
'caption': ko.i18n('facets.caption.validated', 'Validated'),
'binding': (o) => {
return o.isApproved ? ko.i18n('common.yes', 'Yes')() : ko.i18n('common.no', 'No')();
}
},
{
'caption': ko.i18n('facets.caption.validatedBy', 'Validated By'),
'binding': (o) => {
return o.approver || ko.i18n('common.notValidated', 'Not Validated')();
}
},
{
'caption': ko.i18n('facets.caption.validatedDate', 'Validated Date'),
'binding': (o) => {
if (!o.approvalDate) {
return ko.i18n('common.notValidated', 'Not Validated')();
}
return datatableUtils.getFacetForDate(o.approvalDate);
}
},
]
};

this.columns = ko.observableArray([
{
title: '',
data: 'isApproved',
sortable: false,
defaultContent: '',
createdCell: function (td, cellData, rowData, row, col) {
if (cellData) {
$(td).html('<i class="fa fa-check-circle" style="font-size:15px;color:green" title="Validated"></i>');
} else {
$(td).html('');
}
},
width: '20px',
},
{
title: ko.i18n('columns.id', 'Id'),
data: 'id'
Expand All @@ -172,6 +270,20 @@ define([
{
title: ko.i18n('columns.author', 'Author'),
render: datatableUtils.getCreatedByFormatter(),
},
{
title: ko.i18n('columns.validatedBy', 'Validated By'),
data: 'approver',
render: function (data, type, row) {
return data || '-';
}
},
{
title: ko.i18n('columns.validatedDate', 'Validation Date'),
data: 'approvalDate',
render: function (data, type, row) {
return data ? datatableUtils.getDateFieldFormatter('approvalDate')({approvalDate: data}, type, row) : '-';
}
}
]);

Expand All @@ -186,4 +298,4 @@ define([
};

return component;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</div>
<faceted-datatable params="
dataTableId: repositoryConceptSetTableId,
orderColumn: 3,
orderColumn: 4,
reference: repositoryConceptSets(),
columns: columns,
options: options,
Expand Down
19 changes: 15 additions & 4 deletions js/components/versions/versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,17 @@ function (
this.editVersion = ko.observable();
this.comment = ko.observable();
this.isCommentModalShown = ko.observable(false);
this.extraColumns = params.extraColumns || ko.observableArray([]);

if (params.refreshTrigger) {
this.subscriptions.push(
params.refreshTrigger.subscribe(() => {
this.loadData();
})
);
}

this.columns = [
this.columns = ko.pureComputed(() => [
{
title: ko.i18n('columns.version', 'Version'),
data: 'version'
Expand Down Expand Up @@ -93,8 +102,9 @@ function (
return `<a data-bind="css: '${this.classes('action-link')}', click: copy, text: ko.i18n('components.versions.createACopy', 'Create a copy'), title: ko.i18n('components.versions.createNewAsset', 'Create new asset from this version')"></a>`

}
}
];
},
...ko.unwrap(this.extraColumns())
]);
this.options = {
Facets: [
{
Expand All @@ -104,7 +114,8 @@ function (
{
caption: ko.i18n('facets.caption.author', 'Author'),
binding: datatableUtils.getFacetForCreatedBy
}
},
...(params.extraFacets || [])
],
};
this.tableOptions = params.tableOptions || commonUtils.getTableOptions('L');
Expand Down
36 changes: 36 additions & 0 deletions js/pages/concept-sets/components/modal/validate-comment-modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<atlas-modal params="data: $component, showModal: $component.isModalShown, title: 'Validate concept set'">
<div class="modal-body" data-bind="visible: true">
<!-- Delegate Reviewer Section -->
<div class="form-group">
<label class="modal-label">Reviewer</label>
<select class="form-control" data-bind="options: delegateReviewers,
optionsText: 'nameWithLogin',
optionsValue: 'id',
value: selectedApproverId,
valueAllowUnset: true,
enable: !loadingReviewers()">
</select>
<small class="form-text text-muted">Optional: Select a representative reviewer</small>
</div>

<!-- Validation Comment Section -->
<div class="form-group">
<label class="modal-label">Validation Comment</label>
<textarea class="form-control" data-bind="textInput: validationComment, enable: true" rows="1"
placeholder="Enter your validation comment..."></textarea>
</div>

<!-- Supporting Information Section -->
<div class="form-group">
<label class="modal-label">Supporting Information</label>
<textarea class="form-control" data-bind="textInput: supportingInfo, enable: true" rows="10"
placeholder="Enter approval supporting information..."></textarea>
</div>

<!-- Action Buttons -->
<div class="modal-actions">
<button class="btn btn-success" data-bind="click: () => confirm(), enable: canValidate"> Validate</button>
<button class="btn btn-default" data-bind="click: () => cancel()"> Cancel</button>
</div>
</div>
</atlas-modal>
92 changes: 92 additions & 0 deletions js/pages/concept-sets/components/modal/validate-comment-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
define([
'knockout',
'text!./validate-comment-modal.html',
'components/Component',
'utils/CommonUtils',
'utils/AutoBind',
'services/ConceptSet',
'services/AuthAPI',
'less!./validate-comment-modal.less',
'databindings',
], function (
ko,
view,
Component,
commonUtils,
AutoBind,
conceptSetService,
authApi,
) {
class ValidateCommentModal extends AutoBind(Component) {
constructor(params) {
super(params);
this.isModalShown = params.isModalShown;
this.onConfirm = params.onConfirm;

this.validationComment = ko.observable('');
this.selectedApproverId = ko.observable(null);

this.supportingInfo = ko.observable(params.supportingInfo || '');

this.delegateReviewers = ko.observableArray([]);
this.loadingReviewers = ko.observable(false);

this.canValidate = ko.computed(() => {
return this.validationComment() && this.validationComment().trim().length > 0;
});

this.isModalShown.subscribe((shown) => {
if (shown) {
this.resetForm();
this.loadDelegateReviewers();
}
});
}

async loadDelegateReviewers() {
this.loadingReviewers(true);
try {
const approvers = await conceptSetService.listApprovers('conceptset');
const formattedApprovers = approvers.data.map(approver => ({
...approver,
nameWithLogin: `${approver.name} (${approver.login})`,
}));

this.delegateReviewers(formattedApprovers);

const currentUser = formattedApprovers.find(a => a.id === authApi.id());
if (currentUser) {
this.selectedApproverId(currentUser.id);
}
} catch (error) {
console.error('Failed to load delegate reviewers:', error);
this.delegateReviewers([]);
} finally {
this.loadingReviewers(false);
}
}

resetForm() {
this.validationComment('');
this.supportingInfo(null);
}

confirm() {
const validationData = {
comment: this.validationComment(),
approverId: this.selectedApproverId(),
supportingInfo: this.supportingInfo()
};

this.onConfirm(validationData);
this.isModalShown(false);
}

cancel() {
this.resetForm();
this.isModalShown(false);
}
}

return commonUtils.build('validate-comment-modal', ValidateCommentModal, view);
});
Loading
Loading