Skip to content

feat: Add dashboard configuration store on Parse Server #2860

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: alpha
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,19 @@ 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*.

### Saving Configuration of the server

You can save the confiugration on the server by specifying `preferencesClassName`
```json
"apps": [
{
"preferencesClassName": "DashboardPreferences"
}
]
```

Once this is set, you can visit `AppSettings > Dashboard` to save the current Column and Class settings.

### Scripts

You can specify scripts to execute Cloud Functions with the `scripts` option:
Expand Down
8 changes: 7 additions & 1 deletion jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"compilerOptions": {
"experimentalDecorators": true
"experimentalDecorators": true,
"baseUrl": "src",
"paths": {
"lib/*": ["lib/*"],
"components/*": ["components/*"],
"stylesheets/*": ["stylesheets/*"]
}
},
"typeAcquisition": {
"include": [
Expand Down
5 changes: 5 additions & 0 deletions src/dashboard/Data/Browser/Browser.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ class Browser extends DashboardView {
this.action = new SidebarAction('Create a class', this.showCreateClass.bind(this));
}

if (this.context.preferencesClassName) {
ColumnPreferences.load(this.context.preferencesClassName);
ClassPreferences.load(this.context.preferencesClassName);
}

this.props.schema.dispatch(ActionTypes.FETCH).then(() => this.handleFetchedSchema());
if (!this.props.params.className && this.props.schema.data.get('classes')) {
this.redirectToFirstClass(this.props.schema.data.get('classes'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ import * as ClassPreferences from 'lib/ClassPreferences';
import bcrypt from 'bcryptjs';
import * as OTPAuth from 'otpauth';
import QRCode from 'qrcode';
import Parse from 'parse';
import { CurrentApp } from 'context/currentApp';

export default class DashboardSettings extends DashboardView {
static contextType = CurrentApp;
constructor() {
super();
this.section = 'App Settings';
Expand Down Expand Up @@ -49,6 +52,7 @@ export default class DashboardSettings extends DashboardView {
show: false,
mfa: '',
},
showSavePreferences: !!this.context?.preferencesClassName,
};
}

Expand All @@ -63,6 +67,35 @@ export default class DashboardSettings extends DashboardView {
});
}

async saveColumns() {
const data = ColumnPreferences.getAllPreferences(this.context.applicationId);
let preferences = await new Parse.Query(this.context.preferencesClassName)
.equalTo('applicationId', this.context.applicationId)
.equalTo('key', 'columnPreferences')
.equalTo('user', Parse.User.current())
.first({ useMasterKey: true });

if (!preferences) {
preferences = new Parse.Object(this.context.preferencesClassName);
preferences.set('applicationId', this.context.applicationId);
preferences.set('key', 'columnPreferences');
preferences.set('user', Parse.User.current());
preferences.setACL(
new Parse.ACL(Parse.User.current())
);
}

preferences.set('value', JSON.stringify(data));

try {
await preferences.save(null, { useMasterKey: true });
this.showNote('Column preferences saved successfully');
} catch (error) {
this.showNote(`Error saving column preferences: ${error.message}`);
}

}

getClasses() {
const data = ClassPreferences.getAllPreferences(this.context.applicationId);
this.setState({
Expand All @@ -74,6 +107,31 @@ export default class DashboardSettings extends DashboardView {
});
}

async saveClasses() {
const data = ClassPreferences.getAllPreferences(this.context.applicationId);
let preferences = await new Parse.Query(this.context.preferencesClassName)
.equalTo('applicationId', this.context.applicationId)
.equalTo('key', 'classPreferences')
.equalTo('user', Parse.User.current())
.first({ useMasterKey: true });
if (!preferences) {
preferences = new Parse.Object(this.context.preferencesClassName);
preferences.set('applicationId', this.context.applicationId);
preferences.set('key', 'classPreferences');
preferences.set('user', Parse.User.current());
preferences.setACL(
new Parse.ACL(Parse.User.current())
);
}
preferences.set('value', JSON.stringify(data));
try {
await preferences.save(null, { useMasterKey: true });
this.showNote('Class preferences saved successfully');
} catch (error) {
this.showNote(`Error saving class preferences: ${error.message}`);
}
}

copy(data, label) {
navigator.clipboard.writeText(data);
this.showNote(`${label} copied to clipboard`);
Expand Down Expand Up @@ -367,10 +425,18 @@ export default class DashboardSettings extends DashboardView {
label={<Label text="Export Column Preferences" />}
input={<FormButton color="blue" value="Export" onClick={() => this.getColumns()} />}
/>
{this.state.showSavePreferences && <Field
label={<Label text="Save Column Preferences" />}
input={<FormButton color="blue" value="Save" onClick={() => this.saveColumns()} />}
/>}
<Field
label={<Label text="Export Class Preferences" />}
input={<FormButton color="blue" value="Export" onClick={() => this.getClasses()} />}
/>
{this.state.showSavePreferences && <Field
label={<Label text="Save Class Preferences" />}
input={<FormButton color="blue" value="Save" onClick={() => this.saveClasses()} />}
/>}
<Field
label={<Label text="Create New User" />}
input={
Expand Down
35 changes: 35 additions & 0 deletions src/lib/ClassPreferences.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
const VERSION = 1; // In case we ever need to invalidate these
import Parse from 'parse';

export const load = async (preferencesClassName) => {
const preferences = await new Parse.Query(preferencesClassName)
.equalTo('user', Parse.User.current())
.equalTo('key', 'classPreferences')
.first({ useMasterKey: true });

if (preferences) {
const prefs = preferences.get('value');
setClassPreferences(JSON.parse(prefs), Parse.applicationId);
}

}

export function setClassPreferences(classPreference, appId) {
if (!classPreference) {
return;
}

for (const className in classPreference) {
const preferences = getPreferences(appId, className) || { filters: [] };
const { filters } = classPreference[className];
for (const filter of filters) {
if (Array.isArray(filter.filter)) {
filter.filter = JSON.stringify(filter.filter);
}
if (preferences.filters.some(row => JSON.stringify(row) === JSON.stringify(filter))) {
continue;
}
preferences.filters.push(filter);
}
updatePreferences(preferences, appId, className);
}
}
export function updatePreferences(prefs, appId, className) {
try {
localStorage.setItem(path(appId, className), JSON.stringify(prefs));
Expand Down
18 changes: 18 additions & 0 deletions src/lib/ColumnPreferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ const DEFAULT_WIDTH = 150;
const COLUMN_SORT = '__columnClassesSort'; // Used for storing classes sort field
const DEFAULT_COLUMN_SORT = '-createdAt'; // Default column sorting
const cache = {};
import Parse from 'parse';

export const load = async (preferencesClassName) => {
const preferences = await new Parse.Query(preferencesClassName)
.equalTo('user', Parse.User.current())
.equalTo('key', 'columnPreferences')
.first({ useMasterKey: true });

if (preferences) {
const prefs = JSON.parse(preferences.get('value'));
if (prefs) {
for (const className in prefs) {
updatePreferences(prefs[className], Parse.applicationId, className);
}
}
}

}

export function updatePreferences(prefs, appId, className) {
try {
Expand Down
22 changes: 4 additions & 18 deletions src/lib/ParseApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import * as AJAX from 'lib/AJAX';
import encodeFormData from 'lib/encodeFormData';
import Parse from 'parse';
import { updatePreferences, getPreferences } from 'lib/ClassPreferences';
import { setClassPreferences } from 'lib/ClassPreferences';

function setEnablePushSource(setting, enable) {
const path = `/apps/${this.slug}/update_push_notifications`;
Expand Down Expand Up @@ -50,6 +50,7 @@ export default class ParseApp {
classPreference,
enableSecurityChecks,
cloudConfigHistoryLimit,
preferencesClassName
}) {
this.name = appName;
this.createdAt = created_at ? new Date(created_at) : new Date();
Expand Down Expand Up @@ -79,6 +80,7 @@ export default class ParseApp {
this.scripts = scripts;
this.enableSecurityChecks = !!enableSecurityChecks;
this.cloudConfigHistoryLimit = cloudConfigHistoryLimit;
this.preferencesClassName = preferencesClassName;

if (!supportedPushLocales) {
console.warn(
Expand Down Expand Up @@ -109,23 +111,7 @@ export default class ParseApp {
};

this.hasCheckedForMigraton = false;

if (classPreference) {
for (const className in classPreference) {
const preferences = getPreferences(appId, className) || { filters: [] };
const { filters } = classPreference[className];
for (const filter of filters) {
if (Array.isArray(filter.filter)) {
filter.filter = JSON.stringify(filter.filter);
}
if (preferences.filters.some(row => JSON.stringify(row) === JSON.stringify(filter))) {
continue;
}
preferences.filters.push(filter);
}
updatePreferences(preferences, appId, className);
}
}
setClassPreferences(classPreference, appId);
}

setParseKeys() {
Expand Down
Loading