Skip to content

Commit 7177ee0

Browse files
authored
Merge branch 'alpha' into feature/chart-visualization
2 parents 98a613b + e98ccb2 commit 7177ee0

File tree

3 files changed

+220
-7
lines changed

3 files changed

+220
-7
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2016-present, Parse, LLC
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the license found in the LICENSE file in
6+
* the root directory of this source tree.
7+
*/
8+
import Field from 'components/Field/Field.react';
9+
import Label from 'components/Label/Label.react';
10+
import Modal from 'components/Modal/Modal.react';
11+
import React from 'react';
12+
import TextInput from 'components/TextInput/TextInput.react';
13+
14+
export default class AddArrayEntryDialog extends React.Component {
15+
constructor() {
16+
super();
17+
this.state = { value: '' };
18+
this.inputRef = React.createRef();
19+
}
20+
21+
componentDidMount() {
22+
if (this.inputRef.current) {
23+
this.inputRef.current.focus();
24+
}
25+
}
26+
27+
valid() {
28+
return this.state.value !== '';
29+
}
30+
31+
getValue() {
32+
try {
33+
return JSON.parse(this.state.value);
34+
} catch {
35+
return this.state.value;
36+
}
37+
}
38+
39+
render() {
40+
return (
41+
<Modal
42+
type={Modal.Types.INFO}
43+
icon="plus-solid"
44+
title="Add entry"
45+
confirmText="Add Unique"
46+
cancelText="Cancel"
47+
onCancel={this.props.onCancel}
48+
onConfirm={() => this.props.onConfirm(this.getValue())}
49+
disabled={!this.valid()}
50+
>
51+
<Field
52+
label={
53+
<Label
54+
text="Value"
55+
description="The type is determined based on the entered value. Use quotation marks to enforce string type."
56+
/>
57+
}
58+
input={
59+
<TextInput
60+
placeholder={'Enter value'}
61+
ref={this.inputRef}
62+
value={this.state.value}
63+
onChange={value => this.setState({ value })}
64+
/>
65+
}
66+
/>
67+
</Modal>
68+
);
69+
}
70+
}

src/dashboard/Data/Config/Config.react.js

Lines changed: 134 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ActionTypes } from 'lib/stores/ConfigStore';
99
import Button from 'components/Button/Button.react';
1010
import ConfigDialog from 'dashboard/Data/Config/ConfigDialog.react';
1111
import DeleteParameterDialog from 'dashboard/Data/Config/DeleteParameterDialog.react';
12+
import AddArrayEntryDialog from 'dashboard/Data/Config/AddArrayEntryDialog.react';
1213
import EmptyState from 'components/EmptyState/EmptyState.react';
1314
import Icon from 'components/Icon/Icon.react';
1415
import { isDate } from 'lib/DateUtils';
@@ -20,9 +21,11 @@ import TableHeader from 'components/Table/TableHeader.react';
2021
import TableView from 'dashboard/TableView.react';
2122
import Toolbar from 'components/Toolbar/Toolbar.react';
2223
import browserStyles from 'dashboard/Data/Browser/Browser.scss';
24+
import configStyles from 'dashboard/Data/Config/Config.scss';
2325
import { CurrentApp } from 'context/currentApp';
2426
import Modal from 'components/Modal/Modal.react';
2527
import equal from 'fast-deep-equal';
28+
import Notification from 'dashboard/Data/Browser/Notification.react';
2629

2730
@subscribeTo('Config', 'config')
2831
class Config extends TableView {
@@ -41,7 +44,12 @@ class Config extends TableView {
4144
modalMasterKeyOnly: false,
4245
loading: false,
4346
confirmModalOpen: false,
47+
lastError: null,
48+
lastNote: null,
49+
showAddEntryDialog: false,
50+
addEntryParam: '',
4451
};
52+
this.noteTimeout = null;
4553
}
4654

