diff --git a/packages/nc-gui/components/project/spreadsheet/components/SharedViewsList.vue b/packages/nc-gui/components/project/spreadsheet/components/SharedViewsList.vue index 8e5bf21dc1..5cc237f718 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/SharedViewsList.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/SharedViewsList.vue @@ -18,6 +18,10 @@ {{ $t('labels.password') }} + + + Download Allowed + {{ $t('labels.actions') }} @@ -46,6 +50,11 @@ + + + mdi-content-copy mdi-delete-outline @@ -80,6 +89,11 @@ + + + mdi-content-copy mdi-delete-outline @@ -173,6 +187,14 @@ export default { } return `/nc/${viewType}/${view.uuid}`; }, + renderAllowCSVDownload(view) { + if (view.type === ViewTypes.GRID) { + view.meta = view.meta && typeof view.meta === 'string' ? JSON.parse(view.meta) : view.meta; + return view.meta.allowCSVDownload ? '✔️' : '❌'; + } else { + return 'N/A'; + } + }, }, }; diff --git a/packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue b/packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue index 0386c6cc18..7074c77e29 100644 --- a/packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue +++ b/packages/nc-gui/components/project/spreadsheet/components/SpreadsheetNavDrawer.vue @@ -316,44 +316,63 @@ mdi-content-copy - - - - -
- - - - - - {{ $t('placeholder.password.save') }} - -
+ + + + + More Options + + mdi-chevron-{{ advanceOptionsPanel === 0 ? 'up' : 'down' }} + + + + +
+ + + + + + {{ $t('placeholder.password.save') }} + +
+ +
+
+
@@ -410,6 +429,7 @@ export default { queryParams: Object, }, data: () => ({ + advanceOptionsPanel: false, webhookSliderModal: false, codeSnippetModal: false, drag: false, @@ -425,6 +445,7 @@ export default { searchQueryVal: '', showShareLinkPassword: false, passwordProtect: false, + allowCSVDownload: true, sharedViewPassword: '', overAdvShieldIcon: false, overShieldIcon: false, @@ -611,6 +632,9 @@ export default { this.saveShareLinkPassword(); } }, + onAllowCSVDownloadChange() { + this.saveAllowCSVDownload(); + }, async saveShareLinkPassword() { try { await this.$api.dbViewShare.update(this.shareLink.id, { @@ -632,6 +656,27 @@ export default { this.$e('a:view:share:enable-pwd'); }, + async saveAllowCSVDownload() { + try { + const meta = + this.shareLink.meta && typeof this.shareLink.meta === 'string' + ? JSON.parse(this.shareLink.meta) + : this.shareLink.meta; + + meta.allowCSVDownload = this.allowCSVDownload; + await this.$api.dbViewShare.update(this.shareLink.id, { + meta, + }); + this.$toast.success('Successfully updated').goAway(3000); + } catch (e) { + this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000); + } + if (this.allowCSVDownload) { + this.$e('a:view:share:enable-csv-download'); + } else { + this.$e('a:view:share:disable-csv-download'); + } + }, async loadViews() { // this.viewsList = await this.sqlOp( // { @@ -725,34 +770,12 @@ export default { this.$e('a:view:delete', { view: view.type }); }, async genShareLink() { - // const sharedViewUrl = await this.$store.dispatch('sqlMgr/ActSqlOp', [ - // { dbAlias: this.nodes.dbAlias }, - // 'createSharedViewLink', - // { - // model_name: this.table, - // // meta: this.meta, - // query_params: { - // where: this.concatenatedXWhere, - // sort: this.sort, - // fields: Object.keys(this.showFields) - // .filter(f => this.showFields[f]) - // .join(','), - // showFields: this.showFields, - // fieldsOrder: this.fieldsOrder, - // extraViewParams: this.extraViewParams, - // selectedViewId: this.selectedViewId, - // columnsWidth: this.columnsWidth - // }, - // view_name: this.selectedView.title, - // type: this.selectedView.type, - // show_as: this.selectedView.show_as, - // password: this.sharedViewPassword - // } - // ]) const shared = await this.$api.dbViewShare.create(this.selectedViewId); - + shared.meta = shared.meta && typeof shared.meta === 'string' ? JSON.parse(shared.meta) : shared.meta; // todo: url this.shareLink = shared; + this.passwordProtect = shared.password !== null; + this.allowCSVDownload = shared.meta.allowCSVDownload; this.showShareModel = true; }, copyView(view, i) { @@ -903,4 +926,7 @@ export default { opacity: 0.5; background: grey; } +.mx-auto .v-expansion-panel { + background: var(--v-backgroundColor-base); +} diff --git a/packages/nc-gui/components/project/spreadsheet/public/XcTable.vue b/packages/nc-gui/components/project/spreadsheet/public/XcTable.vue index 2af4fad519..23cff36657 100644 --- a/packages/nc-gui/components/project/spreadsheet/public/XcTable.vue +++ b/packages/nc-gui/components/project/spreadsheet/public/XcTable.vue @@ -46,7 +46,8 @@ @input="loadTableData" /> - , res) { - Tele.emit('evt', { evt_type: 'sharedView:password-updated' }); - res.json(await View.passwordUpdate(req.params.viewId, req.body)); +async function shareViewUpdate(req: Request, res) { + Tele.emit('evt', { evt_type: 'sharedView:updated' }); + res.json(await View.update(req.params.viewId, req.body)); } async function shareViewDelete(req: Request, res) { @@ -140,7 +140,7 @@ router.post( router.patch( '/api/v1/db/meta/views/:viewId/share', metaApiMetrics, - ncMetaAclMw(shareViewPasswordUpdate, 'shareViewPasswordUpdate') + ncMetaAclMw(shareViewUpdate, 'shareViewUpdate') ); router.delete( '/api/v1/db/meta/views/:viewId/share', diff --git a/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts b/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts index eb5b9a9c2c..0bd7404e8d 100644 --- a/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts +++ b/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts @@ -5,6 +5,7 @@ import * as nc_014_alter_column_data_types from './v2/nc_014_alter_column_data_t import * as nc_015_add_meta_col_in_column_table from './v2/nc_015_add_meta_col_in_column_table'; import * as nc_016_alter_hooklog_payload_types from './v2/nc_016_alter_hooklog_payload_types'; import * as nc_017_add_user_token_version_column from './v2/nc_017_add_user_token_version_column'; +import * as nc_018_add_meta_in_view from './v2/nc_018_add_meta_in_view'; // Create a custom migration source class export default class XcMigrationSourcev2 { @@ -21,6 +22,7 @@ export default class XcMigrationSourcev2 { 'nc_015_add_meta_col_in_column_table', 'nc_016_alter_hooklog_payload_types', 'nc_017_add_user_token_version_column', + 'nc_018_add_meta_in_view', ]); } @@ -44,6 +46,8 @@ export default class XcMigrationSourcev2 { return nc_016_alter_hooklog_payload_types; case 'nc_017_add_user_token_version_column': return nc_017_add_user_token_version_column; + case 'nc_018_add_meta_in_view': + return nc_018_add_meta_in_view; } } } diff --git a/packages/nocodb/src/lib/migrations/v2/nc_018_add_meta_in_view.ts b/packages/nocodb/src/lib/migrations/v2/nc_018_add_meta_in_view.ts new file mode 100644 index 0000000000..80ec206059 --- /dev/null +++ b/packages/nocodb/src/lib/migrations/v2/nc_018_add_meta_in_view.ts @@ -0,0 +1,38 @@ +import Knex from 'knex'; +import { MetaTable } from '../../utils/globals'; + +const up = async (knex: Knex) => { + await knex.schema.alterTable(MetaTable.VIEWS, (table) => { + table.text('meta'); + }); +}; + +const down = async (knex) => { + await knex.schema.alterTable(MetaTable.VIEWS, (table) => { + table.dropColumns('meta'); + }); +}; + +export { up, down }; + +/** + * @copyright Copyright (c) 2021, Xgene Cloud Ltd + * + * @author Wing-Kam Wong + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ diff --git a/packages/nocodb/src/lib/models/View.ts b/packages/nocodb/src/lib/models/View.ts index f5eb88d7f9..a3dc3b657e 100644 --- a/packages/nocodb/src/lib/models/View.ts +++ b/packages/nocodb/src/lib/models/View.ts @@ -42,6 +42,7 @@ export default class View implements ViewType { project_id?: string; base_id?: string; show_system_fields?: boolean; + meta?: any; constructor(data: View) { Object.assign(this, data); @@ -614,7 +615,31 @@ export default class View implements ViewType { viewId ); } - + if (!view.meta) { + const defaultMeta = { + allowCSVDownload: true, + }; + // get existing cache + const key = `${CacheScope.VIEW}:${view.id}`; + const o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); + if (o) { + // update data + o.meta = JSON.stringify(defaultMeta); + // set cache + await NocoCache.set(key, o); + } + // set meta + await ncMeta.metaUpdate( + null, + null, + MetaTable.VIEWS, + { + meta: JSON.stringify(defaultMeta), + }, + viewId + ); + view.meta = defaultMeta; + } return view; } @@ -675,6 +700,7 @@ export default class View implements ViewType { lock_type?: string; password?: string; uuid?: string; + meta?: any; }, ncMeta = Noco.ncMeta ) { @@ -684,14 +710,19 @@ export default class View implements ViewType { 'show_system_fields', 'lock_type', 'password', + 'meta', 'uuid', ]); + updateObj.meta = JSON.stringify(updateObj.meta); // get existing cache const key = `${CacheScope.VIEW}:${viewId}`; let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); if (o) { // update data - o = { ...o, ...updateObj }; + o = { + ...o, + ...updateObj, + }; if (o.is_default) { await NocoCache.set(`${CacheScope.VIEW}:${o.fk_model_id}:default`, o); } diff --git a/scripts/cypress/integration/common/4b_table_view_share.js b/scripts/cypress/integration/common/4b_table_view_share.js index e8c77af5b1..4a283973e9 100644 --- a/scripts/cypress/integration/common/4b_table_view_share.js +++ b/scripts/cypress/integration/common/4b_table_view_share.js @@ -5,26 +5,24 @@ let storedURL = ""; let linkText = ""; const generateLinkWithPwd = () => { - // cy.get(".v-navigation-drawer__content > .container") - // .find(".v-list > .v-list-item") - // .contains("Share View") - // .click(); - mainPage.shareView().click(); + mainPage.shareView().click({ force: true }); + + cy.wait(3000); cy.snipActiveModal("Modal_ShareView"); // enable checkbox & feed pwd, save - cy.getActiveModal() - .find('[role="switch"][type="checkbox"]') - .click({ force: true }); - cy.getActiveModal().find('input[type="password"]').type("1"); - - cy.snipActiveModal("Modal_ShareView_Password"); - - cy.getActiveModal().find('button:contains("Save password")').click(); - - cy.toastWait("Successfully updated"); - + cy.getActiveModal().find('button:contains("More Options")').click({ force: true }); + cy.getActiveModal().find('[role="checkbox"][type="checkbox"]').first().then(($el) => { + if (!$el.prop("checked")) { + cy.wrap($el).click({ force: true }); + cy.getActiveModal().find('input[type="password"]').type("1"); + cy.snipActiveModal("Modal_ShareView_Password"); + cy.getActiveModal().find('button:contains("Save password")').click(); + cy.toastWait("Successfully updated"); + } + }); + // copy link text, visit URL cy.getActiveModal() .find(".share-link-box") @@ -93,6 +91,11 @@ export const genTest = (apiType, dbType) => { cy.get("body") .find(".v-dialog.v-dialog--active") .should("not.exist"); + + // Verify Download as CSV is here + cy.get(".nc-actions-menu-btn").click(); + cy.snipActiveMenu("Menu_ActionsMenu"); + cy.get(`.menuable__content__active .v-list-item span:contains("Download as CSV")`).should("exist"); }); it("Delete view", () => { diff --git a/scripts/cypress/integration/common/4e_form_view_share.js b/scripts/cypress/integration/common/4e_form_view_share.js index c661775c3b..dfc5a67202 100644 --- a/scripts/cypress/integration/common/4e_form_view_share.js +++ b/scripts/cypress/integration/common/4e_form_view_share.js @@ -97,7 +97,9 @@ export const genTest = (apiType, dbType) => { // .find(".v-list > .v-list-item") // .contains("Share View") // .click(); - mainPage.shareView().click(); + mainPage.shareView().click({ force: true }); + + cy.wait(5000); cy.snipActiveModal("Modal_ShareView"); diff --git a/scripts/cypress/integration/common/4f_grid_view_share.js b/scripts/cypress/integration/common/4f_grid_view_share.js index c625951b58..6d06ebc773 100644 --- a/scripts/cypress/integration/common/4f_grid_view_share.js +++ b/scripts/cypress/integration/common/4f_grid_view_share.js @@ -20,7 +20,9 @@ export const genTest = (apiType, dbType) => { // .find(".v-list > .v-list-item") // .contains("Share View") // .click(); - mainPage.shareView().click(); + mainPage.shareView().click({ force: true }); + + cy.wait(5000); // wait, as URL initially will be /undefined cy.getActiveModal() diff --git a/scripts/cypress/integration/common/4f_pg_grid_view_share.js b/scripts/cypress/integration/common/4f_pg_grid_view_share.js index e049477f05..7e74114db1 100644 --- a/scripts/cypress/integration/common/4f_pg_grid_view_share.js +++ b/scripts/cypress/integration/common/4f_pg_grid_view_share.js @@ -20,8 +20,10 @@ export const genTest = (apiType, dbType) => { // .find(".v-list > .v-list-item") // .contains("Share View") // .click(); - mainPage.shareView().click(); - + mainPage.shareView().click({ force: true }); + + cy.wait(5000); + // wait, as URL initially will be /undefined cy.getActiveModal() .find(".share-link-box") diff --git a/scripts/cypress/integration/common/6f_attachments.js b/scripts/cypress/integration/common/6f_attachments.js index b7aab2dc0d..9f692524a2 100644 --- a/scripts/cypress/integration/common/6f_attachments.js +++ b/scripts/cypress/integration/common/6f_attachments.js @@ -66,7 +66,9 @@ export const genTest = (apiType, dbType) => { // .find(".v-list > .v-list-item") // .contains("Share View") // .click(); - mainPage.shareView().click(); + mainPage.shareView().click({ force: true }); + + cy.wait(5000); // copy link text, visit URL cy.getActiveModal() diff --git a/scripts/cypress/support/page_objects/mainPage.js b/scripts/cypress/support/page_objects/mainPage.js index 307452ed61..b8d321561b 100644 --- a/scripts/cypress/support/page_objects/mainPage.js +++ b/scripts/cypress/support/page_objects/mainPage.js @@ -245,6 +245,7 @@ export class _mainPage { }; shareView = () => { + cy.wait(3000); return cy.get(".nc-btn-share-view"); };