Skip to content

Commit

Permalink
GDB-9074: When only one Query and Update is running in the UI is not …
Browse files Browse the repository at this point in the history
…displayed

What
When there are ongoing rebuild similarity index. The global operation component is displayed without icon and count of the ongoing operation.

Why
The issue arose after a code refactoring that removed the summary object for all operations, done for optimization. To display ongoing operations, the UI relies on an array returned from the backend, which contains information about icons and the count of ongoing operations per operation group. The problem occurred because "queries" and "imports" are part of the same operation group, and they share the same icon and count (the sum of both operations). This resulted in double-displaying the "query" group icon and count, causing the update query to be skipped for visualization. Rebuilding the similarity index triggers only one update operation, which is why the global operation component is displayed without an icon (it's skipped).

How
Revert the removal of the 'OperationGroupSummary' object and related functionality.

Additional work:
 - added a test to cover this scenario.
  • Loading branch information
boyan-tonchev committed Nov 2, 2023
1 parent c5d0eb3 commit a8901b5
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {OPERATION_GROUP_TYPE} from "../../../models/monitoring/operations/operation-group";
import {OPERATION_STATUS} from "../../../models/monitoring/operations/operation-status";
import {OPERATION_STATUS, OPERATION_STATUS_SORT_ORDER} from "../../../models/monitoring/operations/operation-status";
import {
OPERATION_TYPE,
OPERATION_TYPE_SORT_ORDER
Expand All @@ -25,6 +25,7 @@ function operationsStatusesMonitorDirectives($interval, $repositories, Monitorin
scope.OPERATION_STATUS = OPERATION_STATUS;
scope.OPERATION_GROUP_TYPE = OPERATION_GROUP_TYPE;
scope.OPERATION_TYPE = OPERATION_TYPE;
scope.operationsSummary = undefined;
scope.activeOperations = undefined;

let updateActiveOperationsTimer = undefined;
Expand All @@ -35,24 +36,51 @@ function operationsStatusesMonitorDirectives($interval, $repositories, Monitorin
// =========================
// Private functions
// =========================
/**
*
* @param {string} operationGroupType - the value must be one of {@see OPERATION_GROUP_TYPE} options.
* @return {OperationGroupSummary}
*/
const getOperationGroupSummary = (operationGroupType) => {
const runningOperations = getOperationRunningCount(operationGroupType);
return new OperationGroupSummary(operationGroupType, runningOperations, getSummaryGroupOperationStatus(operationGroupType));
};

/**
* Calculates count of active operations in the entire group depending on <code>operationGroupType</code>.
* Calculates count of active operations depending on <code>operationGroupType</code>.
* @param {string} operationGroupType - the value must be one of {@see OPERATION_GROUP_TYPE} options.
* @return {number}
*/
const getOperationGroupRunningCount = (operationGroupType) => {
const getOperationRunningCount = (operationGroupType) => {
if (scope.activeOperations.operations) {
return scope.activeOperations.operations.reduce((runningOperation, operationStatus) => {
if (operationGroupType !== operationStatus.operationGroup) {
// we skip the cluster and backup_and_restore operations because we do not support multiple operations of that type.
if (operationGroupType !== operationStatus.operationGroup || OPERATION_GROUP_TYPE.CLUSTER_OPERATION === operationStatus.operationGroup || OPERATION_GROUP_TYPE.BACKUP_AND_RESTORE_OPERATION === operationStatus.operationGroup) {
return runningOperation;
}

return runningOperation + operationStatus.runningOperationCount;
if (OPERATION_GROUP_TYPE.QUERIES_OPERATION === operationStatus.operationGroup || OPERATION_GROUP_TYPE.IMPORT_OPERATION === operationStatus.operationGroup) {
return runningOperation + operationStatus.runningOperationCount;
} else {
return runningOperation + 1;
}
}, 0);
}
};

const getSummaryGroupOperationStatus = (operationGroupType) => {
return scope.activeOperations.operations.reduce((groupStatus, operationStatus) => {
if (operationGroupType !== operationStatus.operationGroup) {
return groupStatus;
}

if (OPERATION_STATUS_SORT_ORDER.getOrder(operationStatus.status) > OPERATION_STATUS_SORT_ORDER.getOrder(groupStatus)) {
return operationStatus.status;
}
return groupStatus;
}, OPERATION_STATUS.INFORMATION);
};

/**
*
* @param {ActiveOperationsModel} newActiveOperations - new active operations.
Expand All @@ -63,13 +91,21 @@ function operationsStatusesMonitorDirectives($interval, $repositories, Monitorin
}
scope.activeOperations = newActiveOperations;

scope.activeOperations.operations.forEach((op) => {
op.groupRunningOperationCount = getOperationGroupRunningCount(op.operationGroup);
});

scope.activeOperations.operations.sort((a, b) => {
return OPERATION_TYPE_SORT_ORDER[a.type] - OPERATION_TYPE_SORT_ORDER[b.type];
});

const operationsStatusesSummary = [];
const processedGroup = [];
scope.activeOperations.operations.forEach((operation) => {
const groupType = operation.operationGroup;
if (!processedGroup.includes(groupType)) {
processedGroup.push(groupType);
operationsStatusesSummary.push(getOperationGroupSummary(groupType));
}
});
scope.operationsSummary = operationsStatusesSummary;

};

const reloadActiveOperations = () => {
Expand Down Expand Up @@ -116,3 +152,11 @@ function operationsStatusesMonitorDirectives($interval, $repositories, Monitorin
updateActiveOperationsTimer = $interval(() => reloadActiveOperations(), UPDATE_ACTIVE_OPERATION_TIME_INTERVAL);
}
}

class OperationGroupSummary {
constructor(operationGroup, runningOperations, status) {
this.type = operationGroup;
this.runningOperations = runningOperations;
this.status = status;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@

<div ng-if="activeOperations && activeOperations.operations && activeOperations.operations.length > 0"
class="operations-statuses btn-group">
<button type="button" class="btn btn-secondary operations-statuses-dropdown-toggle dropdown-toggle" data-toggle="dropdown"
<button type="button"
class="btn btn-secondary operations-statuses-dropdown-toggle dropdown-toggle" data-toggle="dropdown"
ng-if="operationsSummary && operationsSummary.length > 0"
ng-class="{
'btn-fade-danger': OPERATION_STATUS.CRITICAL === activeOperations.status,
'btn-fade-warning': OPERATION_STATUS.WARNING === activeOperations.status
}">
<div class="operation-status-header" ng-repeat="operation in activeOperations.operations"
ng-if="OPERATION_TYPE.UPDATES !== operation.type">
<div class="operation-status-header" ng-repeat="operationGroup in operationsSummary">
<div ng-class="{
'icon-import': OPERATION_GROUP_TYPE.IMPORT_OPERATION === operation.operationGroup,
'icon-exchange': OPERATION_GROUP_TYPE.QUERIES_OPERATION === operation.operationGroup,
'fa fa-archive': OPERATION_GROUP_TYPE.BACKUP_AND_RESTORE_OPERATION === operation.operationGroup,
'fa fa-sitemap': OPERATION_GROUP_TYPE.CLUSTER_OPERATION === operation.operationGroup,
'status-critical': OPERATION_STATUS.CRITICAL === operation.status,
'status-information': OPERATION_STATUS.INFORMATION === operation.status,
'status-warning': OPERATION_STATUS.WARNING === operation.status}">
'icon-import': OPERATION_GROUP_TYPE.IMPORT_OPERATION === operationGroup.type,
'icon-exchange': OPERATION_GROUP_TYPE.QUERIES_OPERATION === operationGroup.type,
'fa fa-archive': OPERATION_GROUP_TYPE.BACKUP_AND_RESTORE_OPERATION === operationGroup.type,
'fa fa-sitemap': OPERATION_GROUP_TYPE.CLUSTER_OPERATION === operationGroup.type,
'status-critical': OPERATION_STATUS.CRITICAL === operationGroup.status,
'status-information': OPERATION_STATUS.INFORMATION === operationGroup.status,
'status-warning': OPERATION_STATUS.WARNING === operationGroup.status}">
</div>
<span ng-if="operation.groupRunningOperationCount" class="running-operation-count">
<sup class="tag-info">{{operation.groupRunningOperationCount}}</sup>
<span ng-if="operationGroup.runningOperations" class="running-operation-count">
<sup class="tag-info">{{operationGroup.runningOperations}}</sup>
</span>
</div>
<span class="caret"></span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export class ActiveOperationModel {
*/
this.operationGroup = undefined;
this.runningOperationCount = 0;
this.groupRunningOperationCount = 0;
this.status = OPERATION_STATUS.INFORMATION;
/**
* @type {string} - the value must be one of the {@see OPERATION_TYPE} option.
*/
this.type = undefined;
this.titleLabelKey = '';
this.monitoringViewUrl = '';
Expand Down
22 changes: 22 additions & 0 deletions src/js/angular/models/monitoring/operations/operation-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,25 @@ export const OPERATION_STATUS = {
'WARNING': 'WARNING',
'INFORMATION': 'INFORMATION'
};

export const OPERATION_STATUS_SORT_ORDER = {
'INFORMATION': 0,
'WARNING': 1,
'CRITICAL': 2,
/**
*
* @param {string} operationStatus - the value must be one of the {@see OPERATION_STATUS} option.
*
* @return {number} the order of operation status.
*/
'getOrder': (operationStatus) => {
switch (operationStatus) {
case OPERATION_STATUS.WARNING:
return OPERATION_STATUS_SORT_ORDER.WARNING;
case OPERATION_STATUS.CRITICAL:
return OPERATION_STATUS_SORT_ORDER.CRITICAL;
default:
return OPERATION_STATUS_SORT_ORDER.INFORMATION;
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,41 @@ describe('Operations Status Component', () => {
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('not.exist');
});

it('should display icons with ongoing operations', () => {
// When I visit some page and there are running operations.
GlobalOperationsStatusesStub.stubGlobalOperationsStatusesResponse(repositoryId);
HomeSteps.visitAndWaitLoader();
// Then I expect "Global Operations Component" to be displayed.
OperationsStatusesComponentSteps.getOperationsStatusesComponent().should('exist');

OperationsStatusesComponentSteps.getImportOperationStatusHeaderElement()
.should('have.length', 1)
.parent()
.find('.running-operation-count')
.should('exist')
.contains('1');

OperationsStatusesComponentSteps.getQueriesOperationStatusHeaderElement()
.should('have.length', 1)
.parent()
.find('.running-operation-count')
.should('exist')
.contains('26');

OperationsStatusesComponentSteps.getBackupAndRestoreOperationStatusHeaderElement()
.should('have.length', 1)
.parent()
.find('.running-operation-count')
.should('not.exist');

OperationsStatusesComponentSteps.getClusterOperationStatusHeaderElement()
.should('have.length', 1)
.parent()
.find('.running-operation-count')
.should('not.exist');

});

it('should redirect to query and update view wen click on queries operation element', () => {
// When I visit some page and there are running operations.
GlobalOperationsStatusesStub.stubGlobalOperationsStatusesResponse(repositoryId);
Expand Down
20 changes: 20 additions & 0 deletions test-cypress/steps/operations-statuses-component-steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ export class OperationsStatusesComponentSteps {
return cy.get('.operations-statuses');
}

static getOperationStatusHeader(iconName) {
return OperationsStatusesComponentSteps.getOperationsStatusesComponent().find(`.operation-status-header ${iconName}`);
}

static getImportOperationStatusHeaderElement() {
return OperationsStatusesComponentSteps.getOperationStatusHeader('.icon-import');
}

static getQueriesOperationStatusHeaderElement() {
return OperationsStatusesComponentSteps.getOperationStatusHeader('.icon-exchange');
}

static getBackupAndRestoreOperationStatusHeaderElement() {
return OperationsStatusesComponentSteps.getOperationStatusHeader('.fa.fa-archive');
}

static getClusterOperationStatusHeaderElement() {
return OperationsStatusesComponentSteps.getOperationStatusHeader('.fa.fa-sitemap');
}

static getOperationStatuses() {
return cy.get('.operation-status-content');
}
Expand Down

0 comments on commit a8901b5

Please sign in to comment.