4755
componentWillMount() {
@@ -107,6 +115,15 @@ class Config extends TableView {
107115
onConfirm={this.deleteParam.bind(this, this.state.modalParam)}
108116
/>
109117
);
118+
} else if (this.state.showAddEntryDialog) {
119+
extras = (
120+
<AddArrayEntryDialog
121+
onCancel={this.closeAddEntryDialog.bind(this)}
122+
onConfirm={value =>
123+
this.addArrayEntry(this.state.addEntryParam, value)
124+
}
125+
/>
126+
);
110127
}
111128

112129
if (this.state.confirmModalOpen) {
@@ -127,12 +144,24 @@ class Config extends TableView {
127144
}}
128145
>
129146
<div className={[browserStyles.confirmConfig]}>
130-
This parameter changed while you were editing it. If you continue, the latest changes will be lost and replaced with your version. Do you want to proceed?
147+
This parameter changed while you were editing it. If you continue, the latest changes
148+
will be lost and replaced with your version. Do you want to proceed?
131149
</div>
132150
</Modal>
133151
);
134152
}
135-
return extras;
153+
let notification = null;
154+
if (this.state.lastError) {
155+
notification = <Notification note={this.state.lastError} isErrorNote={true} />;
156+
} else if (this.state.lastNote) {
157+
notification = <Notification note={this.state.lastNote} isErrorNote={false} />;
158+
}
159+
return (
160+
<>
161+
{extras}
162+
{notification}
163+
</>
164+
);
136165
}
137166

