-
Notifications
You must be signed in to change notification settings - Fork 14
[OGUI-1892] Disable level buttons for shifter users on page load and after #3438
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
base: dev
Are you sure you want to change the base?
Changes from all commits
3f14a80
cd8c76d
fdfab13
28efc39
caa7eec
9031c35
78c22c0
b631069
8c6e7d6
6337802
78b27f3
38e4d4b
4ea774e
746724d
328e93e
49ac19f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,7 @@ import { | |
| Observable, WebSocketClient, QueryRouter, | ||
| Loader, RemoteData, sessionService, Notification, | ||
| } from '/js/src/index.js'; | ||
| import { callRateLimiter, setBrowserTabTitle } from './common/utils.js'; | ||
| import { callRateLimiter, setBrowserTabTitle, hasShifterButNoAdminRole } from './common/utils.js'; | ||
| import { ConfigurationService } from './services/ConfigurationService.js'; | ||
| import { MODE } from './constants/mode.const.js'; | ||
| import Log from './log/Log.js'; | ||
|
|
@@ -311,12 +311,16 @@ export default class Model extends Observable { | |
|
|
||
| /** | ||
| * Delegates sub-model actions depending new location of the page | ||
| * If user is shifter but not admin, set Ops as maximum level for filtering | ||
| */ | ||
| handleLocationChange() { | ||
| const { params } = this.router; | ||
| if (params) { | ||
| this.parseLocation(params); | ||
| } | ||
| if (hasShifterButNoAdminRole(this.session.access)) { | ||
| this.log.filter.setCriteria('level', 'max', 1); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use infologger-level.const.js? |
||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,13 +12,16 @@ | |
| * or submit itself to any jurisdiction. | ||
| */ | ||
|
|
||
| import { Role } from './../constants/role.const.js'; | ||
| import { INFOLOGGER_LEVEL_LIST, InfoLoggerLevel } from './../constants/infologger-level.const.js'; | ||
|
|
||
| /** | ||
| * Limit the number of calls to `fn` to 1 per `time` maximum. | ||
| * First call is immediate if `time` have been waited already. | ||
| * All other calls before end of `time` window will lead to 1 exececution at the end of window. | ||
| * @param {string} fn - function to be called | ||
| * @param {string} time - ms | ||
| * @returns {Function} lambda function to be called to call `fn` | ||
| * @returns {void} lambda function to be called to call `fn` | ||
| * @example | ||
| * let f = callRateLimiter((arg) => console.log('called', arg), 1000); | ||
| * 00:00:00 f(1);f(2);f(3);f(4); | ||
|
|
@@ -60,3 +63,30 @@ export function setBrowserTabTitle(title = undefined) { | |
| document.title = title; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Method to check if the user has only shifter role and not admin role | ||
| * @param {string[]} access - array of user roles email groups affiliation | ||
| * @returns {boolean} true if the user has only shifter role and not admin role, false otherwise | ||
| */ | ||
| export function hasShifterButNoAdminRole(access = []) { | ||
| return access.includes(Role.SHIFTER) && !access.includes(Role.ADMIN); | ||
| } | ||
|
|
||
| /** | ||
| * Method to return filter levels allowed for filtering based on current user role email groups affiliation | ||
| * * Shifters are only allowed to filter by Ops level | ||
| * @param {string[]} access - array of user roles email groups affiliation | ||
| * @returns {{label: string, index:number}[]} - filter levels allowed for filtering | ||
| */ | ||
| export function getFilterLevelsAllowed(access = []) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I comment here, but the change would be elsewhere The access is not checked server-side? If the user changes it locally they can still query things they aren't allowed to. Are we okay with this level of security? |
||
| return hasShifterButNoAdminRole(access) | ||
| ? INFOLOGGER_LEVEL_LIST.map((level) => ({ | ||
| ...level, | ||
| available: level.label === InfoLoggerLevel.OPS.label, | ||
| })) | ||
| : INFOLOGGER_LEVEL_LIST.map((level) => ({ | ||
| ...level, | ||
| available: true, | ||
| })); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2019-2020 CERN and copyright holders of ALICE O2. | ||
| * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. | ||
| * All rights not expressly granted are reserved. | ||
| * | ||
| * This software is distributed under the terms of the GNU General Public | ||
| * License v3 (GPL Version 3), copied verbatim in the file "COPYING". | ||
| * | ||
| * In applying this license CERN does not waive the privileges and immunities | ||
| * granted to it by virtue of its status as an Intergovernmental Organization | ||
| * or submit itself to any jurisdiction. | ||
| */ | ||
|
|
||
| /** | ||
| * Object containing the different levels of logs that can be displayed in the application, | ||
| * with their label and index as in the database | ||
| * These values are as per InfoLogger defined levels: | ||
| * {@link https://github.com/AliceO2Group/InfoLogger/blob/master/doc/README.md} | ||
| */ | ||
| export const InfoLoggerLevel = Object.freeze({ | ||
| OPS: { | ||
| label: 'Ops', | ||
| index: 1, | ||
| }, | ||
| SUPPORT: { | ||
| label: 'Support', | ||
| index: 6, | ||
| }, | ||
| DEVEL: { | ||
| label: 'Devel', | ||
| index: 11, | ||
| }, | ||
| TRACE: { | ||
| label: 'Trace', | ||
| index: null, | ||
| }, | ||
| }); | ||
|
|
||
| /** | ||
| * Array containing the different levels of logs that can be displayed in the application, | ||
| * with their label and index as in the database, used for iterating over the levels in the UI | ||
| * These values are as per InfoLogger defined levels: | ||
| */ | ||
| export const INFOLOGGER_LEVEL_LIST = Object.values(InfoLoggerLevel); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2019-2020 CERN and copyright holders of ALICE O2. | ||
| * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. | ||
| * All rights not expressly granted are reserved. | ||
| * | ||
| * This software is distributed under the terms of the GNU General Public | ||
| * License v3 (GPL Version 3), copied verbatim in the file "COPYING". | ||
| * | ||
| * In applying this license CERN does not waive the privileges and immunities | ||
| * granted to it by virtue of its status as an Intergovernmental Organization | ||
| * or submit itself to any jurisdiction. | ||
| */ | ||
|
|
||
| /** | ||
| * Object containing the different roles that a user can have in the application, used for checking | ||
| * permissions and access levels. These roles are defined in CERN Application Service | ||
| */ | ||
| export const Role = Object.freeze({ | ||
| SHIFTER: 'shifter', | ||
| ADMIN: 'admin', | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,92 +14,97 @@ | |
|
|
||
| import { h } from '/js/src/index.js'; | ||
|
|
||
| const LIMIT_LEVELS = [ | ||
| { label: '100k', value: 100000 }, | ||
| { label: '500k', value: 500000 }, | ||
| { label: '1M', value: 1000000 }, | ||
| ]; | ||
| const SEVERITIES_ALLOWED = [ | ||
| { label: 'Debug', value: 'D' }, | ||
| { label: 'Info', value: 'I' }, | ||
| { label: 'Warn', value: 'W' }, | ||
| { label: 'Error', value: 'E' }, | ||
| { label: 'Fatal', value: 'F' }, | ||
| ]; | ||
|
|
||
| /** | ||
| * Filtering main options, in toolbar, top-right. | ||
| * - severity | ||
| * - level | ||
| * - limit | ||
| * - reset | ||
| * @param {Model} model - root model of the application | ||
| * @param {Log} logModel - log model of the application | ||
| * @param {{label: string, index:number}[]} filterLevelsAllowed - levels allowed for filtering | ||
| * @returns {vnode} - the view of filters panel | ||
| */ | ||
| export default (model) => [ | ||
| export default (logModel, filterLevelsAllowed) => [ | ||
| h( | ||
| '', | ||
| h('.btn-group', [ | ||
| buttonSeverity(model, 'Debug', 'Match severity debug', 'D'), | ||
| buttonSeverity(model, 'Info', 'Match severity info', 'I'), | ||
| buttonSeverity(model, 'Warn', 'Match severity warnings', 'W'), | ||
| buttonSeverity(model, 'Error', 'Match severity errors', 'E'), | ||
| buttonSeverity(model, 'Fatal', 'Match severity fatal', 'F'), | ||
| ]), | ||
| h('span.mh3'), | ||
| h('.btn-group', [ | ||
| buttonFilterLevel(model, 'Ops', 1), | ||
| buttonFilterLevel(model, 'Support', 6), | ||
| buttonFilterLevel(model, 'Devel', 11), | ||
| buttonFilterLevel(model, 'Trace', null), // 21 | ||
| ]), | ||
| h('span.mh3'), | ||
| h('.btn-group', [ | ||
| buttonLogLimit(model, '100k', 100000), | ||
| buttonLogLimit(model, '500k', 500000), | ||
| buttonLogLimit(model, '1M', 1000000), | ||
| ]), | ||
| h('span.mh3'), | ||
| buttonReset(model), | ||
| '.btn-group', | ||
| SEVERITIES_ALLOWED.map(({ label, value }) => _selectableButtonComponent( | ||
| label, | ||
| { | ||
| id: `severity-${value}`, | ||
| title: `Match severity ${label.toLowerCase()}`, | ||
| isActive: logModel.filter.criterias.severity.in.includes(value), | ||
| onclick: () => logModel.setCriteria('severity', 'in', value), | ||
| }, | ||
| )), | ||
| ), | ||
| ]; | ||
|
|
||
| /** | ||
| * Makes a button to toggle severity | ||
| * @param {Model} model - root model of the application | ||
| * @param {string} label - button's label | ||
| * @param {string} title - button's title on mouse over | ||
| * @param {string} value - a char to represent severity: W E F or I, can be many with spaces like 'W E' | ||
| * @returns {vnode} - the button to toggle severity | ||
| */ | ||
| const buttonSeverity = (model, label, title, value) => h('button.btn', { | ||
| className: model.log.filter.criterias.severity.in.includes(value) ? 'active' : '', | ||
| onclick: (e) => { | ||
| model.log.setCriteria('severity', 'in', value); | ||
| e.target.blur(); // remove focus so user can 'enter' without actually toggle again the button | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was |
||
| }, | ||
| title: title, | ||
| }, label); | ||
| h( | ||
| '.btn-group', | ||
| filterLevelsAllowed.map(({ label, index, available }) => _selectableButtonComponent( | ||
| label, | ||
| { | ||
| id: `level-${index}`, | ||
| title: available ? `Filter level ≤ ${index}` : `You don't have access to level ${label}`, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the tooltip will show |
||
| isActive: logModel.filter.criterias.level.max === index, | ||
| onclick: () => logModel.setCriteria('level', 'max', index), | ||
| disabled: !available, | ||
| }, | ||
| )), | ||
| ), | ||
|
|
||
| h( | ||
| '.btn-group', | ||
| LIMIT_LEVELS.map(({ label, value }) => | ||
| _selectableButtonComponent( | ||
| label, | ||
| { | ||
| id: `limit-${value}`, | ||
| title: `Keep only ${value / 1000}k logs in the view`, | ||
| isActive: logModel.limit === value, | ||
| onclick: () => logModel.setLimit(value), | ||
| }, | ||
| )), | ||
| ), | ||
|
|
||
| _selectableButtonComponent( | ||
| 'Reset filters', | ||
| { | ||
| title: 'Reset date, time, matches, excludes, log levels', | ||
| isActive: false, | ||
| onclick: () => logModel.filter.resetCriteria(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reset filters will allow resetting to a default, more permissive criteria set. |
||
| }, | ||
| ), | ||
| ]; | ||
|
|
||
| /** | ||
| * Makes a button to set filtering level (shifter, debug, etc) with number | ||
| * @param {Model} model - root model of the application | ||
| * Component representing the creation of a button for filtering header | ||
| * @param {string} label - button's label | ||
| * @param {number} value - maximum level of filtering, from 1 to 21 | ||
| * @param {object} options - options for the button | ||
| * @param {string} options.id - button's id | ||
| * @param {string} options.title - button's title on mouse over | ||
| * @param {boolean} options.isActive - whether the button is active | ||
| * @param {void} options.onclick - function to call when button is clicked | ||
| * @param {boolean} options.disabled - whether the button is disabled | ||
| * @returns {vnode} - component representing the creation of a button for filtering | ||
| */ | ||
| const buttonFilterLevel = (model, label, value) => h('button.btn', { | ||
| className: model.log.filter.criterias.level.max === value ? 'active' : '', | ||
| onclick: () => model.log.setCriteria('level', 'max', value), | ||
| title: `Filter level ≤ ${value}`, | ||
| }, label); | ||
| const _selectableButtonComponent = (label, { id, title, isActive, onclick, disabled }) => h('button.btn', { | ||
| id, | ||
| className: [isActive ? 'active' : '', disabled ? 'disabled' : ''].join(' '), | ||
| onclick, | ||
| title, | ||
| disabled, | ||
|
|
||
| /** | ||
| * Makes a button to set log limit, maximum logs in memory | ||
| * @param {Model} model - root model of the application | ||
| * @param {string} label - button's label | ||
| * @param {number} limit - how much logs to keep in memory | ||
| * @returns {vnode} - component representing the creation of a button for log limit | ||
| */ | ||
| const buttonLogLimit = (model, label, limit) => h('button.btn', { | ||
| className: model.log.limit === limit ? 'active' : '', | ||
| onclick: () => model.log.setLimit(limit), | ||
| title: `Keep only ${label} logs in the view`, | ||
| }, label); | ||
|
|
||
| /** | ||
| * Makes a button to reset filters | ||
| * @param {Model} model - root model of the application | ||
| * @returns {vnode} - component representing the creation of a button to reset filters | ||
| */ | ||
| const buttonReset = (model) => h('button.btn', { | ||
| onclick: () => model.log.filter.resetCriteria(), | ||
| title: 'Reset date, time, matches, excludes, log levels', | ||
| }, 'Reset filters'); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overwriting on each navigation call could be unnecessary and setCriteria could be a better more central place for it to be checked and set to a default.