From 08d017c5c318514f10cca4e3fc0cc76228895ffe Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 9 Jun 2023 23:50:42 +1000 Subject: [PATCH 01/10] feat: allow exporting of classPreference --- .../CategoryList/CategoryList.react.js | 4 ++- .../DashboardSettings.react.js | 26 +++++++++++++------ .../DashboardSettings/DashboardSettings.scss | 2 +- src/lib/ClassPreferences.js | 17 ++++++++++++ src/lib/ParseApp.js | 21 ++++++++++++++- 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/components/CategoryList/CategoryList.react.js b/src/components/CategoryList/CategoryList.react.js index 82afff31c5..1fd4962ee2 100644 --- a/src/components/CategoryList/CategoryList.react.js +++ b/src/components/CategoryList/CategoryList.react.js @@ -136,7 +136,7 @@ export default class CategoryList extends React.Component { {this.state.openClasses.includes(id) && c.filters.map((filterData, index) => { - const { name, filter } = filterData; + const { name, filter, readonly } = filterData; const url = `${this.props.linkPrefix}${c.name}?filters=${encodeURIComponent(filter)}`; return (
@@ -150,6 +150,7 @@ export default class CategoryList extends React.Component { > {name} + {!readonly && { @@ -159,6 +160,7 @@ export default class CategoryList extends React.Component { > × + }
); })} diff --git a/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js b/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js index 3dfc4d4bc7..c528c19fd0 100644 --- a/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js +++ b/src/dashboard/Settings/DashboardSettings/DashboardSettings.react.js @@ -16,6 +16,7 @@ import Toolbar from 'components/Toolbar/Toolbar.react'; import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react'; import Notification from 'dashboard/Data/Browser/Notification.react'; import * as ColumnPreferences from 'lib/ColumnPreferences'; +import * as ClassPreferences from 'lib/ClassPreferences'; import bcrypt from 'bcryptjs'; import * as OTPAuth from 'otpauth'; import QRCode from 'qrcode'; @@ -38,9 +39,10 @@ export default class DashboardSettings extends DashboardView { message: null, passwordInput: '', passwordHidden: true, - columnData: { + copyData: { data: '', show: false, + type: '' }, newUser: { data: '', @@ -53,7 +55,14 @@ export default class DashboardSettings extends DashboardView { getColumns() { const data = ColumnPreferences.getAllPreferences(this.context.applicationId); this.setState({ - columnData: { data: JSON.stringify(data, null, 2), show: true }, + copyData: { data: JSON.stringify(data, null, 2), show: true, type: 'Column Preferences' }, + }); + } + + getClasses() { + const data = ClassPreferences.getAllPreferences(this.context.applicationId); + this.setState({ + copyData: { data: JSON.stringify(data, null, 2), show: true, type: 'Class Preferences' }, }); } @@ -190,14 +199,14 @@ export default class DashboardSettings extends DashboardView { this.createUser()} />} /> ); - const columnPreferences = ( + const copyData = (
-
- +
+
-
); @@ -225,9 +234,10 @@ export default class DashboardSettings extends DashboardView {
} input={ this.getColumns()} />} /> + } input={ this.getClasses()} />} /> } input={ this.setState({ createUserInput: true })} />} />
- {this.state.columnData.show && columnPreferences} + {this.state.copyData.show && copyData} {this.state.createUserInput && createUserInput} {this.state.newUser.show && userData} diff --git a/src/dashboard/Settings/DashboardSettings/DashboardSettings.scss b/src/dashboard/Settings/DashboardSettings/DashboardSettings.scss index 579c813055..66ddd63796 100644 --- a/src/dashboard/Settings/DashboardSettings/DashboardSettings.scss +++ b/src/dashboard/Settings/DashboardSettings/DashboardSettings.scss @@ -1,4 +1,4 @@ -.columnData { +.copyData { max-height: 50vh; overflow-y: scroll; } diff --git a/src/lib/ClassPreferences.js b/src/lib/ClassPreferences.js index 75cc0d7b1e..6e7603f480 100644 --- a/src/lib/ClassPreferences.js +++ b/src/lib/ClassPreferences.js @@ -38,3 +38,20 @@ export function getPreferences(appId, className) { function path(appId, className) { return `ParseDashboard:${VERSION}:${appId}:ClassPreference:${className}`; } + +export function getAllPreferences(appId) { + const storageKeys = Object.keys(localStorage); + const result = {}; + for (const key of storageKeys) { + const split = key.split(':') + if (split.length <= 1 || split[2] !== appId) { + continue; + } + const className = split.at(-1); + const preferences = getPreferences(appId, className); + if (preferences) { + result[className] = preferences; + } + } + return result; +} diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js index 7a4bb1b888..19e87bfe06 100644 --- a/src/lib/ParseApp.js +++ b/src/lib/ParseApp.js @@ -8,6 +8,7 @@ import * as AJAX from 'lib/AJAX'; import encodeFormData from 'lib/encodeFormData'; import Parse from 'parse'; +import { updatePreferences, getPreferences } from 'lib/ClassPreferences'; function setEnablePushSource(setting, enable) { let path = `/apps/${this.slug}/update_push_notifications`; @@ -44,7 +45,8 @@ export default class ParseApp { supportedPushLocales, preventSchemaEdits, graphQLServerURL, - columnPreference + columnPreference, + classPreference }) { this.name = appName; this.createdAt = created_at ? new Date(created_at) : new Date(); @@ -97,6 +99,23 @@ export default class ParseApp { } this.hasCheckedForMigraton = false; + + if (classPreference) { + for (const className in classPreference) { + const preferences = getPreferences(appId, className) || {filters: []}; + console.log(preferences.filters); + const {filters} = classPreference[className]; + for (const filter of filters) { + filter.readonly = true; + // console.log(JSON.stringify(row) === JSON.stringify(filter), filter); + if (preferences.filters.some(row => JSON.stringify(row) === JSON.stringify(filter))) { + continue; + } + preferences.filters.push(filter); + } + updatePreferences(preferences, appId, className); + } + } } setParseKeys() { From 7b3278c3070cfdc652099939e2b5c6a6e2c9c70e Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 9 Jun 2023 23:52:30 +1000 Subject: [PATCH 02/10] Update ParseApp.js --- src/lib/ParseApp.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js index 19e87bfe06..dca5b4fc9d 100644 --- a/src/lib/ParseApp.js +++ b/src/lib/ParseApp.js @@ -102,12 +102,10 @@ export default class ParseApp { if (classPreference) { for (const className in classPreference) { - const preferences = getPreferences(appId, className) || {filters: []}; - console.log(preferences.filters); - const {filters} = classPreference[className]; + const preferences = getPreferences(appId, className) || { filters: [] }; + const { filters } = classPreference[className]; for (const filter of filters) { filter.readonly = true; - // console.log(JSON.stringify(row) === JSON.stringify(filter), filter); if (preferences.filters.some(row => JSON.stringify(row) === JSON.stringify(filter))) { continue; } From 8e9e1f47d1af7a0566a057cba9cbb73bae179379 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 10 Jun 2023 14:26:52 +1000 Subject: [PATCH 03/10] Update README.md --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb73ee0ee7..0dd552b5e0 100644 --- a/README.md +++ b/README.md @@ -329,13 +329,33 @@ If you have classes with a lot of columns and you filter them often with the sam { "name": "email", "filterSortToTop": true - } + } ] } } ] ``` +### Persistent Filters + +`classPreference` allows for filters to be saved across sessions. To save the current filters, navigate to App Settings and click "Export Class Preferences". + +```json +"apps": [ + { + "classPreference": { + "_Role": { + "filters": [ + { + "name": "Filter Name", + "filter": "[{\"field\":\"objectId\",\"constraint\":\"exists\"}]" + } + ] + } + } +] +``` + # Running as Express Middleware Instead of starting Parse Dashboard with the CLI, you can also run it as an [express](https://github.com/expressjs/express) middleware. @@ -452,7 +472,7 @@ With MFA enabled, a user must provide a one-time password that is typically boun The user requires an authenticator app to generate the one-time password. These apps are provided by many 3rd parties and mostly for free. -If you create a new user by running `parse-dashboard --createUser`, you will be asked whether you want to enable MFA for the new user. To enable MFA for an existing user, +If you create a new user by running `parse-dashboard --createUser`, you will be asked whether you want to enable MFA for the new user. To enable MFA for an existing user, run `parse-dashboard --createMFA` to generate a `mfa` secret that you then add to the existing user configuration, for example: ```json From a6eec539a306e07ee3a86bf540b5f021b8cf689f Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 10 Jun 2023 12:37:35 +0200 Subject: [PATCH 04/10] rewording Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dd552b5e0..3ea7afc7d7 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,9 @@ If you have classes with a lot of columns and you filter them often with the sam ### Persistent Filters -`classPreference` allows for filters to be saved across sessions. To save the current filters, navigate to App Settings and click "Export Class Preferences". +`classPreference` allows for filters to be saved across sessions. To save the current filters, navigate to *App Settings > Export Class Preferences*. + +For example: ```json "apps": [ From 46cdabeb828de8ec50877d2f2e8482288a9e0222 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 10 Jun 2023 12:39:04 +0200 Subject: [PATCH 05/10] rewording Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ea7afc7d7..a63a67a284 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ If you have classes with a lot of columns and you filter them often with the sam ### Persistent Filters -`classPreference` allows for filters to be saved across sessions. To save the current filters, navigate to *App Settings > Export Class Preferences*. +The `classPreference` setting allows for data browser filters to be saved across sessions. To save the current filters, navigate to *App Settings > Export Class Preferences*. For example: From 9b11ae50e708458fbc945feb2ce0dfbb3195b3de Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 10 Jun 2023 12:42:08 +0200 Subject: [PATCH 06/10] fix code example Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- README.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a63a67a284..6c89c5b591 100644 --- a/README.md +++ b/README.md @@ -343,19 +343,16 @@ The `classPreference` setting allows for data browser filters to be saved across For example: ```json -"apps": [ - { - "classPreference": { - "_Role": { - "filters": [ - { - "name": "Filter Name", - "filter": "[{\"field\":\"objectId\",\"constraint\":\"exists\"}]" - } - ] - } - } -] +"apps": [{ + "classPreference": { + "_Role": { + "filters": [{ + "name": "Filter Name", + "filter": "[{\"field\":\"objectId\",\"constraint\":\"exists\"}]" + }] + } + } +}] ``` # Running as Express Middleware From 960b656bb3e5ac161c6bc97e13927abb9214ec32 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 10 Jun 2023 21:38:06 +1000 Subject: [PATCH 07/10] support JSON --- README.md | 7 ++++++- src/components/CategoryList/CategoryList.react.js | 2 -- src/lib/ClassPreferences.js | 6 ++++++ src/lib/ParseApp.js | 4 +++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6c89c5b591..4eac4e5ed2 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,12 @@ For example: "_Role": { "filters": [{ "name": "Filter Name", - "filter": "[{\"field\":\"objectId\",\"constraint\":\"exists\"}]" + "filter": [ + { + "field": "objectId", + "constraint": "exists" + } + ] }] } } diff --git a/src/components/CategoryList/CategoryList.react.js b/src/components/CategoryList/CategoryList.react.js index 1fd4962ee2..22fe5d0bea 100644 --- a/src/components/CategoryList/CategoryList.react.js +++ b/src/components/CategoryList/CategoryList.react.js @@ -150,7 +150,6 @@ export default class CategoryList extends React.Component { > {name} - {!readonly && { @@ -160,7 +159,6 @@ export default class CategoryList extends React.Component { > × - }
); })} diff --git a/src/lib/ClassPreferences.js b/src/lib/ClassPreferences.js index 6e7603f480..b36b1a5a7b 100644 --- a/src/lib/ClassPreferences.js +++ b/src/lib/ClassPreferences.js @@ -50,6 +50,12 @@ export function getAllPreferences(appId) { const className = split.at(-1); const preferences = getPreferences(appId, className); if (preferences) { + preferences.filters = preferences.filters.map(filter => { + if (typeof filter.filter === 'string') { + filter.filter = JSON.parse(filter.filter); + } + return filter; + }); result[className] = preferences; } } diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js index dca5b4fc9d..b71d14dc48 100644 --- a/src/lib/ParseApp.js +++ b/src/lib/ParseApp.js @@ -105,7 +105,9 @@ export default class ParseApp { const preferences = getPreferences(appId, className) || { filters: [] }; const { filters } = classPreference[className]; for (const filter of filters) { - filter.readonly = true; + if (Array.isArray(filter.filter)) { + filter.filter = JSON.stringify(filter.filter); + } if (preferences.filters.some(row => JSON.stringify(row) === JSON.stringify(filter))) { continue; } From 239974fa5edae2b541e90fa2e8ba2e6960214185 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 10 Jun 2023 21:47:55 +1000 Subject: [PATCH 08/10] Update CategoryList.react.js --- src/components/CategoryList/CategoryList.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CategoryList/CategoryList.react.js b/src/components/CategoryList/CategoryList.react.js index 22fe5d0bea..82afff31c5 100644 --- a/src/components/CategoryList/CategoryList.react.js +++ b/src/components/CategoryList/CategoryList.react.js @@ -136,7 +136,7 @@ export default class CategoryList extends React.Component {
{this.state.openClasses.includes(id) && c.filters.map((filterData, index) => { - const { name, filter, readonly } = filterData; + const { name, filter } = filterData; const url = `${this.props.linkPrefix}${c.name}?filters=${encodeURIComponent(filter)}`; return (
From bb1f0109f557dd31cc825114deeba10d095bf7e9 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 10 Jun 2023 15:08:10 +0200 Subject: [PATCH 09/10] style Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 4eac4e5ed2..e650b155d2 100644 --- a/README.md +++ b/README.md @@ -476,8 +476,7 @@ With MFA enabled, a user must provide a one-time password that is typically boun The user requires an authenticator app to generate the one-time password. These apps are provided by many 3rd parties and mostly for free. -If you create a new user by running `parse-dashboard --createUser`, you will be asked whether you want to enable MFA for the new user. To enable MFA for an existing user, -run `parse-dashboard --createMFA` to generate a `mfa` secret that you then add to the existing user configuration, for example: +If you create a new user by running `parse-dashboard --createUser`, you will be asked whether you want to enable MFA for the new user. To enable MFA for an existing user, run `parse-dashboard --createMFA` to generate a `mfa` secret that you then add to the existing user configuration, for example: ```json { From 206cc2195664486b4e342e05119ff35e8c90b4d4 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Sat, 10 Jun 2023 15:22:36 +0200 Subject: [PATCH 10/10] extending docs Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e650b155d2..0a12d09e8c 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ If you have classes with a lot of columns and you filter them often with the sam ### Persistent Filters -The `classPreference` setting allows for data browser filters to be saved across sessions. To save the current filters, navigate to *App Settings > Export Class Preferences*. +The filters you save in the data browser of Parse Dashboard are only available for the current dashboard user in the current browser session. To make filters permanently available for all dashboard users of an app, you can define filters in the `classPreference` setting. For example: @@ -360,6 +360,8 @@ For example: }] ``` +You can conveniently create a filter definition without having to write it by hand by first saving a filter in the data browser, then exporting the filter definition under *App Settings > Export Class Preferences*. + # Running as Express Middleware Instead of starting Parse Dashboard with the CLI, you can also run it as an [express](https://github.com/expressjs/express) middleware.