138167
parseValueForModal(dataValue) {
@@ -186,7 +215,6 @@ class Config extends TableView {
186215
* Opens the modal dialog to edit the Config parameter.
187216
*/
188217
const openModal = async () => {
189-
190218
// Show dialog
191219
this.setState({
192220
loading: true,
@@ -203,7 +231,8 @@ class Config extends TableView {
203231
// Get latest param values
204232
const fetchedParams = this.props.config.data.get('params');
205233
const fetchedValue = fetchedParams.get(this.state.modalParam);
206-
const fetchedMasterKeyOnly = this.props.config.data.get('masterKeyOnly')?.get(this.state.modalParam) || false;
234+
const fetchedMasterKeyOnly =
235+
this.props.config.data.get('masterKeyOnly')?.get(this.state.modalParam) || false;
207236

208237
// Parse fetched data
209238
const { modalValue: fetchedModalValue } = this.parseValueForModal(fetchedValue);
@@ -219,6 +248,8 @@ class Config extends TableView {
219248
// Define column styles
220249
const columnStyleLarge = { width: '30%', cursor: 'pointer' };
221250
const columnStyleSmall = { width: '15%', cursor: 'pointer' };
251+
const columnStyleValue = { width: '25%', cursor: 'pointer' };
252+
const columnStyleAction = { width: '10%' };
222253

223254
const openModalValueColumn = () => {
224255
if (data.value instanceof Parse.File) {
@@ -241,13 +272,23 @@ class Config extends TableView {
241272
<td style={columnStyleSmall} onClick={openModal}>
242273
{type}
243274
</td>
244-
<td style={columnStyleLarge} onClick={openModalValueColumn}>
275+
<td style={columnStyleValue} onClick={openModalValueColumn}>
245276
{value}
246277
</td>
278+
<td style={columnStyleAction}>
279+
{type === 'Array' && (
280+
<a
281+
className={configStyles.configActionIcon}
282+
onClick={() => this.openAddEntryDialog(data.param)}
283+
>
284+
<Icon width={18} height={18} name="plus-solid" />
285+
</a>
286+
)}
287+
</td>
247288
<td style={columnStyleSmall} onClick={openModal}>
248289
{data.masterKeyOnly.toString()}
249290
</td>
250-
<td style={{ textAlign: 'center' }}>
291+
<td style={{ textAlign: 'center', width: '5%' }}>
251292
<a onClick={openDeleteParameterDialog}>
252293
<Icon width={16} height={16} name="trash-solid" fill="#ff395e" />
253294
</a>
@@ -264,9 +305,12 @@ class Config extends TableView {
264305
<TableHeader key="type" width={15}>
265306
Type
266307
</TableHeader>,
267-
<TableHeader key="value" width={30}>
308+
<TableHeader key="value" width={25}>
268309
Value
269310
</TableHeader>,
311+
<TableHeader key="action" width={10}>
312+
Action
313+
</TableHeader>,
270314
<TableHeader key="masterKeyOnly" width={15}>
271315
Master key only
272316
</TableHeader>,
@@ -430,6 +474,89 @@ class Config extends TableView {
430474
modalMasterKeyOnly: false,
431475
});
432476
}
477+
478+
showNote(message, isError) {
479+
if (!message) {
480+
return;
481+
}
482+
clearTimeout(this.noteTimeout);
483+
if (isError) {
484+
this.setState({ lastError: message, lastNote: null });
485+
} else {
486+
this.setState({ lastNote: message, lastError: null });
487+
}
488+
this.noteTimeout = setTimeout(() => {
489+
this.setState({ lastError: null, lastNote: null });
490+
}, 3500);
491+
}
492+
493+
openAddEntryDialog(param) {
494+
this.setState({ showAddEntryDialog: true, addEntryParam: param });
495+
}
496+
497+
closeAddEntryDialog() {
498+
this.setState({ showAddEntryDialog: false, addEntryParam: '' });
499+
}
500+
501+
async addArrayEntry(param, value) {
502+
try {
503+
this.setState({ loading: true });
504+
const masterKeyOnlyMap = this.props.config.data.get('masterKeyOnly');
505+
const masterKeyOnly = masterKeyOnlyMap?.get(param) || false;
506+
await Parse._request(
507+
'PUT',
508+
'config',
509+
{
510+
params: {
511+
[param]: { __op: 'AddUnique', objects: [Parse._encode(value)] },
512+
},
513+
masterKeyOnly: { [param]: masterKeyOnly },
514+
},
515+
{ useMasterKey: true }
516+
);
517+
await this.props.config.dispatch(ActionTypes.FETCH);
518+
519+
// Update config history
520+
const limit = this.context.cloudConfigHistoryLimit;
521+
const applicationId = this.context.applicationId;
522+
const params = this.props.config.data.get('params');
523+
const updatedValue = params.get(param);
524+
const configHistory = localStorage.getItem(`${applicationId}_configHistory`);
525+
const newHistoryEntry = {
526+
time: new Date(),
527+
value: updatedValue,
528+
};
529+
530+
if (!configHistory) {
531+
localStorage.setItem(
532+
`${applicationId}_configHistory`,
533+
JSON.stringify({
534+
[param]: [newHistoryEntry],
535+
})
536+
);
537+
} else {
538+
const oldConfigHistory = JSON.parse(configHistory);
539+
const updatedHistory = !oldConfigHistory[param]
540+
? [newHistoryEntry]
541+
: [newHistoryEntry, ...oldConfigHistory[param]].slice(0, limit || 100);
542+
543+
localStorage.setItem(
544+
`${applicationId}_configHistory`,
545+
JSON.stringify({
546+
...oldConfigHistory,
547+
[param]: updatedHistory,
548+
})
549+
);
550+
}
551+
552+
this.showNote('Entry added');
553+
} catch (e) {
554+
this.showNote(`Failed to add entry: ${e.message}`, true);
555+
} finally {
556+
this.setState({ loading: false });
557+
}
558+
this.closeAddEntryDialog();
559+
}
433560
}
434561

435562
export default Config;

src/dashboard/Data/Config/Config.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@import 'stylesheets/globals.scss';
2+
3+
.configActionIcon {
4+
display: inline-flex;
5+
align-items: center;
6+
justify-content: center;
7+
vertical-align: middle;
8+
width: 25px;
9+
height: 25px;
10+
cursor: pointer;
11+
svg {
12+
fill: currentColor;
13+
color: $darkBlue;
14+
}
15+
}
16+

0 commit comments

Comments
 (0)