import { CallExpressionShape, type ObjectShape, Plugin, type Record as RecordType } from '@servicenow/sdk-build-core' import { NowIdShape } from '../now-id-plugin' import { resolveComponentToSysId, resolveComponentToName } from './dashboard-component-resolver' import { DEFAULT_PROPERTY_VALUES, removeRestrictedValues } from './dashboard-component-property-defaults' const transformWidget = (widget: RecordType) => widget.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(widget)), component: $.map((v) => resolveComponentToName(v.toString().getValue())), componentProps: $.from('component_props') .map((v) => { const json = v.ifString()?.getValue() const props = json ? JSON.parse(json) : v.getValue() return removeRestrictedValues(props) }) .def(DEFAULT_PROPERTY_VALUES), height: $.from('h').toNumber(), width: $.from('w').toNumber(), position: $.map(() => ({ x: widget.get('x').toNumber().getValue(), y: widget.get('y').toNumber().getValue(), })), })) const createWidgetProperties = (widgetData: ObjectShape, canvas: ObjectShape) => widgetData.transform(({ $ }) => ({ canvas: $.val(canvas), component: $.map((v) => resolveComponentToSysId(v.toString().getValue())), component_props: $.from('componentProps').map((v) => { const props = v.getValue() || {} return JSON.stringify({ ...DEFAULT_PROPERTY_VALUES, ...props }) }), h: $.from('height').toNumber(), w: $.from('width').toNumber(), x: $.from('position').map((v) => { const posObj = v.asObject() return posObj.get('x').toNumber().getValue() }), y: $.from('position').map((v) => { const posObj = v.asObject() return posObj.get('y').toNumber().getValue() }), })) /** * Creates a Dashboard (`par_dashboard`). * * @param config - an object containing the following properties: * * **$id** - unique id for the record, typically using `Now.ID["value"]` * * **name** - The name of the dashboard * * **tabs**? - Array of dashboard tabs (each with $id, name, and widgets array) * * **visibilities**? - Array of visibility rules (each with $id and experience reference) * * **permissions**? - Array of user permissions (each with $id, user, can_read, can_write, can_share, owner) */ export const DashboardPlugin = Plugin.create({ name: 'DashboardPlugin', records: { par_dashboard: { relationships: { par_dashboard_tab: { via: 'dashboard', descendant: true, relationships: { par_dashboard_canvas: { via: 'dashboard_tab', descendant: true, relationships: { par_dashboard_widget: { via: 'canvas', descendant: true, }, }, }, }, }, par_dashboard_canvas: { via: 'dashboard', descendant: true, relationships: { par_dashboard_widget: { via: 'canvas', descendant: true, }, }, }, par_dashboard_visibility: { via: 'dashboard', descendant: true, }, par_dashboard_permission: { via: 'dashboard', descendant: true, relationships: { sys_user: { via: 'user', inverse: true, }, sys_user_group: { via: 'group', inverse: true, }, sys_user_role: { via: 'role', inverse: true, }, }, }, }, toShape(record, { descendants }) { const topCanvas = descendants.query('par_dashboard_canvas').find((c) => { const tabField = c.get('dashboard_tab') return ( !tabField || tabField.getValue() === undefined || tabField.getValue() === null || tabField.getValue() === '' ) }) const topWidgets = topCanvas ? descendants .query('par_dashboard_widget') .filter((w) => { const canvasField = w.get('canvas') const canvasValue = canvasField.isRecord() ? canvasField.asRecord().getId().getValue() : canvasField.asRecordId().getValue() return canvasValue === topCanvas.getId().getValue() }) .map(transformWidget) : [] const topLayout = topWidgets.length > 0 ? { widgets: topWidgets } : undefined const tabs = descendants .query('par_dashboard_tab') .sort((a, b) => a.get('order').toNumber().getValue() - b.get('order').toNumber().getValue()) .map((tab) => { // Find the canvas for this tab const canvas = descendants.query('par_dashboard_canvas').find((c) => { const tabField = c.get('dashboard_tab') if ( !tabField || tabField.getValue() === undefined || tabField.getValue() === null || tabField.getValue() === '' ) { return false } const tabValue = tabField.isRecord() ? tabField.asRecord().getId().getValue() : tabField.asRecordId().getValue() return tabValue === tab.getId().getValue() }) const widgets = canvas ? descendants .query('par_dashboard_widget') .filter((w) => { const canvasField = w.get('canvas') const canvasValue = canvasField.isRecord() ? canvasField.asRecord().getId().getValue() : canvasField.asRecordId().getValue() return canvasValue === canvas.getId().getValue() }) .map(transformWidget) : [] return tab.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(tab)), name: $, active: $.toBoolean().def(true), widgets: $.val(widgets), })) }) const visibilities = descendants.query('par_dashboard_visibility').map((visibility) => visibility.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(visibility)), experience: $.from('experience').def(''), })) ) const permissions = descendants.query('par_dashboard_permission').map((permission) => permission.transform(({ $ }) => { const hasUser = permission.get('user').asString().getValue() || false const hasRole = permission.get('role').asString().getValue() || false const hasGroup = permission.get('group').asString().getValue() || false const permissionSubject = hasUser ? { user: $ } : hasRole ? { role: $ } : hasGroup ? { group: $ } : {} return { $id: $.val(NowIdShape.from(permission)), canRead: $.from('can_read').toBoolean(), canShare: $.from('can_share').toBoolean(), canWrite: $.from('can_write').toBoolean(), owner: $.toBoolean(), ...permissionSubject, } }) ) const dashboardShape = record.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(record)), name: $, active: $.toBoolean().def(true), description: $.def(''), certified: $.toBoolean().def(false), tabs: $.val(tabs).def([]), visibilities: $.val(visibilities).def([]), permissions: $.val(permissions).def([]), topLayout: $.val(topLayout), })) return { success: true, value: new CallExpressionShape({ source: record, callee: 'Dashboard', args: [dashboardShape], }), } }, }, par_dashboard_tab: { relationships: { par_dashboard_canvas: { via: 'dashboard_tab', descendant: true, }, }, }, par_dashboard_canvas: { coalesce: ['dashboard', 'dashboard_tab'], relationships: { par_dashboard_widget: { via: 'canvas', descendant: true, }, }, }, par_dashboard_visibility: { coalesce: ['dashboard', 'experience'], }, par_dashboard_widget: {}, par_dashboard_user_metadata: {}, par_dashboard_permission: { coalesce: ['dashboard', 'user', 'group', 'role'], }, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(source, { factory }) { if (source.getCallee() !== 'Dashboard') { return { success: false } } const arg = source.getArgument(0).asObject() const dashboard = await factory.createRecord({ source: source, table: 'par_dashboard', explicitId: arg.get('$id'), properties: arg.transform(({ $ }) => ({ name: $, active: $.def(true), description: $.def(''), certified: $.def(false), })), }) const topLayoutRecords: RecordType[] = [] // Process top layout if (arg.get('topLayout').ifObject()) { const topLayout = arg.get('topLayout').asObject() const topCanvas = await factory.createRecord({ source, table: 'par_dashboard_canvas', properties: { dashboard: dashboard, }, }) const topWidgets = topLayout.get('widgets').ifArray()?.getElements() ?? [] topLayoutRecords.push(topCanvas) for (const widgetObj of topWidgets) { const widgetData = widgetObj.asObject() const widget = await factory.createRecord({ source: widgetObj, table: 'par_dashboard_widget', explicitId: widgetData.get('$id'), properties: createWidgetProperties(widgetData, topCanvas), }) topLayoutRecords.push(widget) } } // Process tabs const tabs = arg.get('tabs').ifArray()?.getElements() ?? [] const tabRecords: RecordType[] = [] for (const [index, tabObj] of tabs.entries()) { const tabData = tabObj.asObject() const tab = await factory.createRecord({ source: tabObj, table: 'par_dashboard_tab', explicitId: tabData.get('$id'), properties: tabData.transform(({ $ }) => ({ dashboard: $.val(dashboard), name: $, order: $.val(index), active: $.def(true), })), }) tabRecords.push(tab) // Create canvas for this tab const canvas = await factory.createRecord({ source: tabObj, table: 'par_dashboard_canvas', properties: { dashboard: dashboard, dashboard_tab: tab, }, }) tabRecords.push(canvas) // Process widgets for this tab's canvas const widgets = tabData.get('widgets').ifArray()?.getElements() ?? [] for (const widgetObj of widgets) { const widgetData = widgetObj.asObject() const widget = await factory.createRecord({ source: widgetObj, table: 'par_dashboard_widget', explicitId: widgetData.get('$id'), properties: createWidgetProperties(widgetData, canvas), }) tabRecords.push(widget) } } // Process visibilities const visibilities = arg.get('visibilities').ifArray()?.getElements() ?? [] const visibilityRecords: RecordType[] = [] if (visibilities.length === 0) { // Add default visibility if none provided visibilityRecords.push( await factory.createRecord({ source: source, table: 'par_dashboard_visibility', properties: { dashboard: dashboard, experience: '08c73d60537101100834ddeeff7b1287', }, }) ) } else { for (const visibility of visibilities) { const visibilityObj = visibility.asObject() visibilityRecords.push( await factory.createRecord({ source: visibility, table: 'par_dashboard_visibility', explicitId: visibilityObj.get('$id'), properties: visibilityObj.transform(({ $ }) => { return { dashboard: $.val(dashboard), experience: $.from('experience'), } }), }) ) } } // Process permissions const permissions = arg.get('permissions').ifArray()?.getElements() ?? [] const permissionRecords: RecordType[] = [] for (const permission of permissions) { const permissionObj = permission.asObject() // Build properties object const properties: Record = { dashboard: dashboard, can_read: permissionObj.get('canRead').ifUndefined() ? true : permissionObj.get('canRead').getValue(), can_write: permissionObj.get('canWrite').ifUndefined() ? false : permissionObj.get('canWrite').getValue(), can_share: permissionObj.get('canShare').ifUndefined() ? false : permissionObj.get('canShare').getValue(), owner: permissionObj.get('owner').ifUndefined() ? false : permissionObj.get('owner').getValue(), } // Handle user reference (optional) const userValue = permissionObj.get('user') if (!userValue.ifUndefined()) { properties['user'] = userValue } // Handle group reference (optional) const groupValue = permissionObj.get('group') if (!groupValue.ifUndefined()) { properties['group'] = groupValue } // Handle role reference (optional) const roleValue = permissionObj.get('role') if (!roleValue.ifUndefined()) { properties['role'] = roleValue } permissionRecords.push( await factory.createRecord({ source: permission, table: 'par_dashboard_permission', explicitId: permissionObj.get('$id'), properties: properties, }) ) } return { success: true, value: dashboard.with( ...topLayoutRecords, ...tabRecords, ...visibilityRecords, ...permissionRecords ), } }, }, ], })