Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions src/dashboard/Data/CustomDashboard/AddElementDialog.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const elementTypes = [
title: 'Data Table',
description: 'Show filtered data in a table format',
},
{
type: 'view',
icon: 'visibility',
title: 'View',
description: 'Display data from a saved View',
},
];

const AddElementDialog = ({ onClose, onSelectType }) => {
Expand Down
219 changes: 177 additions & 42 deletions src/dashboard/Data/CustomDashboard/CustomDashboard.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import GraphElement from './elements/GraphElement.react';
import GraphConfigDialog from './elements/GraphConfigDialog.react';
import DataTableElement from './elements/DataTableElement.react';
import DataTableConfigDialog from './elements/DataTableConfigDialog.react';
import ViewElement from './elements/ViewElement.react';
import ViewConfigDialog from './elements/ViewConfigDialog.react';
import GraphPreferencesManager from 'lib/GraphPreferencesManager';
import ViewPreferencesManager from 'lib/ViewPreferencesManager';
import FilterPreferencesManager from 'lib/FilterPreferencesManager';
import CanvasPreferencesManager from 'lib/CanvasPreferencesManager';
import { CurrentApp } from 'context/currentApp';
Expand Down Expand Up @@ -52,11 +55,13 @@ class CustomDashboard extends DashboardView {
showStaticTextDialog: false,
showGraphDialog: false,
showDataTableDialog: false,
showViewDialog: false,
showSaveDialog: false,
showLoadDialog: false,
editingElement: null,
availableGraphs: {},
availableFilters: {},
availableViews: [],
classes: [],
classSchemas: {},
autoReloadInterval: 0,
Expand Down Expand Up @@ -139,9 +144,9 @@ class CustomDashboard extends DashboardView {
currentCanvasGroup: canvas.group || null,
hasUnsavedChanges: false,
}, () => {
// Fetch data for all graph and data table elements
// Fetch data for all graph, data table, and view elements
canvas.elements?.forEach(element => {
if (element.type === 'graph' || element.type === 'dataTable') {
if (element.type === 'graph' || element.type === 'dataTable' || element.type === 'view') {
this.fetchElementData(element.id);
}
});
Expand Down Expand Up @@ -209,6 +214,7 @@ class CustomDashboard extends DashboardView {
this.setState({ classes, classSchemas }, () => {
this.loadAvailableGraphs();
this.loadAvailableFilters();
this.loadAvailableViews();
});
}

Expand Down Expand Up @@ -262,6 +268,22 @@ class CustomDashboard extends DashboardView {
this.setState({ availableFilters: filtersByClass });
}

async loadAvailableViews() {
if (!this.context || !this.context.applicationId) {
return;
}

const viewPreferencesManager = new ViewPreferencesManager(this.context);

try {
const views = await viewPreferencesManager.getViews(this.context.applicationId);
this.setState({ availableViews: views || [] });
} catch (e) {
console.error('Error loading views:', e);
this.setState({ availableViews: [] });
}
}

handleAddElement = (type) => {
this.setState({ showAddDialog: false });
switch (type) {
Expand All @@ -274,6 +296,9 @@ class CustomDashboard extends DashboardView {
case 'dataTable':
this.setState({ showDataTableDialog: true, editingElement: null });
break;
case 'view':
this.setState({ showViewDialog: true, editingElement: null });
break;
}
};

Expand Down Expand Up @@ -376,6 +401,41 @@ class CustomDashboard extends DashboardView {
}
};

handleSaveView = (config) => {
const { editingElement, elements } = this.state;

if (editingElement) {
const updatedElements = elements.map(el =>
el.id === editingElement.id ? { ...el, config } : el
);
this.setState({
elements: updatedElements,
showViewDialog: false,
editingElement: null,
}, () => {
this.fetchElementData(editingElement.id);
this.markUnsavedChanges();
});
} else {
const newElement = {
id: generateId(),
type: 'view',
x: 50,
y: 50,
width: 500,
height: 300,
config,
};
this.setState({
elements: [...elements, newElement],
showViewDialog: false,
}, () => {
this.fetchElementData(newElement.id);
this.markUnsavedChanges();
});
}
};

async fetchElementData(elementId) {
const element = this.state.elements.find(el => el.id === elementId);
if (!element) {
Expand Down Expand Up @@ -403,51 +463,71 @@ class CustomDashboard extends DashboardView {
}));

try {
const { className, filterConfig, sortField, sortOrder, limit } = config;
const query = new Parse.Query(className);

if (filterConfig && Array.isArray(filterConfig)) {
filterConfig.forEach(savedFilter => {
// Saved filters have structure: { id, name, filter: '[{field, constraint, compareTo}]' }
// The 'filter' property contains a JSON string array of filter conditions
if (savedFilter.filter) {
try {
const conditions = typeof savedFilter.filter === 'string'
? JSON.parse(savedFilter.filter)
: savedFilter.filter;
if (Array.isArray(conditions)) {
conditions.forEach(condition => {
this.applyFilterToQuery(query, condition);
});
let data;

if (type === 'view') {
// Handle View element - uses aggregation pipeline or cloud function
const { cloudFunction, query: viewQuery, className } = config;

if (cloudFunction) {
// Cloud Function view
const results = await Parse.Cloud.run(cloudFunction, {}, { useMasterKey: true });
data = this.normalizeViewResults(results);
} else if (viewQuery && Array.isArray(viewQuery) && className) {
// Aggregation pipeline view
const results = await new Parse.Query(className).aggregate(viewQuery, { useMasterKey: true });
data = this.normalizeViewResults(results);
} else {
throw new Error('Invalid view configuration');
}
} else {
// Handle DataTable and Graph elements
const { className, filterConfig, sortField, sortOrder, limit } = config;
const query = new Parse.Query(className);

if (filterConfig && Array.isArray(filterConfig)) {
filterConfig.forEach(savedFilter => {
// Saved filters have structure: { id, name, filter: '[{field, constraint, compareTo}]' }
// The 'filter' property contains a JSON string array of filter conditions
if (savedFilter.filter) {
try {
const conditions = typeof savedFilter.filter === 'string'
? JSON.parse(savedFilter.filter)
: savedFilter.filter;
if (Array.isArray(conditions)) {
conditions.forEach(condition => {
this.applyFilterToQuery(query, condition);
});
}
} catch (e) {
console.error('Error parsing filter conditions:', e);
}
} catch (e) {
console.error('Error parsing filter conditions:', e);
}
}
});
}
});
}

if (sortField) {
if (sortOrder === 'descending') {
query.descending(sortField);
} else {
query.ascending(sortField);
if (sortField) {
if (sortOrder === 'descending') {
query.descending(sortField);
} else {
query.ascending(sortField);
}
}
}

if (limit != null) {
const numericLimit = Number(limit);
if (Number.isFinite(numericLimit) && numericLimit >= 0) {
query.limit(numericLimit);
if (limit != null) {
const numericLimit = Number(limit);
if (Number.isFinite(numericLimit) && numericLimit >= 0) {
query.limit(numericLimit);
} else {
query.limit(1000);
}
} else {
query.limit(1000);
}
} else {
query.limit(1000);
}

const results = await query.find({ useMasterKey: true });
const data = results.map(obj => obj.toJSON());
const results = await query.find({ useMasterKey: true });
data = results.map(obj => obj.toJSON());
}

// Check if component is still mounted and this is the latest request
if (!this._isMounted || localToken !== this._elementSeq[elementId]) {
Expand Down Expand Up @@ -539,6 +619,38 @@ class CustomDashboard extends DashboardView {
}
}

normalizeViewResults(results) {
// Normalize Parse.Object instances to raw JSON for consistent rendering
const normalizeValue = val => {
if (val && typeof val === 'object' && val instanceof Parse.Object) {
return {
__type: 'Pointer',
className: val.className,
objectId: val.id
};
}
if (val && typeof val === 'object' && !Array.isArray(val)) {
const normalized = {};
Object.keys(val).forEach(key => {
normalized[key] = normalizeValue(val[key]);
});
return normalized;
}
if (Array.isArray(val)) {
return val.map(normalizeValue);
}
return val;
};

return results.map(item => {
const normalized = {};
Object.keys(item).forEach(key => {
normalized[key] = normalizeValue(item[key]);
});
return normalized;
});
}

handleSelectElement = (id) => {
this.setState({ selectedElement: id });
};
Expand Down Expand Up @@ -600,6 +712,9 @@ class CustomDashboard extends DashboardView {
case 'dataTable':
this.setState({ showDataTableDialog: true });
break;
case 'view':
this.setState({ showViewDialog: true });
break;
}
};

Expand All @@ -610,7 +725,7 @@ class CustomDashboard extends DashboardView {
handleReloadAll = () => {
const { elements } = this.state;
elements.forEach(element => {
if (element.type === 'graph' || element.type === 'dataTable') {
if (element.type === 'graph' || element.type === 'dataTable' || element.type === 'view') {
this.fetchElementData(element.id);
}
});
Expand Down Expand Up @@ -719,9 +834,9 @@ class CustomDashboard extends DashboardView {
// Update URL to include canvas ID
this.navigateToCanvas(canvas.id);

// Fetch data for all graph and data table elements
// Fetch data for all graph, data table, and view elements
canvas.elements?.forEach(element => {
if (element.type === 'graph' || element.type === 'dataTable') {
if (element.type === 'graph' || element.type === 'dataTable' || element.type === 'view') {
this.fetchElementData(element.id);
}
});
Expand Down Expand Up @@ -895,6 +1010,16 @@ class CustomDashboard extends DashboardView {
onRefresh={() => this.handleRefreshElement(element.id)}
/>
);
case 'view':
return (
<ViewElement
config={element.config}
data={data?.data}
isLoading={data?.isLoading}
error={data?.error}
onRefresh={() => this.handleRefreshElement(element.id)}
/>
);
default:
return null;
}
Expand Down Expand Up @@ -957,7 +1082,7 @@ class CustomDashboard extends DashboardView {
} = this.state;

const hasDataElements = elements.some(
el => el.type === 'graph' || el.type === 'dataTable'
el => el.type === 'graph' || el.type === 'dataTable' || el.type === 'view'
);

const hasElements = elements.length > 0;
Expand Down Expand Up @@ -1085,11 +1210,13 @@ class CustomDashboard extends DashboardView {
showStaticTextDialog,
showGraphDialog,
showDataTableDialog,
showViewDialog,
showSaveDialog,
showLoadDialog,
editingElement,
availableGraphs,
availableFilters,
availableViews,
classes,
classSchemas,
savedCanvases,
Expand Down Expand Up @@ -1139,6 +1266,14 @@ class CustomDashboard extends DashboardView {
onSave={this.handleSaveDataTable}
/>
)}
{showViewDialog && (
<ViewConfigDialog
initialConfig={editingElement?.config}
availableViews={availableViews}
onClose={() => this.setState({ showViewDialog: false, editingElement: null })}
onSave={this.handleSaveView}
/>
)}
{showSaveDialog && (
<SaveCanvasDialog
currentName={currentCanvasName}
Expand Down
Loading
Loading