|
13 | 13 | */ |
14 | 14 |
|
15 | 15 | import { h } from '/js/src/index.js'; |
16 | | -import { prettyFormatDate } from './../utils.js'; |
| 16 | +import { camelToTitleCase, copyToClipboard, isContextSecure, prettyFormatDate } from './../utils.js'; |
| 17 | + |
| 18 | +const SPECIFIC_KEY_LABELS = { |
| 19 | + id: 'ID (etag)', |
| 20 | +}; |
17 | 21 |
|
18 | 22 | const DATE_FIELDS = ['validFrom', 'validUntil', 'createdAt', 'lastModified']; |
19 | | -const TO_REMOVE_FIELDS = ['qcObject', 'versions', 'name', 'location']; |
| 23 | +const TO_REMOVE_FIELDS = ['etag', 'qcObject', 'versions', 'name', 'location']; |
| 24 | +const HIGHLIGHTED_FIELDS = ['runNumber', 'runType', 'path', 'qcVersion']; |
| 25 | + |
| 26 | +const KEY_TO_RENDER_FIRST = 'path'; |
20 | 27 |
|
21 | 28 | /** |
22 | 29 | * Builds a panel with information of the object; Fields are parsed according to their category |
23 | 30 | * @param {QCObjectDTO} qcObject - QC object with its associated details |
24 | 31 | * @param {object} style - properties of the vnode |
| 32 | + * @param {function(Notification): function(string, string): object} rowAttributes - |
| 33 | + * An optional curried function that returns the VNode attribute builder. |
| 34 | + * Use {@link defaultRowAttributes} exported from this module, supplying the Notification API. |
25 | 35 | * @returns {vnode} - panel with information about the object |
| 36 | + * @example |
| 37 | + * ``` |
| 38 | + * qcObjectInfoPanel(qcObject, {}, defaultRowAttributes(model.notification)) |
| 39 | + * ``` |
26 | 40 | */ |
27 | | -export const qcObjectInfoPanel = (qcObject, style = {}) => |
28 | | - h('.flex-column.scroll-y', { style }, [ |
29 | | - Object.keys(qcObject) |
30 | | - .filter((key) => !TO_REMOVE_FIELDS.includes(key)) |
31 | | - .map((key) => infoRow(key, qcObject[key])), |
| 41 | +export const qcObjectInfoPanel = (qcObject, style = {}, rowAttributes = () => undefined) => |
| 42 | + h('.flex-column.scroll-y#qcObjectInfoPanel', { style }, [ |
| 43 | + [ |
| 44 | + KEY_TO_RENDER_FIRST, |
| 45 | + ...Object.keys(qcObject) |
| 46 | + .filter((key) => |
| 47 | + key !== KEY_TO_RENDER_FIRST && !TO_REMOVE_FIELDS.includes(key)), |
| 48 | + ] |
| 49 | + .map((key) => infoRow(key, qcObject[key], rowAttributes)), |
32 | 50 | ]); |
33 | 51 |
|
34 | 52 | /** |
35 | 53 | * Builds a raw with the key and value information parsed based on their type |
36 | 54 | * @param {string} key - key of the object info |
37 | 55 | * @param {string|number|object|undefined} value - value of the object info |
| 56 | + * @param {function(key, value)} infoRowAttributes - function that return given attributes for the row |
38 | 57 | * @returns {vnode} - row with object information key and value |
39 | 58 | */ |
40 | | -const infoRow = (key, value) => h('.flex-row.g2', [ |
41 | | - h('b.w-25.w-wrapped', key), |
42 | | - h('.w-75', infoPretty(key, value)), |
43 | | -]); |
| 59 | +const infoRow = (key, value, infoRowAttributes) => { |
| 60 | + const highlightedClasses = HIGHLIGHTED_FIELDS.includes(key) ? '.highlighted' : ''; |
| 61 | + const formattedValue = infoPretty(key, value); |
| 62 | + const formattedKey = getUILabel(key); |
| 63 | + |
| 64 | + const hasValue = value != null && value !== '' && (!Array.isArray(value) || value.length !== 0); |
| 65 | + |
| 66 | + return h(`.flex-row.g2.info-row${highlightedClasses}`, [ |
| 67 | + h('b.w-25.w-wrapped', formattedKey), |
| 68 | + h('.w-75.cursor-pointer', hasValue && infoRowAttributes(formattedKey, formattedValue), formattedValue), |
| 69 | + ]); |
| 70 | +}; |
| 71 | + |
| 72 | +/** |
| 73 | + * Retrieves the final UI-friendly label for given data key |
| 74 | + * * Priority: |
| 75 | + * 1. Manual override using `SPECIFIC_KEY_LABELS` |
| 76 | + * 2. Use `defaultKeyTransform` to generate a label |
| 77 | + * @param {string} key - key of the object info |
| 78 | + * @returns {string} - formatted label for the given key |
| 79 | + */ |
| 80 | +const getUILabel = (key) => { |
| 81 | + if (Object.hasOwn(SPECIFIC_KEY_LABELS, key)) { |
| 82 | + return SPECIFIC_KEY_LABELS[key]; |
| 83 | + } |
| 84 | + |
| 85 | + return camelToTitleCase(key); |
| 86 | +}; |
44 | 87 |
|
45 | 88 | /** |
46 | 89 | * Parses the value and returns it in a specific format based on type |
| 90 | + * safely handeling nulls and objects. |
47 | 91 | * @param {string} key - key of the object info |
48 | 92 | * @param {string|number|object|undefined} value - value of the object info |
49 | | - * @returns {vnode} - value of object based on its type |
| 93 | + * @returns {string} - string representation of the value passed |
50 | 94 | */ |
51 | 95 | const infoPretty = (key, value) => { |
| 96 | + if (value == null) { |
| 97 | + return '-'; |
| 98 | + } |
| 99 | + |
52 | 100 | if (DATE_FIELDS.includes(key)) { |
53 | 101 | return prettyFormatDate(value); |
54 | | - } else if (Array.isArray(value)) { |
| 102 | + } |
| 103 | + |
| 104 | + if (Array.isArray(value)) { |
55 | 105 | return value.length > 0 |
56 | 106 | ? value.join(', ') |
57 | 107 | : '-'; |
58 | 108 | } |
59 | | - return h('', value); |
| 109 | + |
| 110 | + if (typeof value === 'object') { |
| 111 | + return JSON.stringify(value); |
| 112 | + } |
| 113 | + |
| 114 | + return String(value); |
60 | 115 | }; |
| 116 | + |
| 117 | +/** |
| 118 | + * Default function to configure the info row vnode attributes |
| 119 | + * @typedef {import('/js/src/index.js').Notification} Notification |
| 120 | + * @param {Notification} notification - Notification API from WebUI framework |
| 121 | + * @returns {function(string, string): object} object containing the constructed vnode attributes |
| 122 | + */ |
| 123 | +export const defaultRowAttributes = (notification) => |
| 124 | + (key, value) => ({ |
| 125 | + onclick: async (e) => { |
| 126 | + // to allowing the default behaviour for clicking multiple times |
| 127 | + const clickCount = e.detail; |
| 128 | + if (clickCount === 1) { |
| 129 | + if (!isContextSecure()) { |
| 130 | + return; |
| 131 | + } |
| 132 | + |
| 133 | + try { |
| 134 | + await copyToClipboard(value); |
| 135 | + notification.show('Value has been successfully copied to clipboard', 'success', 1500); |
| 136 | + } catch (error) { |
| 137 | + notification.show(`Failed to copy to clipboard: ${error.message}`, 'danger', 1500); |
| 138 | + } |
| 139 | + } |
| 140 | + }, |
| 141 | + title: `Copy ${key}`, |
| 142 | + }); |
0 commit comments