Skip to content

Commit 679e46f

Browse files
authored
[OGUI-1876] Add detector active/inactive on locks page to ask user for confirmation on release (#3312)
* Lock Page: * adds fetching of detector activity on lock initialization page * activity is then used to ask user to confirm they wish to release a lock for an active environment * Global Page * Creation Page: * activity is taken from already existing `isActive` from environment list instead * Remove dead code
1 parent 501abe7 commit 679e46f

File tree

7 files changed

+82
-111
lines changed

7 files changed

+82
-111
lines changed

Control/docs/LOCKS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Acquires a lock on a detector for the current user.
3737

3838
### Release Lock
3939

40-
Releases a lock on a detector.
40+
Releases a lock on a detector. If the detector is currently reported as `Active` by ECS, the release lock button will prompt the user to confirm the action is indeed correct.
4141

4242
**Parameters**:
4343
- `detectorId`: The detector identifier or `ALL` to release all locks (excluding TST)

Control/public/common/enums/DetectorState.enum.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
export const DetectorState = Object.freeze({
2020
UNDEFINED: 'UNDEFINED', // GUI initial set state
2121
NULL_STATE: 'NULL_STATE',
22+
ACTIVE: 'ACTIVE', // Custom GUI state; Detector is active in a run, i.e. it is taking data
2223
READY: 'READY',
2324
RUN_OK: 'RUN_OK',
2425
RUN_FAILURE: 'RUN_FAILURE',
@@ -45,6 +46,7 @@ export const DetectorState = Object.freeze({
4546
export const DetectorStateStyle = Object.freeze({
4647
UNDEFINED: '',
4748
NULL_STATE: '',
49+
ACTIVE: 'warning',
4850
READY: 'bg-primary white',
4951
RUN_OK: 'bg-success white',
5052
RUN_FAILURE: 'bg-danger white',

Control/public/lock/lockButton.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import {DetectorLockAction} from './../common/enums/DetectorLockAction.enum.js';
2525
* @param {LockModel} lockModel - model of the lock state and actions
2626
* @param {String} detector - detector name
2727
* @param {Object} lockState - lock state of the detector
28+
* @param {Boolean} isIcon - whether to render as an icon or a button
29+
* @param {Boolean} [isActive = false] - whether the detector is active
2830
*/
29-
export const detectorLockButton = (lockModel, detector, lockState, isIcon = false) => {
31+
export const detectorLockButton = (lockModel, detector, lockState, isIcon = false, isActive = false) => {
3032
const isDetectorLockTaken = lockModel.isLocked(detector);
3133

3234
let detectorLockHandler = null;
@@ -35,7 +37,14 @@ export const detectorLockButton = (lockModel, detector, lockState, isIcon = fals
3537
if (isDetectorLockTaken) {
3638
if (lockModel.isLockedByCurrentUser(detector)) {
3739
detectorLockButtonClass = '.success';
38-
detectorLockHandler = () => lockModel.actionOnLock(detector, DetectorLockAction.RELEASE, false);
40+
detectorLockHandler = () => {
41+
if (isActive) {
42+
confirm(`Are you sure you want to release the lock for an ACTIVE ${detector}?`)
43+
&& lockModel.actionOnLock(detector, DetectorLockAction.RELEASE, false);
44+
} else {
45+
lockModel.actionOnLock(detector, DetectorLockAction.RELEASE, false);
46+
}
47+
};
3948
} else {
4049
detectorLockButtonClass = '.warning.disabled.disabled-item';
4150
}

Control/public/lock/lockPage.js

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import loading from './../common/loading.js';
2222
import {DetectorLockAction} from '../common/enums/DetectorLockAction.enum.js';
2323
import {isUserAllowedRole} from './../common/userRole.js';
2424
import { getDetectorListWithTstAtEnd, TST_DETECTOR_NAME } from '../common/detectorUtils.js';
25+
import { DetectorState, DetectorStateStyle } from '../common/enums/DetectorState.enum.js';
2526

26-
const LOCK_TABLE_HEADER_KEYS = ['Detector', 'Owner'];
27+
const LOCK_TABLE_HEADER_KEYS = ['Detector', 'Owner', 'Active'];
2728
const DETECTOR_ALL = 'ALL';
2829

2930
/**
@@ -50,7 +51,10 @@ export const content = (model) => {
5051
const { padlockState } = lockModel;
5152
return [
5253
detectorHeader(model),
53-
h('.text-center.scroll-y.absolute-fill', {style: 'top: 40px'}, [
54+
h('.text-center.scroll-y.absolute-fill', {
55+
style: 'top: 40px',
56+
oncreate: () => detectorsService.getActiveDetectors(),
57+
}, [
5458
padlockState.match({
5559
NotAsked: () => null,
5660
Loading: () => loading(3),
@@ -78,7 +82,7 @@ export const content = (model) => {
7882
],
7983
]),
8084
],
81-
detectorLocksTable(model, detectorsLocksState)
85+
detectorLocksTable(model, detectorsLocksState, detectorsService.activeDetectors)
8286
])
8387
})
8488
])
@@ -89,9 +93,10 @@ export const content = (model) => {
8993
* Table with lock status details, buttons to lock them, and admin actions such us "Force release"
9094
* @param {Model} model - root model of the application
9195
* @param {Object<String, DetectorLock>} detectorsLockState - state of the detectors lock
96+
* @param {RemoteData} activeDetectorsRemote - remote data with the list of active detectors
9297
* @return {vnode}
9398
*/
94-
const detectorLocksTable = (model, detectorLocksState) => {
99+
const detectorLocksTable = (model, detectorLocksState, activeDetectorsRemote) => {
95100
const { detectors: detectorsService, lock: lockModel } = model;
96101
const isUserGlobal = isUserAllowedRole(ROLES.Global);
97102
const detectorKeysWithTstLast = getDetectorListWithTstAtEnd(Object.keys(detectorLocksState));
@@ -104,10 +109,14 @@ const detectorLocksTable = (model, detectorLocksState) => {
104109
return (isUserGlobal && isSelectedDetectorViewGlobalOrCurrent) || isUserAllowedDetector;
105110
})
106111
.map((detectorName) => {
112+
const detectorActivityState = _getDetectorState(activeDetectorsRemote, detectorName);
107113
if (detectorName.toLocaleUpperCase().includes(TST_DETECTOR_NAME)) {
108-
return [emptyRowSeparator(), detectorLockRow(lockModel, detectorName, detectorLocksState[detectorName])];
114+
return [
115+
emptyRowSeparator(),
116+
detectorLockRow(lockModel, detectorName, detectorLocksState[detectorName], detectorActivityState)
117+
];
109118
} else {
110-
return detectorLockRow(lockModel, detectorName, detectorLocksState[detectorName])
119+
return detectorLockRow(lockModel, detectorName, detectorLocksState[detectorName], detectorActivityState)
111120
}
112121
});
113122
return h('table.table.table-sm',
@@ -121,7 +130,7 @@ const detectorLocksTable = (model, detectorLocksState) => {
121130
detectorRows.length > 0
122131
? detectorRows
123132
: h('tr',
124-
h('td.ph2.warning', {colspan: 3}, [
133+
h('td.ph2.warning', { colspan: LOCK_TABLE_HEADER_KEYS.length } , [
125134
'Missing Role permissions needed for being allowed to own locks',
126135
' If you have just started your shift, please allow a few minutes for the system ',
127136
'to update before trying again or calling an FLP expert.'
@@ -136,20 +145,24 @@ const detectorLocksTable = (model, detectorLocksState) => {
136145
* @param {LockModel} lockModel - model of the lock state and actions
137146
* @param {String} detector - detector name
138147
* @param {DetectorLock} lockState - state of the lock {owner: {fullName: String}, isLocked: Boolean
148+
* @param {DetectorState} detectorActivityState - state of the detector as per AliECS (Active, Inactive, Unknown)
139149
* @return {vnode}
140150
*/
141-
const detectorLockRow = (lockModel, detector, lockState) => {
151+
const detectorLockRow = (lockModel, detector, lockState, detectorActivityState) => {
142152
const ownerName = lockState?.owner?.fullName || '-';
143153
return h('tr', {
144154
id: `detector-row-${detector}`,
145155
}, [
146156
h('td',
147157
h('.flex-row.g2.items-center.f5', [
148-
detectorLockButton(lockModel, detector, lockState),
158+
detectorLockButton(lockModel, detector, lockState, false, detectorActivityState === DetectorState.ACTIVE),
149159
detector
150160
])
151161
),
152162
h('td', ownerName),
163+
h(`td`, {
164+
class: DetectorStateStyle[detectorActivityState]
165+
}, detectorActivityState),
153166
isUserAllowedRole(ROLES.Global) && h('td', [
154167
detectorLockActionButton(lockModel, detector, lockState, DetectorLockAction.RELEASE, true, 'Force Release'),
155168
detectorLockActionButton(lockModel, detector, lockState, DetectorLockAction.TAKE, true, 'Force Take')
@@ -161,4 +174,20 @@ const detectorLockRow = (lockModel, detector, lockState) => {
161174
* Empty table row separator vnode
162175
* @return {vnode}
163176
*/
164-
const emptyRowSeparator = () => h('tr', h('td', {colspan: 3}, h('hr')));
177+
const emptyRowSeparator = () => h('tr', h('td', {colspan: LOCK_TABLE_HEADER_KEYS.length}, h('hr')));
178+
179+
/**
180+
* Helper function to get the state of the detector (Active, Inactive, Unknown) based on the activeDetectorsRemote data
181+
* @param {RemoteData} activeDetectorsRemote - remote data with the list of active detectors
182+
* @param {String} detectorName - name of the detector to get the state for
183+
* @return {String} state of the detector (Active, Inactive, Unknown)
184+
*/
185+
const _getDetectorState = (activeDetectorsRemote, detectorName) => {
186+
return activeDetectorsRemote.match({
187+
NotAsked: () => DetectorState.UNDEFINED,
188+
Loading: () => DetectorState.UNDEFINED,
189+
Failure: () => DetectorState.ERROR,
190+
Success: (activeDetectors) =>
191+
activeDetectors.includes(detectorName) ? DetectorState.ACTIVE : DetectorState.UNDEFINED
192+
})
193+
}

Control/public/services/DetectorService.js

Lines changed: 20 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export default class DetectorService extends Observable {
3737
this.hostsByDetectorRemote = RemoteData.notAsked();
3838
this._selected = '';
3939

40+
this._activeDetectors = RemoteData.notAsked();
41+
4042
/**
4143
* @type {Object<String, Detector>}
4244
*/
@@ -55,9 +57,7 @@ export default class DetectorService extends Observable {
5557
this._listRemote = await this.getDetectorsAsRemoteData(this._listRemote, this);
5658
this.notify();
5759
if (this._listRemote.isSuccess()) {
58-
this.hostsByDetectorRemote = await this.getHostsByDetectorsAsRemoteData(
59-
this.hostsByDetectorRemote, this._listRemote.payload, this
60-
);
60+
this.hostsByDetectorRemote = await this.getHostsByDetectorsAsRemoteData(this.hostsByDetectorRemote, this);
6161
for (const detector of this._listRemote.payload) {
6262
this._availability[detector] = {
6363
pfrAvailability: DetectorState.UNDEFINED,
@@ -107,7 +107,7 @@ export default class DetectorService extends Observable {
107107
* @param {Object} that
108108
* @returns {RemoteData}
109109
*/
110-
async getHostsByDetectorsAsRemoteData(item, detectors, that) {
110+
async getHostsByDetectorsAsRemoteData(item, that) {
111111
item = RemoteData.loading();
112112
that.notify();
113113
const {ok, result} = await this.model.loader.get(`/api/core/hostsByDetectors`);
@@ -136,56 +136,20 @@ export default class DetectorService extends Observable {
136136
}
137137

138138
/**
139-
* Fetch detectors and return it as a remoteData object
140-
* @param {RemoteData} item
141-
*/
142-
async getAndSetDetectorsAsRemoteData() {
143-
this._listRemote = RemoteData.loading();
144-
this.notify();
145-
146-
const {result, ok} = await this.model.loader.get(`/api/core/detectors`);
147-
if (!ok) {
148-
this._listRemote = RemoteData.failure(result.message);
149-
} else {
150-
this._listRemote = RemoteData.success(result.detectors);
151-
}
152-
this.notify();
153-
}
154-
155-
/**
156-
* Fetch detectors and return it as a remoteData object
157-
* @param {RemoteData} item
139+
* Fetch active detectors from AliECS and update the corresponding RemoteData object
140+
* @return {void}
158141
*/
159-
async getActiveDetectorsAsRemoteData(item) {
160-
item = RemoteData.loading();
142+
async getActiveDetectors() {
143+
this._activeDetectors = RemoteData.loading();
161144
this.notify();
162-
163-
const {result, ok} = await this.model.loader.post(`/api/GetActiveDetectors`);
145+
const { result, ok } = await this.model.loader.post('/api/GetActiveDetectors', {});
164146
if (!ok) {
165-
item = RemoteData.failure(result.message);
147+
this._activeDetectors = RemoteData.failure(result.message);
166148
} else {
167-
item = RemoteData.success(result.detectors);
149+
const { detectors = []} = result || {};
150+
this._activeDetectors = RemoteData.success(detectors);
168151
}
169152
this.notify();
170-
return item;
171-
}
172-
173-
/**
174-
* Given a detector, it will return a RemoteData objects containing the result of query 'GetHostInventory'
175-
* @param {String} detector
176-
* @return {RemoteData}
177-
*/
178-
async getHostsForDetector(detector, item, that) {
179-
item = RemoteData.loading();
180-
that.notify();
181-
const {result, ok} = await this.model.loader.post(`/api/GetHostInventory`, {detector});
182-
if (!ok) {
183-
item = RemoteData.failure(result.message);
184-
} else {
185-
item = RemoteData.success(result.hosts);
186-
}
187-
that.notify();
188-
return item;
189153
}
190154

191155
/**
@@ -207,47 +171,6 @@ export default class DetectorService extends Observable {
207171
return this._listRemote;
208172
}
209173

210-
/**
211-
* Method to return a RemoteData object containing list of detectors fetched from AliECS and their availability
212-
* @param {boolean} [restrictToUser = true] - if the list should be restricted to user permissions only
213-
* @param {RemoteData} item - item in which data should be loaded and notified
214-
* @param {typeof Model} that - model that should be notified after a change in data fetching
215-
* @returns {RemoteData<Array<DetectorAvailability>>} - returns the state of the detectors
216-
*/
217-
async getDetectorsAvailabilityAsRemote(restrictToUser = true, item = RemoteData.notAsked(), that = this) {
218-
item = RemoteData.loading();
219-
that.notify();
220-
221-
let {result: {detectors}, ok: detectorsOk} = await this.model.loader.get(`/api/core/detectors`);
222-
const {
223-
result: {detectors: activeDetectors},
224-
ok: detectorsActivityOk
225-
} = await this.model.loader.post(`/api/GetActiveDetectors`);
226-
const isLockDataOk = this.model.lock.padlockState.isSuccess();
227-
228-
if (detectorsOk && detectorsActivityOk && isLockDataOk) {
229-
const padLock = this.model.lock.padlockState.payload;
230-
if (restrictToUser && this.isSingleView()) {
231-
detectors = detectors.filter((detector) => detector === this._selected);
232-
}
233-
/**
234-
* @type {Array<DetectorAvailability>}
235-
*/
236-
const detectorsAvailability = detectors.map((detector) => ({
237-
name: detector,
238-
isActive: activeDetectors.includes(detector),
239-
isLockedBy: padLock.lockedBy[detector],
240-
}));
241-
item = RemoteData.success(detectorsAvailability);
242-
that.notify();
243-
return item;
244-
} else {
245-
item = RemoteData.failure('Unable to fetch information on detectors state');
246-
that.notify();
247-
return item;
248-
}
249-
}
250-
251174
/**
252175
* Method to return a RemoteData object containing list of detectors fetched from AliECS
253176
* @deprecated as it should be using `getDetectorsAsRemote` instead
@@ -295,6 +218,14 @@ export default class DetectorService extends Observable {
295218
return this._availability;
296219
}
297220

221+
/**
222+
* Return an instance of the current active detectors as per AliECS
223+
* @return {RemoteData<Array<String>>}
224+
*/
225+
get activeDetectors() {
226+
return this._activeDetectors;
227+
}
228+
298229
/**
299230
* Given a list of detectors, return if all are available for specified property (PFR/SOR)
300231
* @param {Array<String>} detectors - list of detectors to check

Control/public/workflow/panels/flps/FlpSelection.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,26 @@ export default class FlpSelection extends Observable {
5858
this.notify();
5959

6060
await this.getAndSetDetectors();
61-
/*if (this.workflow.model.detectors.isSingleView()
62-
&& this.activeDetectors.isSuccess()
63-
&& !this.activeDetectors.payload.detectors.includes(this.workflow.model.detectors.selected)
64-
) {
65-
// if single view preselect detectors and hosts for users
66-
this.toggleDetectorSelection(this.workflow.model.detectors.selected);
67-
}*/
6861
}
6962

7063
/**
7164
* Method to request a list of detectors from AliECS and initialized the user form accordingly
65+
* @return {Promise<void>}
7266
*/
7367
async getAndSetDetectors() {
7468
this.detectors = this.workflow.model.detectors.listRemote;
69+
await this.getActiveDetectors();
70+
}
7571

72+
/**
73+
* Method to retrieve the detectors that are active as per AliECS
74+
* @return {Promise<void>}
75+
*/
76+
async getActiveDetectors() {
7677
this.activeDetectors = RemoteData.loading();
7778
this.notify();
7879
const {result, ok} = await this.workflow.model.loader.post('/api/GetActiveDetectors', {});
7980
this.activeDetectors = ok ? RemoteData.success(result) : RemoteData.failure(result.message);
80-
8181
this.notify();
8282
}
8383

Control/public/workflow/panels/flps/detectorsPanel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const detectorSelectionPanel = (model, name) => {
122122
id: `detector-selection-panel-${name}'`,
123123
}, [
124124
h('.flex-row', [
125-
detectorLockButton(lockModel, name, lockState, true),
125+
detectorLockButton(lockModel, name, lockState, true, isDetectorActive),
126126
h('a.menu-item.w-wrapped', {
127127
className,
128128
id: `detectorSelectionButtonFor${name}`,

0 commit comments

Comments
 (0)