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
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