diff --git a/client/web/reporter/src/components/EditorToolbar.vue b/client/web/reporter/src/components/EditorToolbar.vue
index b5c51bd88..151d7ebc1 100644
--- a/client/web/reporter/src/components/EditorToolbar.vue
+++ b/client/web/reporter/src/components/EditorToolbar.vue
@@ -28,16 +28,27 @@
size="lg"
size-confirm="lg"
variant="danger"
- :disabled="deleteDisabled || processingDelete"
+ :disabled="deleteDisabled || processingDelete || processing"
:processing="processingDelete"
:text="$t('general:label.delete')"
:borderless="false"
@confirmed="$emit('delete')"
/>
+
+
diff --git a/client/web/reporter/src/components/ReportSidebar.vue b/client/web/reporter/src/components/ReportSidebar.vue
index 2dd63adfb..d94bc299f 100644
--- a/client/web/reporter/src/components/ReportSidebar.vue
+++ b/client/web/reporter/src/components/ReportSidebar.vue
@@ -72,7 +72,7 @@ export default {
return reports.map(({ reportID, handle, meta: { name = '' } }) => {
return {
- page: { pageID: reportID, name: 'report.view', title: name || handle },
+ page: { pageID: reportID, name: this.$route.name, title: name || handle },
params: { reportID },
}
})
@@ -90,6 +90,14 @@ export default {
},
},
+ created () {
+ this.$root.$on('refetch:reports', this.fetchReports)
+ },
+
+ beforeDestroy () {
+ this.$root.$off('refetch:reports', this.fetchReports)
+ },
+
methods: {
fetchReports () {
this.$SystemAPI.reportList()
diff --git a/client/web/reporter/src/components/faIcons.js b/client/web/reporter/src/components/faIcons.js
index 768cd0207..088c1c515 100644
--- a/client/web/reporter/src/components/faIcons.js
+++ b/client/web/reporter/src/components/faIcons.js
@@ -48,6 +48,7 @@ import {
faQuestion,
faEllipsisV,
faTools,
+ faClone,
} from '@fortawesome/free-solid-svg-icons'
import {
@@ -112,4 +113,5 @@ library.add(
faQuestion,
faEllipsisV,
faTools,
+ faClone,
)
diff --git a/client/web/reporter/src/components/index.js b/client/web/reporter/src/components/index.js
index 39fd9b3dd..519dc4d12 100644
--- a/client/web/reporter/src/components/index.js
+++ b/client/web/reporter/src/components/index.js
@@ -53,3 +53,4 @@ Vue.component('c-permissions-button', components.CPermissionsButton)
Vue.component('c-input-confirm', components.CInputConfirm)
Vue.component('c-button-submit', components.CButtonSubmit)
Vue.component('c-input-select', components.CInputSelect)
+Vue.component('c-form-table-wrapper', components.CFormTableWrapper)
diff --git a/client/web/reporter/src/mixins/report.js b/client/web/reporter/src/mixins/report.js
index fa8fd2437..054c58868 100644
--- a/client/web/reporter/src/mixins/report.js
+++ b/client/web/reporter/src/mixins/report.js
@@ -6,6 +6,7 @@ export default {
processing: false,
processingSave: false,
processingDelete: false,
+ processingClone: false,
report: undefined,
}
},
@@ -21,7 +22,9 @@ export default {
})
.catch(this.toastErrorHandler(this.$t('notification:report.fetchFailed')))
.finally(() => {
- this.processing = false
+ setTimeout(() => {
+ this.processing = false
+ }, 400)
})
},
@@ -90,5 +93,29 @@ export default {
this.processingDelete = false
})
},
+
+ handleClone (report) {
+ this.processing = true
+ this.processingClone = true
+ const { meta, sources, blocks, scenarios, labels } = report
+ const cloneSuffix = `${meta.name} (${this.$t('general:cloneSuffix')})`
+ let name = meta.name
+
+ if (!this.initialReportState || this.initialReportState.meta.name === name) {
+ name = cloneSuffix
+ }
+
+ return this.$SystemAPI.reportCreate({ handle: '', meta: { ...meta, name }, sources, blocks, scenarios, labels })
+ .then(report => {
+ report = new system.Report(report)
+ this.toastSuccess(this.$t('notification:report.created'))
+ return report
+ })
+ .catch(this.toastErrorHandler(this.$t('notification:report.createFailed')))
+ .finally(() => {
+ this.processing = false
+ this.processingClone = false
+ })
+ },
},
}
diff --git a/client/web/reporter/src/views/Report/Builder.vue b/client/web/reporter/src/views/Report/Builder.vue
index c2651be3e..d8a7fe01d 100644
--- a/client/web/reporter/src/views/Report/Builder.vue
+++ b/client/web/reporter/src/views/Report/Builder.vue
@@ -2,7 +2,10 @@
-
+
{{ pageTitle }}
@@ -13,10 +16,10 @@
:options="scenarioOptions"
:get-option-key="getOptionKey"
:placeholder="$t('builder:pick-scenario')"
+ :disabled="processing || fetchingReport"
size="sm"
@input="refreshReport()"
/>
-
{{ $t('builder:datasources.label') }}
-
@@ -72,8 +74,15 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
{
this.mapBlocks()
- }).finally(() => {
- this.processing = false
+ }).catch(() => {
+ this.toastErrorHandler(this.$t('notification:report.loadFailed'))
+ })
+ .finally(() => {
+ setTimeout(() => {
+ this.fetchingReport = false
+ this.processing = false
+ }, 400)
})
}
},
@@ -853,6 +857,13 @@ export default {
})
},
+ handleReportCloning () {
+ this.handleClone(this.report).then(({ reportID }) => {
+ this.$root.$emit('refetch:reports')
+ this.$router.push({ name: 'report.builder', params: { reportID } })
+ })
+ },
+
mapBlocks () {
this.reportBlocks = this.report.blocks.map(({ xywh, ...p }, i) => {
const [x, y, w, h] = xywh
diff --git a/client/web/reporter/src/views/Report/Edit.vue b/client/web/reporter/src/views/Report/Edit.vue
index 35016da98..21efe1ef1 100644
--- a/client/web/reporter/src/views/Report/Edit.vue
+++ b/client/web/reporter/src/views/Report/Edit.vue
@@ -26,6 +26,7 @@
:icon="['fas', 'tools']"
/>
+
+
+
+
+
@@ -319,6 +330,12 @@ export default {
next(!isEqual(reportState, initialReportState) ? window.confirm(this.$t('unsavedChanges')) : true)
},
+
+ handleReportCloning () {
+ this.handleClone(this.report).then(({ reportID }) => {
+ this.$router.push({ name: 'report.builder', params: { reportID } })
+ })
+ },
},
}
diff --git a/client/web/reporter/src/views/Report/List.vue b/client/web/reporter/src/views/Report/List.vue
index efe56d2b1..a1a7551ee 100644
--- a/client/web/reporter/src/views/Report/List.vue
+++ b/client/web/reporter/src/views/Report/List.vue
@@ -122,6 +122,13 @@
/>
+
+
+ {{ $t('general:resourceList.clone') }}
+
+
import { mapGetters } from 'vuex'
import listHelpers from 'corteza-webapp-reporter/src/mixins/listHelpers'
+import report from 'corteza-webapp-reporter/src/mixins/report'
import { components } from '@cortezaproject/corteza-vue'
const { CResourceList } = components
@@ -161,6 +169,7 @@ export default {
mixins: [
listHelpers,
+ report,
],
data () {
@@ -224,14 +233,10 @@ export default {
methods: {
viewReport ({ reportID, canReadReport = false }) {
if (reportID) {
- if (canReadReport) {
- this.$router.push({
- name: 'report.view',
- params: { reportID },
- })
- } else {
- this.toastDanger(this.$t('notification:report.notAllowed.read'))
- }
+ this.$router.push({
+ name: 'report.view',
+ params: { reportID },
+ })
}
},
@@ -252,6 +257,12 @@ export default {
this.processingDelete = false
})
},
+
+ handleReportCloning (report) {
+ this.handleClone(report).then(({ reportID }) => {
+ this.viewReport({ reportID })
+ })
+ },
},
}
diff --git a/client/web/reporter/src/views/Report/View.vue b/client/web/reporter/src/views/Report/View.vue
index ce64b8ae1..fea4834ab 100644
--- a/client/web/reporter/src/views/Report/View.vue
+++ b/client/web/reporter/src/views/Report/View.vue
@@ -2,7 +2,10 @@
-
+
{{ pageTitle }}
@@ -13,6 +16,7 @@
:options="scenarioOptions"
:get-option-key="getOptionKey"
:placeholder="$t('pick-scenario')"
+ :disabled="processing || fetchingReport"
size="sm"
style="max-width: 300px; min-width: 150px;"
@input="refreshReport()"
@@ -47,8 +51,15 @@
+
+
+
+
{
+ this.toastErrorHandler(this.$t('notification:report.loadFailed'))
+ })
+ .finally(() => {
+ setTimeout(() => {
+ this.fetchingReport = false
+ this.processing = false
+ }, 400)
+ })
}
},
},
@@ -161,8 +186,6 @@ export default {
},
async fetchReport (reportID) {
- this.processing = true
-
this.report = undefined
return this.$SystemAPI.reportRead({ reportID })
@@ -175,9 +198,6 @@ export default {
})
})
.catch(this.toastErrorHandler(this.$t('notification:report.fetchFailed')))
- .finally(() => {
- this.processing = false
- })
},
getOptionKey (scenario) {
diff --git a/lib/vue/src/components/input/CInputSelect.vue b/lib/vue/src/components/input/CInputSelect.vue
index 279fbecf8..cfecf1a35 100644
--- a/lib/vue/src/components/input/CInputSelect.vue
+++ b/lib/vue/src/components/input/CInputSelect.vue
@@ -6,8 +6,9 @@
:clearable="clearable"
:options="options"
:searchable="searchable"
+ :disabled="disabled"
:calculate-position="calculateDropdownPosition"
- :append-to-body="appendToBody"
+ :append-to-body="appendToBody"
class="bg-white rounded"
:class="sizeClass"
@search="onSearch"
@@ -73,6 +74,11 @@ export default {
type: String,
default: 'md',
},
+
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
},
computed: {
diff --git a/locale/en/corteza-webapp-reporter/general.yaml b/locale/en/corteza-webapp-reporter/general.yaml
index d0c3547f0..a64ce6424 100644
--- a/locale/en/corteza-webapp-reporter/general.yaml
+++ b/locale/en/corteza-webapp-reporter/general.yaml
@@ -17,6 +17,7 @@ label:
openLinkInNewTab: Open link in a new tab
save: Save
saveAndClose: Save and close
+ clone: Save as copy
today: Today
urlPlaceholder: https://example.tld
default: Default
@@ -34,8 +35,11 @@ resourceList:
single: 'One {{data}}'
single_plural: '{{count}} {{data}}'
recordsPerPage: Records per page
+ clone: Clone
themes:
labels:
light: Light
dark: Dark
+
+cloneSuffix: copy
diff --git a/locale/en/corteza-webapp-reporter/notification.yaml b/locale/en/corteza-webapp-reporter/notification.yaml
index 9bf2a0cfe..4f0de7ed4 100644
--- a/locale/en/corteza-webapp-reporter/notification.yaml
+++ b/locale/en/corteza-webapp-reporter/notification.yaml
@@ -13,6 +13,7 @@ namespace:
fetch-failed: Failed to fetch namespaces
report:
createFailed: Failed to create report
+ loadFailed: Loading the report failed
created: Report created
delete: Report deleted
deleteFailed: Failed to delete report