Skip to content
Merged
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
169 changes: 163 additions & 6 deletions src/dashboard/Data/Config/Config.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Button from 'components/Button/Button.react';
import ConfigDialog from 'dashboard/Data/Config/ConfigDialog.react';
import DeleteParameterDialog from 'dashboard/Data/Config/DeleteParameterDialog.react';
import AddArrayEntryDialog from 'dashboard/Data/Config/AddArrayEntryDialog.react';
import RemoveArrayEntryDialog from 'dashboard/Data/Config/RemoveArrayEntryDialog.react';
import EmptyState from 'components/EmptyState/EmptyState.react';
import Icon from 'components/Icon/Icon.react';
import { isDate } from 'lib/DateUtils';
Expand Down Expand Up @@ -49,6 +50,9 @@ class Config extends TableView {
showAddEntryDialog: false,
addEntryParam: '',
addEntryLastType: null,
showRemoveEntryDialog: false,
removeEntryParam: '',
removeEntryArrayValue: [],
};
this.noteTimeout = null;
}
Expand Down Expand Up @@ -127,6 +131,17 @@ class Config extends TableView {
param={this.state.addEntryParam}
/>
);
} else if (this.state.showRemoveEntryDialog) {
extras = (
<RemoveArrayEntryDialog
onCancel={this.closeRemoveEntryDialog.bind(this)}
onConfirm={removeConfig =>
this.removeArrayEntry(this.state.removeEntryParam, removeConfig)
}
param={this.state.removeEntryParam}
arrayValue={this.state.removeEntryArrayValue}
/>
);
}

if (this.state.confirmModalOpen) {
Expand Down Expand Up @@ -290,12 +305,20 @@ class Config extends TableView {
</td>
<td style={columnStyleAction}>
{type === 'Array' && (
<a
className={configStyles.configActionIcon}
onClick={() => this.openAddEntryDialog(data.param)}
>
<Icon width={18} height={18} name="plus-solid" />
</a>
<>
<a
className={configStyles.configActionIcon}
onClick={() => this.openAddEntryDialog(data.param)}
>
<Icon width={18} height={18} name="plus-solid" />
</a>
<a
className={configStyles.configActionIcon}
onClick={() => this.openRemoveEntryDialog(data.param)}
>
<Icon width={18} height={18} name="minus-solid" />
</a>
</>
)}
</td>
<td style={columnStyleSmall} onClick={openModal}>
Expand Down Expand Up @@ -525,6 +548,24 @@ class Config extends TableView {
});
}

openRemoveEntryDialog(param) {
const params = this.props.config.data.get('params');
const arr = params?.get(param);
this.setState({
showRemoveEntryDialog: true,
removeEntryParam: param,
removeEntryArrayValue: Array.isArray(arr) ? arr : [],
});
}

closeRemoveEntryDialog() {
this.setState({
showRemoveEntryDialog: false,
removeEntryParam: '',
removeEntryArrayValue: [],
});
}

async addArrayEntry(param, value) {
try {
this.setState({ loading: true });
Expand Down Expand Up @@ -584,6 +625,122 @@ class Config extends TableView {
}
this.closeAddEntryDialog();
}

/**
* Gets a nested value from an object using a dot-notation path.
* @param {Object} obj - The object to extract from
* @param {string} path - The dot-notation path (e.g., "a.b.c")
* @returns {*} - The value at the path, or undefined if not found
*/
getValueAtPath(obj, path) {
if (obj === null || typeof obj !== 'object') {
return undefined;
}
const parts = path.split('.');
let current = obj;
for (const part of parts) {
if (current === null || typeof current !== 'object') {
return undefined;
}
current = current[part];
}
return current;
}

async removeArrayEntry(param, removeConfig) {
try {
this.setState({ loading: true });

// Fetch latest config data to ensure we have the current array value
await this.props.config.dispatch(ActionTypes.FETCH);

const masterKeyOnlyMap = this.props.config.data.get('masterKeyOnly');
const masterKeyOnly = masterKeyOnlyMap?.get(param) || false;

let objectsToRemove;

if (removeConfig.filterByKey) {
// Filter mode: find all objects where keyPath matches the value
const { keyPath, value } = removeConfig;
const params = this.props.config.data.get('params');
const currentArray = params?.get(param) || [];

objectsToRemove = currentArray.filter(item => {
if (item === null || typeof item !== 'object' || Array.isArray(item)) {
return false;
}
const itemValue = this.getValueAtPath(item, keyPath);
return equal(itemValue, value);
});

if (objectsToRemove.length === 0) {
this.showNote(`No matching entries found for ${keyPath} = ${JSON.stringify(value)}`, true);
this.closeRemoveEntryDialog();
return;
}
} else {
// Direct mode: remove the exact value
objectsToRemove = [removeConfig.value];
}

await Parse._request(
'PUT',
'config',
{
params: {
[param]: { __op: 'Remove', objects: objectsToRemove.map(v => Parse._encode(v)) },
},
masterKeyOnly: { [param]: masterKeyOnly },
},
{ useMasterKey: true }
);
await this.props.config.dispatch(ActionTypes.FETCH);

// Update config history
const limit = this.context.cloudConfigHistoryLimit;
const applicationId = this.context.applicationId;
const params = this.props.config.data.get('params');
const updatedValue = params.get(param);
const configHistory = localStorage.getItem(`${applicationId}_configHistory`);
const newHistoryEntry = {
time: new Date(),
value: updatedValue,
};

if (!configHistory) {
localStorage.setItem(
`${applicationId}_configHistory`,
JSON.stringify({
[param]: [newHistoryEntry],
})
);
} else {
const oldConfigHistory = JSON.parse(configHistory);
const updatedHistory = !oldConfigHistory[param]
? [newHistoryEntry]
: [newHistoryEntry, ...oldConfigHistory[param]].slice(0, limit || 100);

localStorage.setItem(
`${applicationId}_configHistory`,
JSON.stringify({
...oldConfigHistory,
[param]: updatedHistory,
})
);
}

const removedCount = objectsToRemove.length;
const message = removedCount === 1
? `Entry removed from ${param}`
: `${removedCount} entries removed from ${param}`;
this.showNote(message);
} catch (e) {
this.showNote(`Failed to remove entry: ${e.message}`, true);
} finally {
this.setState({ loading: false });
}
this.closeRemoveEntryDialog();
}
}

export default Config;
4 changes: 4 additions & 0 deletions src/dashboard/Data/Config/Config.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
width: 20px;
height: 20px;
cursor: pointer;
margin-right: 4px;
svg {
fill: currentColor;
color: $darkBlue;
}
&:last-child {
margin-right: 0;
}
}

Loading
Loading