diff --git a/client/web/admin/package.json b/client/web/admin/package.json index c67b28e0c6..064b4f03fa 100644 --- a/client/web/admin/package.json +++ b/client/web/admin/package.json @@ -45,7 +45,6 @@ "vue-echarts": "^6.2.3", "vue-native-websocket": "^2.0.15", "vue-router": "3.1.5", - "vue2-brace-editor": "^2.0.2", "vue2-dropzone": "^3.6.0", "vuedraggable": "^2.23.2", "vuex": "3.1.2" diff --git a/client/web/admin/src/components/Apigw/Profiler/CProfilerHitInfo.vue b/client/web/admin/src/components/Apigw/Profiler/CProfilerHitInfo.vue index 32802cc6ac..a865cd38d0 100644 --- a/client/web/admin/src/components/Apigw/Profiler/CProfilerHitInfo.vue +++ b/client/web/admin/src/components/Apigw/Profiler/CProfilerHitInfo.vue @@ -214,7 +214,7 @@ - diff --git a/client/web/compose/src/components/Common/FieldPicker.vue b/client/web/compose/src/components/Common/FieldPicker.vue index 613fdd7336..de430be569 100644 --- a/client/web/compose/src/components/Common/FieldPicker.vue +++ b/client/web/compose/src/components/Common/FieldPicker.vue @@ -133,7 +133,9 @@ export default { let sysFields = [] - if (!this.disableSystemFields) { + if (this.disableSystemFields && mFields) { + mFields = mFields.filter(({ isSystem }) => !isSystem) + } else if (!this.fieldSubset) { sysFields = this.module.systemFields().map(sf => { sf.label = this.$t(`field:system.${sf.name}`) return sf @@ -142,10 +144,6 @@ export default { if (this.systemFields) { sysFields = sysFields.filter(({ name }) => this.systemFields.find(sf => sf === name)) } - } else { - if (mFields) { - mFields = mFields.filter(({ isSystem }) => !isSystem) - } } if (!this.disableSorting && mFields) { diff --git a/client/web/compose/src/components/ModuleFields/Viewer/DateTime.vue b/client/web/compose/src/components/ModuleFields/Viewer/DateTime.vue index f4ae6afa0f..bdee25eb85 100644 --- a/client/web/compose/src/components/ModuleFields/Viewer/DateTime.vue +++ b/client/web/compose/src/components/ModuleFields/Viewer/DateTime.vue @@ -10,10 +10,6 @@ export default { } return this.value ? this.field.formatValue(this.value) : null }, - - classes () { - return this.field.isMulti ? ['multiline'] : [] - }, }, } diff --git a/client/web/compose/src/components/ModuleFields/Viewer/Email.vue b/client/web/compose/src/components/ModuleFields/Viewer/Email.vue index 0e291f0c56..d97f5c494c 100644 --- a/client/web/compose/src/components/ModuleFields/Viewer/Email.vue +++ b/client/web/compose/src/components/ModuleFields/Viewer/Email.vue @@ -1,5 +1,5 @@ + diff --git a/client/web/compose/src/views/Admin/Pages/Edit.vue b/client/web/compose/src/views/Admin/Pages/Edit.vue index 366c8700bf..c51ea08397 100644 --- a/client/web/compose/src/views/Admin/Pages/Edit.vue +++ b/client/web/compose/src/views/Admin/Pages/Edit.vue @@ -232,11 +232,15 @@ cols="12" >
-
- + - {{ $t('page-layout.title') }} - {{ $t('page-layout.handle') }} - @@ -284,7 +285,6 @@ class="text-secondary" /> - @@ -294,7 +294,6 @@ :state="layoutTitleState(layout.meta.title)" @input="layout.meta.updated = true" /> - - @@ -314,7 +312,6 @@ :state="layoutHandleState(layout.handle)" @input="layout.meta.updated = true" /> - - - - - - - - {{ $t('general:label.add') }} - - -
+ + @@ -599,11 +580,14 @@ -
- +
- - - - - - - - -
- - - - {{ $t('general:label.add') }} - - -
+ + @@ -1028,7 +990,6 @@ export default { on: this.$t('general:label.yes'), off: this.$t('general:label.no'), }, - abortableRequests: [], } }, diff --git a/client/web/compose/src/views/Public/Pages/Records/View.vue b/client/web/compose/src/views/Public/Pages/Records/View.vue index 7e9579b8e8..72bcf7f693 100644 --- a/client/web/compose/src/views/Public/Pages/Records/View.vue +++ b/client/web/compose/src/views/Public/Pages/Records/View.vue @@ -114,6 +114,7 @@ import { mapGetters, mapActions } from 'vuex' import Grid from 'corteza-webapp-compose/src/components/Public/Page/Grid' import RecordToolbar from 'corteza-webapp-compose/src/components/Common/RecordToolbar' import record from 'corteza-webapp-compose/src/mixins/record' +import page from 'corteza-webapp-compose/src/mixins/page' import { compose, NoID } from '@cortezaproject/corteza-js' import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter' import axios from 'axios' @@ -133,6 +134,7 @@ export default { mixins: [ // The record mixin contains all of the logic for creating/editing/deleting/undeleting the record record, + page, ], beforeRouteLeave (to, from, next) { @@ -153,28 +155,12 @@ export default { }, props: { - namespace: { - type: compose.Namespace, - required: true, - }, - module: { type: compose.Module, required: false, default: () => ({}), }, - page: { - type: compose.Page, - required: true, - }, - - recordID: { - type: String, - required: false, - default: '', - }, - // When creating from related record blocks refRecord: { type: compose.Record, @@ -206,10 +192,7 @@ export default { inEditing: false, inCreating: false, - layouts: [], - layout: undefined, layoutButtons: new Set(), - blocks: undefined, recordNavigation: { prev: undefined, @@ -223,7 +206,6 @@ export default { computed: { ...mapGetters({ getNextAndPrevRecord: 'ui/getNextAndPrevRecord', - getPageLayouts: 'pageLayout/getByPageID', previousPages: 'ui/previousPages', modalPreviousPages: 'ui/modalPreviousPages', }), @@ -356,14 +338,6 @@ export default { }, }, - mounted () { - this.$root.$on('refetch-record-blocks', this.refetchRecordBlocks) - - if (this.showRecordModal) { - this.$root.$on('bv::modal::hide', this.checkUnsavedChangesOnModal) - } - }, - beforeDestroy () { this.abortRequests() this.destroyEvents() @@ -377,6 +351,23 @@ export default { popModalPreviousPage: 'ui/popModalPreviousPage', }), + createEvents () { + this.$root.$on('refetch-record-blocks', this.refetchRecordBlocks) + this.$root.$on('record-field-change', this.validateBlocksVisibilityCondition) + + if (this.showRecordModal) { + this.$root.$on('bv::modal::hide', this.checkUnsavedChangesOnModal) + } + }, + + validateBlocksVisibilityCondition ({ fieldName }) { + const { blocks = [] } = this.page + + if (blocks.some(({ meta = {} }) => ((meta.visibility || {}).expression).includes(fieldName))) { + this.updateBlocks() + } + }, + async loadRecord (recordID = this.recordID) { if (!this.page) { return @@ -517,19 +508,7 @@ export default { evaluateLayoutExpressions (variables = {}) { const expressions = {} variables = { - user: this.$auth.user, - record: this.record ? this.record.serialize() : {}, - screen: { - width: window.innerWidth, - height: window.innerHeight, - userAgent: navigator.userAgent, - breakpoint: this.getBreakpoint(), // This is from a global mixin uiHelpers - }, - oldLayout: this.layout, - layout: undefined, - isView: !this.inEditing && !this.inCreating, - isCreate: this.inCreating, - isEdit: this.inEditing && !this.inCreating, + ...this.expressionVariables, ...variables, } @@ -550,40 +529,7 @@ export default { }) }, - async determineLayout (pageLayoutID, variables = {}) { - // Clear stored records so they can be refetched with latest values - this.clearRecordSet() - let expressions = {} - - // Only evaluate if one of the layouts has an expressions variable - if (this.layouts.some(({ config = {} }) => config.visibility.expression)) { - expressions = await this.evaluateLayoutExpressions(variables) - } - - // Check layouts for expressions/roles and find the first one that fits - const matchedLayout = this.layouts.find(l => { - if (pageLayoutID && l.pageLayoutID !== pageLayoutID) return - - const { expression, roles = [] } = l.config.visibility - - if (expression && !expressions[l.pageLayoutID]) return false - - if (!roles.length) return true - - return this.$auth.user.roles.some(roleID => roles.includes(roleID)) - }) - - if (!matchedLayout) { - this.toastWarning(this.$t('notification:page.page-layout.notFound.view')) - return this.$router.go(-1) - } - - if (this.layout && matchedLayout.pageLayoutID === this.layout.pageLayoutID) { - return - } - - this.layout = matchedLayout - + handleRecordButtons () { const { config = {} } = this.layout const { buttons = [] } = config @@ -593,20 +539,6 @@ export default { } return acc }, new Set()) - - const tempBlocks = [] - const { blocks = [] } = this.layout || {} - - blocks.forEach(({ blockID, xywh }) => { - const block = this.page.blocks.find(b => b.blockID === blockID) - - if (block) { - block.xywh = xywh - tempBlocks.push(block) - } - }) - - this.blocks = tempBlocks }, refetchRecordBlocks () { @@ -653,6 +585,7 @@ export default { destroyEvents () { this.$root.$off('refetch-record-blocks', this.refetchRecordBlocks) + this.$root.$off('record-field-change', this.validateBlocksVisibilityCondition) if (this.showRecordModal) { this.$root.$off('bv::modal::hide', this.checkUnsavedChangesOnModal) diff --git a/client/web/compose/src/views/Public/Pages/View.vue b/client/web/compose/src/views/Public/Pages/View.vue index 8b7ec05bad..eec70f4b26 100644 --- a/client/web/compose/src/views/Public/Pages/View.vue +++ b/client/web/compose/src/views/Public/Pages/View.vue @@ -88,7 +88,9 @@ import Grid from 'corteza-webapp-compose/src/components/Public/Page/Grid' import RecordModal from 'corteza-webapp-compose/src/components/Public/Record/Modal' import MagnificationModal from 'corteza-webapp-compose/src/components/Public/Page/Block/Modal' import PageTranslator from 'corteza-webapp-compose/src/components/Admin/Page/PageTranslator' -import { compose, NoID } from '@cortezaproject/corteza-js' +import page from 'corteza-webapp-compose/src/mixins/page' +import { NoID } from '@cortezaproject/corteza-js' +import { fetchID } from 'corteza-webapp-compose/src/lib/block' export default { i18nOptions: { @@ -102,6 +104,10 @@ export default { MagnificationModal, }, + mixins: [ + page, + ], + beforeRouteLeave (to, from, next) { this.setPreviousPages([]) next() @@ -123,30 +129,8 @@ export default { next() }, - props: { - namespace: { // via router-view - type: compose.Namespace, - required: true, - }, - - page: { // via route-view - type: compose.Page, - required: true, - }, - - // We're using recordID to check if we need to display router-view or grid component - recordID: { - type: String, - default: '', - }, - }, - data () { return { - layouts: [], - layout: undefined, - blocks: undefined, - pageTitle: '', } }, @@ -155,12 +139,9 @@ export default { ...mapGetters({ recordPaginationUsable: 'ui/recordPaginationUsable', getPageLayouts: 'pageLayout/getByPageID', + namespaces: 'namespace/set', }), - isRecordPage () { - return this.recordID || this.$route.name === 'page.record.create' - }, - module () { if (this.page.moduleID && this.page.moduleID !== NoID) { return this.$store.getters['module/getByID'](this.page.moduleID) @@ -194,8 +175,9 @@ export default { handler (pageID) { if (pageID === NoID) return - this.layouts = [] + this.layouts = this.getPageLayouts(this.page.pageID) this.layout = undefined + this.pageTitle = this.page.title if (!this.isRecordPage) { this.determineLayout() @@ -213,10 +195,6 @@ export default { }, }, - mounted () { - this.$root.$on('refetch-records', this.refetchRecords) - }, - beforeDestroy () { this.destroyEvents() this.setDefaultValues() @@ -232,6 +210,10 @@ export default { clearRecordSet: 'record/clearSet', }), + createEvents () { + this.$root.$on('refetch-records', this.refetchRecords) + }, + evaluateLayoutExpressions () { const expressions = {} const variables = { @@ -297,19 +279,17 @@ export default { this.pageTitle = title || handle || this.$t('navigation:noPageTitle') document.title = [title, this.namespace.name, this.$t('general:label.app-name.public')].filter(v => v).join(' | ') - const tempBlocks = [] - const { blocks = [] } = this.layout || {} - - blocks.forEach(({ blockID, xywh }) => { - const block = this.page.blocks.find(b => b.blockID === blockID) + this.blocks = (this.layout || {}).blocks.map(({ blockID, meta, xywh }) => { + const block = this.fetchBlockData({ + blockID, + meta, + }) if (block) { block.xywh = xywh - tempBlocks.push(block) + return block } - }) - - this.blocks = tempBlocks + }).filter(b => b) }, refetchRecords () { @@ -317,6 +297,18 @@ export default { this.$root.$emit(this.page.moduleID !== NoID ? 'refetch-record-blocks' : `refetch-non-record-blocks:${this.page.pageID}`) }, + fetchBlockData ({ blockID, meta = {} }) { + blockID = fetchID({ blockID, meta }) + + if (meta.namespaceID) { + const { blocks = [] } = this.namespaces.find((n) => n.namespaceID === this.namespace.namespaceID) || {} + + return blocks.find((b) => fetchID(b) === blockID) + } + + return this.page.blocks.find((b) => fetchID(b) === blockID) + }, + setDefaultValues () { this.layouts = [] this.layout = undefined diff --git a/client/web/compose/yarn.lock b/client/web/compose/yarn.lock index 8c8d994ead..958d9180ae 100644 --- a/client/web/compose/yarn.lock +++ b/client/web/compose/yarn.lock @@ -12487,13 +12487,6 @@ vue2-ace-editor@^0.0.15: dependencies: brace "^0.11.0" -vue2-brace-editor@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vue2-brace-editor/-/vue2-brace-editor-2.0.2.tgz#d9cd06f201c3e7af8382fd14a3eb8c2c49971436" - integrity sha512-ADJo1dccmIjq3ZejUNFJT/GpTQV5+TPOkC7xArBbVooVSjL8NOgRbqQ1Ix1LEW2S0hgy1y2ac8VHpZZdV4hjug== - dependencies: - brace "^0.11.0" - vue2-dropzone@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/vue2-dropzone/-/vue2-dropzone-3.6.0.tgz#b4bb4b64de1cbbb3b88f04b24878e06780a51546" diff --git a/client/web/reporter/src/components/Common/Prefilter.vue b/client/web/reporter/src/components/Common/Prefilter.vue index 2bbbc24201..1a7575bff6 100644 --- a/client/web/reporter/src/components/Common/Prefilter.vue +++ b/client/web/reporter/src/components/Common/Prefilter.vue @@ -1,5 +1,5 @@ diff --git a/client/web/workflow/src/components/ExpressionTable.vue b/client/web/workflow/src/components/ExpressionTable.vue index c09893154f..87db633526 100644 --- a/client/web/workflow/src/components/ExpressionTable.vue +++ b/client/web/workflow/src/components/ExpressionTable.vue @@ -97,8 +97,8 @@ - + + diff --git a/lib/vue/src/components/wrapper/index.ts b/lib/vue/src/components/wrapper/index.ts new file mode 100644 index 0000000000..a52af369ed --- /dev/null +++ b/lib/vue/src/components/wrapper/index.ts @@ -0,0 +1 @@ +export { default as CFormTableWrapper } from './CFormTableWrapper.vue' diff --git a/locale/en/corteza-webapp-admin/general.yaml b/locale/en/corteza-webapp-admin/general.yaml index c89c4141c0..74586582ea 100644 --- a/locale/en/corteza-webapp-admin/general.yaml +++ b/locale/en/corteza-webapp-admin/general.yaml @@ -1,4 +1,5 @@ label: + add: Add general: yes: Yes no: No diff --git a/locale/en/corteza-webapp-admin/system.users.yaml b/locale/en/corteza-webapp-admin/system.users.yaml index d305ad4762..919f8d41df 100644 --- a/locale/en/corteza-webapp-admin/system.users.yaml +++ b/locale/en/corteza-webapp-admin/system.users.yaml @@ -5,6 +5,7 @@ editor: type: Type info: confirmEmail: Confirm email address + id: ID createdAt: Created at createdBy: Created by delete: Delete diff --git a/locale/en/corteza-webapp-compose/block.yaml b/locale/en/corteza-webapp-compose/block.yaml index 67569b2a14..2947df7a5a 100644 --- a/locale/en/corteza-webapp-compose/block.yaml +++ b/locale/en/corteza-webapp-compose/block.yaml @@ -5,6 +5,8 @@ selector: clone: ref: Clone with references noRef: Clone without references + selectableGlobalBlocks: + placeholder: Select global block from other pages automation: addPlaceholderLabel: Add placeholder (dummy button) availableScriptsAndWorkflow: Available scripts and workflows ({{count}}) @@ -97,8 +99,9 @@ chart: display: Chart to display inside this block drillDown: label: Drill down - description: If a record list is selected, it will be used to display the drill down data. If no record list is selected the drill down data will be shown in a modal. + description: If a record list is selected, it will be used to display the drill down data. If no record list is selected, the drill down data will be shown in a modal. openInModal: Open in modal + configureColumns: Choose the default fields edit: label: Edit chart dimension: @@ -118,6 +121,7 @@ chart: chartId: Chart preview (ID {{0}}) searchPlaceholder: Type here to search all charts in this namespace openInBuilder: Open in chart builder + openChartList: Open chart list content: ok: Ok label: Content @@ -132,6 +136,20 @@ general: descriptionPlaceholder: Block description headerStyle: Block header style (color) magnifyLabel: Block Magnification + visibility: + label: Visibility + roles: + label: Roles + placeholder: Pick roles that the block will be shown to + condition: + label: Condition + placeholder: When will the block be shown + description: + record-page: "You can use {{0}}, {{1}}, {{2}}, {{3}} inside the expression, for example: {{4}} or {{5}}" + non-record-page: "You can use {{0}}, {{1}} inside the expression, for example: {{2}} or {{3}}" + tooltip: + performance: + condition: Using visibility conditions will impact performance magnifyOptions: disabled: Disabled modal: Modal @@ -178,6 +196,8 @@ general: tooltip: dragAndDrop: Drag and drop to change order translations: Field translations + globalBlock: + label: Global block iframe: label: IFrame pickURLField: Pick an URL field @@ -190,7 +210,7 @@ metric: defaultMetricLabel: Unnamed metric drillDown: label: Drill down - description: If a record list is selected, it will be used to display the drill down data. If no record list is selected the drill down data will be shown in a modal. + description: If a record list is selected, it will be used to display the drill down data. If no record list is selected, the drill down data will be shown in a modal. openInModal: Open in modal edit: bucketLabel: Bucket size @@ -449,6 +469,7 @@ recordList: withPresortedRecords: with presorted ({{0}}) record record: draggable: Can drag & drop records to order them + editMode: Users will be able to open record in edit mode when a row is clicked fullPageNavigation: Full page navigation hideAddButton: Users will be able to add new records hideImportButton: Users will be able to import records @@ -481,6 +502,9 @@ recordList: openInSameTab: Open record in the same tab openInNewTab: Open record in a new tab openInModal: Open record in a modal + createInSameTab: Create record in the same tab + createInNewTab: Create record in a new tab + createInModal: Create record in a modal enableBulkRecordEdit: Enable bulk record edit linkToParent: Link to parent record buttons: Record buttons @@ -494,6 +518,10 @@ recordList: permissions: Permissions recordDisplayOptions: On record click recordSelectorDisplayOptions: On record selector click + addRecordOptions: On add record click + textStyles: Text Styles + configureNonWrappingFelids: Configure non-wrapping fields + showFullText: Show full text recordPage: record page refField: footnote: Field that links records with the parent record @@ -800,7 +828,6 @@ tabs: label: Tabs alertTitle: Set a title for your block title: Tabs - addTab: + Add tab: Tab selectBlock: Choose a block noTabs: No tabs configured @@ -849,4 +876,4 @@ tabs: addBlock: Add new block noConfiguration: Block not configured correctly -interpolationFootnote: Variables like {{0}}, {{1}}, {{2}} and {{3}} are evaluated (when available) \ No newline at end of file +interpolationFootnote: Variables like {{0}}, {{1}}, {{2}} and {{3}} are evaluated (when available) diff --git a/locale/en/corteza-webapp-compose/module.yaml b/locale/en/corteza-webapp-compose/module.yaml index cca1a65bd7..155fd94e25 100644 --- a/locale/en/corteza-webapp-compose/module.yaml +++ b/locale/en/corteza-webapp-compose/module.yaml @@ -71,7 +71,7 @@ edit: steps: recordList: Page with record list recordPage: Record page - systemFields: 'System fields:' + systemFields: 'System fields' tooltip: attributes: Select if the field can hold multiple values, if it's required field or if contains sensitive data name: Should be at least 2 characters long. Can contain only letters, numbers, underscores and dots. Must end with letter or number diff --git a/locale/en/corteza-webapp-compose/page.yaml b/locale/en/corteza-webapp-compose/page.yaml index cfdb1cf3e1..0404c9543c 100644 --- a/locale/en/corteza-webapp-compose/page.yaml +++ b/locale/en/corteza-webapp-compose/page.yaml @@ -70,6 +70,7 @@ block: sample: Sample title: Add new block invalid-handle-characters: Should be at least 2 characters long. Can contain only letters, numbers, underscores and dots. Must end with letter or number + referencedGlobalBlock: This block may be used in other pages changeBlock: Change existing block referencedBlock: This block is used in other layouts copyOf: "Copy of {{title}}" diff --git a/server/assets/src/scss/main/18201141_custom_webapp.scss b/server/assets/src/scss/main/18201141_custom_webapp.scss index eb2d4cdd5a..44ab056e16 100644 --- a/server/assets/src/scss/main/18201141_custom_webapp.scss +++ b/server/assets/src/scss/main/18201141_custom_webapp.scss @@ -294,63 +294,6 @@ fieldset.required { } } -// Remove from here when all ace-editors use c-ace-editor -.ace_editor { - color: var(--black) !important; - background-color: var(--white) !important; - border-radius: 0.25rem; - border: 2px solid var(--extra-light); - - .ace_cursor { - border-left: 2px solid var(--black); - } - - .ace_gutter { - background-color: var(--light) !important; - color: var(--black) !important; - - .ace_gutter-active-line { - background-color: var(--extra-light) !important; - } - } - - .ace_text { - color: var(--black) !important; - } - - .ace_keyword { - color: var(--primary) !important; - } - - .ace_string { - color: var(--secondary) !important; - } - - .ace_variable { - color: var(--warning) !important; - } - - .ace_type { - color: var(--secondary) !important; - } - - .ace_constant { - color: var(--primary) !important; - } - - .ace_tag { - color: var(--primary) !important; - } - - .ace_attribute { - color: var(--warning) !important; - } - - .ace_comment { - color: var(--extra-light) !important; - } -} - // Supporting CSS to improve print-to-PDF option @media print { @page { @@ -369,4 +312,4 @@ fieldset.required { break-inside: avoid; width: 100%; } -} \ No newline at end of file +} diff --git a/server/compose/envoy/store_decode.go b/server/compose/envoy/store_decode.go index 7251ab2e2f..39dc87db1f 100644 --- a/server/compose/envoy/store_decode.go +++ b/server/compose/envoy/store_decode.go @@ -163,28 +163,28 @@ func decodePageRefs(p *types.Page) (refs map[string]envoyx.Ref) { for index, b := range p.Blocks { switch b.Kind { case "RecordList": - refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b.Options, index)) case "Automation": - refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b.Options, index)) case "RecordOrganizer": - refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b.Options, index)) case "Chart": - refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b.Options, index)) case "Calendar": - refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b.Options, index)) case "Metric": - refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b.Options, index)) case "Comment": - refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b.Options, index)) case "Progress": - refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b, index)) + refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b.Options, index)) } } diff --git a/server/compose/envoy/yaml_decode.gen.go b/server/compose/envoy/yaml_decode.gen.go index c368e1d186..d90405777d 100644 --- a/server/compose/envoy/yaml_decode.gen.go +++ b/server/compose/envoy/yaml_decode.gen.go @@ -1306,6 +1306,25 @@ func (d *auxYamlDoc) unmarshalNamespaceNode(dctx documentContext, n *yaml.Node, switch strings.ToLower(k.Value) { + case "blocks": + + // Handle custom node decoder + // + // The decoder may update the passed resource with arbitrary values + // as well as provide additional references and identifiers for the node. + var ( + auxRefs map[string]envoyx.Ref + auxIdents envoyx.Identifiers + ) + auxRefs, auxIdents, err = d.unmarshalNamespaceBlocksNode(r, n) + if err != nil { + return err + } + refs = envoyx.MergeRefs(refs, auxRefs) + ii = ii.Merge(auxIdents) + + break + case "id": // Handle identifiers err = y7s.DecodeScalar(n, "id", &auxNodeValue) diff --git a/server/compose/envoy/yaml_decode.go b/server/compose/envoy/yaml_decode.go index 1135ad5160..ac5a3da8c1 100644 --- a/server/compose/envoy/yaml_decode.go +++ b/server/compose/envoy/yaml_decode.go @@ -73,40 +73,55 @@ func (d *auxYamlDoc) unmarshalPageBlocksNode(r *types.Page, n *yaml.Node) (refs refs = map[string]envoyx.Ref{} for index, b := range r.Blocks { - switch b.Kind { - case "RecordList": - refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(b, index)) + idents, err = unmarshalResourceBlocksNode(b.Kind, b.Options, index, refs) + } + return +} + +func (d *auxYamlDoc) unmarshalNamespaceBlocksNode(r *types.Namespace, n *yaml.Node) (refs map[string]envoyx.Ref, idents envoyx.Identifiers, err error) { + refs = map[string]envoyx.Ref{} + + for index, gBlock := range r.Blocks { + idents, err = unmarshalResourceBlocksNode(gBlock.Kind, gBlock.Options, index, refs) + } + + return +} - case "Automation": - refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(b, index)) +func unmarshalResourceBlocksNode(kind string, options map[string]interface{}, index int, refs map[string]envoyx.Ref) (idents envoyx.Identifiers, err error) { + switch kind { + case "RecordList": + refs = envoyx.MergeRefs(refs, getPageBlockRecordListRefs(options, index)) - case "RecordOrganizer": - refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(b, index)) + case "Automation": + refs = envoyx.MergeRefs(refs, getPageBlockAutomationRefs(options, index)) - case "Chart": - refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(b, index)) + case "RecordOrganizer": + refs = envoyx.MergeRefs(refs, getPageBlockRecordOrganizerRefs(options, index)) - case "Calendar": - refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(b, index)) + case "Chart": + refs = envoyx.MergeRefs(refs, getPageBlockChartRefs(options, index)) - case "Metric": - refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(b, index)) + case "Calendar": + refs = envoyx.MergeRefs(refs, getPageBlockCalendarRefs(options, index)) - case "Comment": - refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(b, index)) + case "Metric": + refs = envoyx.MergeRefs(refs, getPageBlockMetricRefs(options, index)) - case "Progress": - refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(b, index)) - } + case "Comment": + refs = envoyx.MergeRefs(refs, getPageBlockCommentRefs(options, index)) + + case "Progress": + refs = envoyx.MergeRefs(refs, getPageBlockProgressRefs(options, index)) } return } -func getPageBlockRecordListRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockRecordListRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - id := optString(b.Options, "module", "moduleID") + id := optString(options, "module", "moduleID") if id == "" || id == "0" { return } @@ -119,10 +134,10 @@ func getPageBlockRecordListRefs(b types.PageBlock, index int) (refs map[string]e return } -func getPageBlockChartRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockChartRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - id := optString(b.Options, "chart", "chartID") + id := optString(options, "chart", "chartID") if id == "" || id == "0" { return } @@ -135,10 +150,10 @@ func getPageBlockChartRefs(b types.PageBlock, index int) (refs map[string]envoyx return } -func getPageBlockCalendarRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockCalendarRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - ff, _ := b.Options["feeds"].([]interface{}) + ff, _ := options["feeds"].([]interface{}) for j, f := range ff { feed, _ := f.(map[string]interface{}) opt, _ := (feed["options"]).(map[string]interface{}) @@ -157,10 +172,10 @@ func getPageBlockCalendarRefs(b types.PageBlock, index int) (refs map[string]env return } -func getPageBlockMetricRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockMetricRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - mm, _ := b.Options["metrics"].([]interface{}) + mm, _ := options["metrics"].([]interface{}) for j, m := range mm { mops, _ := m.(map[string]interface{}) @@ -178,26 +193,26 @@ func getPageBlockMetricRefs(b types.PageBlock, index int) (refs map[string]envoy return } -func getPageBlockCommentRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockCommentRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { // Same difference - return getPageBlockRecordListRefs(b, index) + return getPageBlockRecordListRefs(options, index) } -func getPageBlockProgressRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockProgressRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) var aux *envoyx.Ref - aux = getPageBlockProgressValueRefs(b.Options["minValue"]) + aux = getPageBlockProgressValueRefs(options["minValue"]) if aux != nil { refs[fmt.Sprintf("Blocks.%d.Options.minValue.ModuleID", index)] = *aux } - aux = getPageBlockProgressValueRefs(b.Options["maxValue"]) + aux = getPageBlockProgressValueRefs(options["maxValue"]) if aux != nil { refs[fmt.Sprintf("Blocks.%d.Options.maxValue.ModuleID", index)] = *aux } - aux = getPageBlockProgressValueRefs(b.Options["value"]) + aux = getPageBlockProgressValueRefs(options["value"]) if aux != nil { refs[fmt.Sprintf("Blocks.%d.Options.value.ModuleID", index)] = *aux } @@ -226,15 +241,15 @@ func getPageBlockProgressValueRefs(val any) (ref *envoyx.Ref) { } } -func getPageBlockRecordOrganizerRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockRecordOrganizerRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { // Same difference - return getPageBlockRecordListRefs(b, index) + return getPageBlockRecordListRefs(options, index) } -func getPageBlockAutomationRefs(b types.PageBlock, index int) (refs map[string]envoyx.Ref) { +func getPageBlockAutomationRefs(options map[string]interface{}, index int) (refs map[string]envoyx.Ref) { refs = make(map[string]envoyx.Ref) - bb, _ := b.Options["buttons"].([]interface{}) + bb, _ := options["buttons"].([]interface{}) for buttonIx, b := range bb { button, _ := b.(map[string]interface{}) id := optString(button, "workflow", "workflowID") diff --git a/server/compose/envoy/yaml_encode.gen.go b/server/compose/envoy/yaml_encode.gen.go index 701997a9d3..62d57d367e 100644 --- a/server/compose/envoy/yaml_encode.gen.go +++ b/server/compose/envoy/yaml_encode.gen.go @@ -398,6 +398,10 @@ func (e YamlEncoder) encodeNamespace(ctx context.Context, p envoyx.EncodeParams, res := node.Resource.(*types.Namespace) // Pre-compute some map values so we can omit error checking when encoding yaml nodes + auxBlocks, err := e.encodeNamespaceBlocksC(ctx, p, tt, node, res, res.Blocks) + if err != nil { + return + } auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt) if err != nil { return @@ -413,6 +417,7 @@ func (e YamlEncoder) encodeNamespace(ctx context.Context, p envoyx.EncodeParams, } out, err = y7s.AddMap(out, + "blocks", auxBlocks, "createdAt", auxCreatedAt, "deletedAt", auxDeletedAt, "enabled", res.Enabled, diff --git a/server/compose/envoy/yaml_encode.go b/server/compose/envoy/yaml_encode.go index 6a5dc8420d..19d6d2c88c 100644 --- a/server/compose/envoy/yaml_encode.go +++ b/server/compose/envoy/yaml_encode.go @@ -1,14 +1,13 @@ package envoy import ( - "context" - "fmt" - - "github.com/cortezaproject/corteza/server/compose/types" - "github.com/cortezaproject/corteza/server/pkg/envoyx" - "github.com/cortezaproject/corteza/server/pkg/y7s" - "github.com/modern-go/reflect2" - "gopkg.in/yaml.v3" + "context" + "fmt" + "github.com/cortezaproject/corteza/server/compose/types" + "github.com/cortezaproject/corteza/server/pkg/envoyx" + "github.com/cortezaproject/corteza/server/pkg/y7s" + "github.com/modern-go/reflect2" + "gopkg.in/yaml.v3" ) func (e YamlEncoder) encode(ctx context.Context, base *yaml.Node, p envoyx.EncodeParams, rt string, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) { @@ -89,128 +88,152 @@ func (e YamlEncoder) encodeModuleFieldOptionsC(ctx context.Context, p envoyx.Enc } func (e YamlEncoder) encodePageBlocksC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, pg *types.Page, bb types.PageBlocks) (_ any, err error) { + var aux any out, _ := y7s.MakeSeq() - var aux any for i, b := range pg.Blocks { - aux, err = e.encodePageBlockC(ctx, p, tt, n, pg, i, b) + options, err := e.encodePageBlockC(ctx, p, tt, n, i, b.Kind, b.Options) if err != nil { - return + return nil, err } + b.Options = options + aux = b + out, err = y7s.AddSeq(out, aux) if err != nil { - return + return nil, err } } return out, nil } -func (e YamlEncoder) encodePageBlockC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, pg *types.Page, index int, b types.PageBlock) (_ any, err error) { - - switch b.Kind { - case "RecordList": - b = e.cleanupPageblockRecordList(b) - - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] - b.Options["module"] = safeParentIdentifier(tt, n, modRef) - delete(b.Options, "moduleID") - break - - case "RecordOrganizer": - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] - b.Options["module"] = safeParentIdentifier(tt, n, modRef) - delete(b.Options, "moduleID") - break - - case "Chart": - chrRef := n.References[fmt.Sprintf("Blocks.%d.Options.ChartID", index)] - b.Options["chart"] = safeParentIdentifier(tt, n, chrRef) - delete(b.Options, "chartID") - break - - case "Calendar": - ff, _ := b.Options["feeds"].([]interface{}) - for i, f := range ff { - feed, _ := f.(map[string]interface{}) - fOpts, _ := (feed["options"]).(map[string]interface{}) - - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.feeds.%d.ModuleID", index, i)] - fOpts["module"] = safeParentIdentifier(tt, n, modRef) - delete(fOpts, "moduleID") - } - break - - case "Automation": - bb, _ := b.Options["buttons"].([]interface{}) - for i, b := range bb { - button, _ := b.(map[string]interface{}) - if _, has := button["workflowID"]; !has { - continue - } - - wfRef := n.References[fmt.Sprintf("Blocks.%d.Options.buttons.%d.WorkflowID", index, i)] - button["workflow"] = safeParentIdentifier(tt, n, wfRef) - delete(button, "workflowID") - i++ - } - break - - case "Metric": - mm, _ := b.Options["metrics"].([]interface{}) - for i, m := range mm { - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.metrics.%d.ModuleID", index, i)] - - mops, _ := m.(map[string]interface{}) - mops["module"] = safeParentIdentifier(tt, n, modRef) - delete(mops, "moduleID") - } - break +func (e YamlEncoder) encodeNamespaceBlocksC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, ns *types.Namespace, bb types.GlobalBlocks) (_ any, err error) { + var aux any + out, _ := y7s.MakeSeq() - case "Comment": - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] - b.Options["module"] = safeParentIdentifier(tt, n, modRef) - delete(b.Options, "moduleID") - break + for i, gB := range ns.Blocks { + options, err := e.encodePageBlockC(ctx, p, tt, n, i, gB.Kind, gB.Options) + if err != nil { + return nil, err + } - case "Progress": - err = e.encodeProgressPageblockVal("minValue", index, n, tt, &b) - if err != nil { - return - } + gB.Options = options + aux = gB - err = e.encodeProgressPageblockVal("maxValue", index, n, tt, &b) - if err != nil { - return - } + out, err = y7s.AddSeq(out, aux) + if err != nil { + return nil, err + } + } - err = e.encodeProgressPageblockVal("value", index, n, tt, &b) - if err != nil { - return - } - break - } + return out, nil +} - return b, nil +func (e YamlEncoder) encodePageBlockC(ctx context.Context, p envoyx.EncodeParams, tt envoyx.Traverser, n *envoyx.Node, index int, kind string, options map[string]interface{}) (opts map[string]interface{}, err error) { + switch kind { + case "RecordList": + options = e.cleanupPageblockRecordList(options) + + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] + options["module"] = safeParentIdentifier(tt, n, modRef) + delete(options, "moduleID") + break + + case "RecordOrganizer": + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] + options["module"] = safeParentIdentifier(tt, n, modRef) + delete(options, "moduleID") + break + + case "Chart": + chrRef := n.References[fmt.Sprintf("Blocks.%d.Options.ChartID", index)] + options["chart"] = safeParentIdentifier(tt, n, chrRef) + delete(options, "chartID") + break + + case "Calendar": + ff, _ := options["feeds"].([]interface{}) + for i, f := range ff { + feed, _ := f.(map[string]interface{}) + fOpts, _ := (feed["options"]).(map[string]interface{}) + + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.feeds.%d.ModuleID", index, i)] + fOpts["module"] = safeParentIdentifier(tt, n, modRef) + delete(fOpts, "moduleID") + } + break + + case "Automation": + bb, _ := options["buttons"].([]interface{}) + for i, b := range bb { + button, _ := b.(map[string]interface{}) + if _, has := button["workflowID"]; !has { + continue + } + + wfRef := n.References[fmt.Sprintf("Blocks.%d.Options.buttons.%d.WorkflowID", index, i)] + button["workflow"] = safeParentIdentifier(tt, n, wfRef) + delete(button, "workflowID") + i++ + } + break + + case "Metric": + mm, _ := options["metrics"].([]interface{}) + for i, m := range mm { + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.metrics.%d.ModuleID", index, i)] + + mops, _ := m.(map[string]interface{}) + mops["module"] = safeParentIdentifier(tt, n, modRef) + delete(mops, "moduleID") + } + break + + case "Comment": + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.ModuleID", index)] + options["module"] = safeParentIdentifier(tt, n, modRef) + delete(options, "moduleID") + break + + case "Progress": + options, err = e.encodeProgressPageblockVal("minValue", index, n, tt, options) + if err != nil { + return + } + + options, err = e.encodeProgressPageblockVal("maxValue", index, n, tt, options) + if err != nil { + return + } + + options, err = e.encodeProgressPageblockVal("value", index, n, tt, options) + if err != nil { + return + } + break + } + + return options, nil } -func (e YamlEncoder) encodeProgressPageblockVal(k string, index int, n *envoyx.Node, tt envoyx.Traverser, b *types.PageBlock) (err error) { - if reflect2.IsNil(b.Options[k]) { +func (e YamlEncoder) encodeProgressPageblockVal(k string, index int, n *envoyx.Node, tt envoyx.Traverser, options map[string]interface{}) (opts map[string]interface{}, err error) { + if reflect2.IsNil(options[k]) { return } - modRef := n.References[fmt.Sprintf("Blocks.%d.Options.%s.ModuleID", index, k)] - opt := b.Options[k].(map[string]any) - opt["moduleID"] = safeParentIdentifier(tt, n, modRef) - delete(opt, "moduleID") + modRef := n.References[fmt.Sprintf("Blocks.%d.Options.%s.ModuleID", index, k)] + opt := options[k].(map[string]any) + opt["moduleID"] = safeParentIdentifier(tt, n, modRef) + delete(opt, "moduleID") - return + return } -func (e YamlEncoder) cleanupPageblockRecordList(b types.PageBlock) (out types.PageBlock) { - out = b - rawFF, has := out.Options["fields"] +func (e YamlEncoder) cleanupPageblockRecordList(options map[string]interface{}) (out map[string]interface{}) { + out = options + rawFF, has := out["fields"] if !has { return } @@ -232,7 +255,7 @@ func (e YamlEncoder) cleanupPageblockRecordList(b types.PageBlock) (out types.Pa } } - out.Options["fields"] = retFF + out["fields"] = retFF - return b + return } diff --git a/server/compose/model/models.gen.go b/server/compose/model/models.gen.go index 13f1992f59..03ed989e95 100644 --- a/server/compose/model/models.gen.go +++ b/server/compose/model/models.gen.go @@ -522,6 +522,14 @@ var Namespace = &dal.Model{ Store: &dal.CodecAlias{Ident: "meta"}, }, + &dal.Attribute{ + Ident: "Blocks", + Type: &dal.TypeJSON{ + DefaultValue: "{}", + }, + Store: &dal.CodecAlias{Ident: "blocks"}, + }, + &dal.Attribute{ Ident: "Name", Sortable: true, Type: &dal.TypeText{}, diff --git a/server/compose/namespace.cue b/server/compose/namespace.cue index 02def2d113..f7f67517ba 100644 --- a/server/compose/namespace.cue +++ b/server/compose/namespace.cue @@ -27,6 +27,18 @@ namespace: { omitSetter: true omitGetter: true } + blocks: { + goType: "types.GlobalBlocks" + dal: { type: "JSON", defaultEmptyObject: true } + omitSetter: true + omitGetter: true + envoy: { + yaml: { + customDecoder: true + customEncoder: true + } + } + } name: { sortable: true dal: {} diff --git a/server/compose/rest.yaml b/server/compose/rest.yaml index d9811092a6..25bad88b8c 100644 --- a/server/compose/rest.yaml +++ b/server/compose/rest.yaml @@ -109,6 +109,10 @@ endpoints: name: meta required: true title: Meta data + - type: sqlxTypes.JSONText + name: blocks + required: false + title: Blocks - type: map[string]string name: labels title: Labels diff --git a/server/compose/rest/namespace.go b/server/compose/rest/namespace.go index 08ce6f9091..3892233913 100644 --- a/server/compose/rest/namespace.go +++ b/server/compose/rest/namespace.go @@ -168,6 +168,14 @@ func (ctrl Namespace) Update(ctx context.Context, r *request.NamespaceUpdate) (i return nil, err } + if len(r.Blocks) > 2 { + // Process blocks if they were included in the request + // if not, do not assume that blocks were removed! + if err = r.Blocks.Unmarshal(&ns.Blocks); err != nil { + return nil, err + } + } + ns, err = ctrl.namespace.Update(ctx, ns) return ctrl.makePayload(ctx, ns, err) } diff --git a/server/compose/rest/request/namespace.go b/server/compose/rest/request/namespace.go index e5b7afce57..3bc58a27f5 100644 --- a/server/compose/rest/request/namespace.go +++ b/server/compose/rest/request/namespace.go @@ -134,6 +134,11 @@ type ( // Meta data Meta sqlxTypes.JSONText + // Blocks POST parameter + // + // Blocks + Blocks sqlxTypes.JSONText + // Labels POST parameter // // Labels @@ -569,6 +574,7 @@ func (r NamespaceUpdate) Auditable() map[string]interface{} { "slug": r.Slug, "enabled": r.Enabled, "meta": r.Meta, + "blocks": r.Blocks, "labels": r.Labels, "updatedAt": r.UpdatedAt, } @@ -599,6 +605,11 @@ func (r NamespaceUpdate) GetMeta() sqlxTypes.JSONText { return r.Meta } +// Auditable returns all auditable/loggable parameters +func (r NamespaceUpdate) GetBlocks() sqlxTypes.JSONText { + return r.Blocks +} + // Auditable returns all auditable/loggable parameters func (r NamespaceUpdate) GetLabels() map[string]string { return r.Labels @@ -658,6 +669,13 @@ func (r *NamespaceUpdate) Fill(req *http.Request) (err error) { } } + if val, ok := req.MultipartForm.Value["blocks"]; ok && len(val) > 0 { + r.Blocks, err = payload.ParseJSONTextWithErr(val[0]) + if err != nil { + return err + } + } + if val, ok := req.MultipartForm.Value["labels[]"]; ok { r.Labels, err = label.ParseStrings(val) if err != nil { @@ -714,6 +732,13 @@ func (r *NamespaceUpdate) Fill(req *http.Request) (err error) { } } + if val, ok := req.Form["blocks"]; ok && len(val) > 0 { + r.Blocks, err = payload.ParseJSONTextWithErr(val[0]) + if err != nil { + return err + } + } + if val, ok := req.Form["labels[]"]; ok { r.Labels, err = label.ParseStrings(val) if err != nil { diff --git a/server/compose/service/namespace.go b/server/compose/service/namespace.go index 38a8476af3..96a8bb2a92 100644 --- a/server/compose/service/namespace.go +++ b/server/compose/service/namespace.go @@ -653,6 +653,37 @@ func (svc namespace) handleUpdate(ctx context.Context, upd *types.Namespace) nam res.Meta = upd.Meta } + // Get max blockID for later use + blockID := uint64(0) + for _, b := range res.Blocks { + if b.BlockID == blockID { + // assign ids on new page blocks + for i := range upd.Blocks { + upd.Blocks[i].BlockID = uint64(i) + 1 + } + } + + if b.BlockID > blockID { + blockID = b.BlockID + } + } + + if !reflect.DeepEqual(res.Blocks, upd.Blocks) { + res.Blocks = upd.Blocks + changes |= namespaceChanged + } + + // Assure blockIDs + for i, b := range res.Blocks { + if b.BlockID == 0 { + blockID++ + b.BlockID = blockID + res.Blocks[i] = b + + changes |= namespaceChanged + } + } + if upd.Labels != nil { if label.Changed(res.Labels, upd.Labels) { changes |= namespaceLabelsChanged diff --git a/server/compose/types/namespace.go b/server/compose/types/namespace.go index 965c4aa1e0..7f8eb0bc92 100644 --- a/server/compose/types/namespace.go +++ b/server/compose/types/namespace.go @@ -15,6 +15,7 @@ type ( Slug string `json:"slug"` Enabled bool `json:"enabled"` Meta NamespaceMeta `json:"meta"` + Blocks GlobalBlocks `json:"blocks"` Labels map[string]string `json:"labels,omitempty"` @@ -28,6 +29,28 @@ type ( Name string `json:"name"` } + GlobalBlocks []GlobalBlock + + GlobalBlock struct { + BlockID uint64 `json:"blockID,string,omitempty"` + + Options map[string]interface{} `json:"options,omitempty"` + Style PageBlockStyle `json:"style,omitempty"` + Kind string `json:"kind"` + XYWH [4]int `json:"xywh"` // x,y,w,h + Meta map[string]any `json:"meta,omitempty"` + + // Warning: value of this field is now handled via resource-translation facility + // struct field is kept for the convenience for now since it allows us + // easy encoding/decoding of the outgoing/incoming values + Title string `json:"title,omitempty"` + + // Warning: value of this field is now handled via resource-translation facility + // struct field is kept for the convenience for now since it allows us + // easy encoding/decoding of the outgoing/incoming values + Description string `json:"description,omitempty"` + } + NamespaceFilter struct { NamespaceID []string `json:"namespaceID"` @@ -91,3 +114,6 @@ func (set NamespaceSet) FindByHandle(handle string) *Namespace { func (nm *NamespaceMeta) Scan(src any) error { return sql.ParseJSON(src, nm) } func (nm NamespaceMeta) Value() (driver.Value, error) { return json.Marshal(nm) } + +func (nm *GlobalBlocks) Scan(src any) error { return sql.ParseJSON(src, nm) } +func (nm GlobalBlocks) Value() (driver.Value, error) { return json.Marshal(nm) } diff --git a/server/store/adapters/rdbms/aux_types.gen.go b/server/store/adapters/rdbms/aux_types.gen.go index 0f2c137c8c..52ebaed538 100644 --- a/server/store/adapters/rdbms/aux_types.gen.go +++ b/server/store/adapters/rdbms/aux_types.gen.go @@ -7,17 +7,17 @@ package rdbms // import ( - automationType "github.com/cortezaproject/corteza/server/automation/types" - composeType "github.com/cortezaproject/corteza/server/compose/types" - discoveryType "github.com/cortezaproject/corteza/server/discovery/types" - federationType "github.com/cortezaproject/corteza/server/federation/types" - actionlogType "github.com/cortezaproject/corteza/server/pkg/actionlog" - "github.com/cortezaproject/corteza/server/pkg/expr" - flagType "github.com/cortezaproject/corteza/server/pkg/flag/types" - labelsType "github.com/cortezaproject/corteza/server/pkg/label/types" - rbacType "github.com/cortezaproject/corteza/server/pkg/rbac" - systemType "github.com/cortezaproject/corteza/server/system/types" - "time" + automationType "github.com/cortezaproject/corteza/server/automation/types" + composeType "github.com/cortezaproject/corteza/server/compose/types" + discoveryType "github.com/cortezaproject/corteza/server/discovery/types" + federationType "github.com/cortezaproject/corteza/server/federation/types" + actionlogType "github.com/cortezaproject/corteza/server/pkg/actionlog" + "github.com/cortezaproject/corteza/server/pkg/expr" + flagType "github.com/cortezaproject/corteza/server/pkg/flag/types" + labelsType "github.com/cortezaproject/corteza/server/pkg/label/types" + rbacType "github.com/cortezaproject/corteza/server/pkg/rbac" + systemType "github.com/cortezaproject/corteza/server/system/types" + "time" ) type ( @@ -279,6 +279,7 @@ type ( Slug string `db:"slug"` Enabled bool `db:"enabled"` Meta composeType.NamespaceMeta `db:"meta"` + Blocks composeType.GlobalBlocks `db:"blocks"` Name string `db:"name"` CreatedAt time.Time `db:"created_at"` UpdatedAt *time.Time `db:"updated_at"` @@ -1593,6 +1594,7 @@ func (aux *auxComposeNamespace) encode(res *composeType.Namespace) (_ error) { aux.Slug = res.Slug aux.Enabled = res.Enabled aux.Meta = res.Meta + aux.Blocks = res.Blocks aux.Name = res.Name aux.CreatedAt = res.CreatedAt aux.UpdatedAt = res.UpdatedAt @@ -1609,6 +1611,7 @@ func (aux auxComposeNamespace) decode() (res *composeType.Namespace, _ error) { res.Slug = aux.Slug res.Enabled = aux.Enabled res.Meta = aux.Meta + res.Blocks = aux.Blocks res.Name = aux.Name res.CreatedAt = aux.CreatedAt res.UpdatedAt = aux.UpdatedAt @@ -1625,6 +1628,7 @@ func (aux *auxComposeNamespace) scan(row scanner) error { &aux.Slug, &aux.Enabled, &aux.Meta, + &aux.Blocks, &aux.Name, &aux.CreatedAt, &aux.UpdatedAt, diff --git a/server/store/adapters/rdbms/queries.gen.go b/server/store/adapters/rdbms/queries.gen.go index ff79fe6503..fdf70d22de 100644 --- a/server/store/adapters/rdbms/queries.gen.go +++ b/server/store/adapters/rdbms/queries.gen.go @@ -1869,6 +1869,7 @@ var ( "slug", "enabled", "meta", + "blocks", "name", "created_at", "updated_at", @@ -1886,6 +1887,7 @@ var ( "slug": res.Slug, "enabled": res.Enabled, "meta": res.Meta, + "blocks": res.Blocks, "name": res.Name, "created_at": res.CreatedAt, "updated_at": res.UpdatedAt, @@ -1906,6 +1908,7 @@ var ( "slug": res.Slug, "enabled": res.Enabled, "meta": res.Meta, + "blocks": res.Blocks, "name": res.Name, "created_at": res.CreatedAt, "updated_at": res.UpdatedAt, @@ -1924,6 +1927,7 @@ var ( "slug": res.Slug, "enabled": res.Enabled, "meta": res.Meta, + "blocks": res.Blocks, "name": res.Name, "created_at": res.CreatedAt, "updated_at": res.UpdatedAt, diff --git a/server/store/adapters/rdbms/upgrade_fixes.go b/server/store/adapters/rdbms/upgrade_fixes.go index 1219c802ba..cc22319796 100644 --- a/server/store/adapters/rdbms/upgrade_fixes.go +++ b/server/store/adapters/rdbms/upgrade_fixes.go @@ -49,6 +49,7 @@ var ( fix_2022_09_07_changePostgresIdColumnsDatatype, fix_2022_09_00_migrateComposeModuleDiscoveryConfigSettings, fix_2023_03_00_migrateComposePageMeta, + fix_2024_03_00_migrateNamespacePageBlocks, } ) @@ -242,6 +243,13 @@ func fix_2023_03_00_migrateComposePageMeta(ctx context.Context, s *Store) (err e ) } +func fix_2024_03_00_migrateNamespacePageBlocks(ctx context.Context, s *Store) (err error) { + return addColumn(ctx, s, + "compose_namespace", + &dal.Attribute{Ident: "blocks", Type: &dal.TypeJSON{DefaultValue: "[]"}}, + ) +} + func fix_2022_09_00_extendComposeModuleForPrivacyAndDAL(ctx context.Context, s *Store) (err error) { return addColumn(ctx, s, "compose_module",