Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • graphpolaris/frontend-v2
  • rijkheere/frontend-v-2-reordering-paoh
2 results
Show changes
import React, { useEffect, useState } from 'react';
import { ReportingForm } from './reporting/ReportingForm';
import { AlertingForm } from './alerting/AlertingForm';
import { Button, Icon } from '../components';
import { AddItem } from './components/AddItem';
import { StartScreen } from './components/StartScreen';
import { MonitorType } from './components/Sidebar';
type Props = {
active: string;
activeCategory: MonitorType | undefined;
adding: boolean;
setAdding: (val: boolean) => void;
setActiveCategory: (val: MonitorType | undefined) => void;
};
export function SettingsPanel(props: Props) {
const [contacts, setContacts] = useState<string[]>([]);
const getContacts = (): string[] => ['Jan', 'Piet'];
useEffect(() => {
if (!contacts) {
const userContacts = getContacts();
setContacts(userContacts);
}
}, []);
return props.activeCategory ? (
<div className="w-3/4 p-4">
{props.adding ? (
<AddItem category={props.activeCategory} />
) : (
props.active &&
props.activeCategory && (
<div>
{props.activeCategory === 'report' ? (
<ReportingForm activeTemplate={props.active} />
) : (
<AlertingForm activeTemplate={props.active} />
)}
</div>
)
)}
<div className="flex justify-end mt-2">
<Button label="Delete" variantType="secondary" variant="outline" />
<Button label="Save" variantType="primary" className="ml-2" />
</div>
</div>
) : (
<StartScreen setAdding={props.setAdding} setActiveCategory={props.setActiveCategory} />
);
}
import React, { useState } from 'react';
import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '../../components/accordion';
import { TextEditor } from '../../components/textEditor';
import { EditorState } from 'lexical';
type Props = {
activeTemplate: string;
};
export function AlertingForm(props: Props) {
const [editorState, setEditorState] = useState<EditorState | undefined>(undefined);
return (
<div>
<span className="text-lg text-secondary-600 font-bold mb-4">Alert ID: {props.activeTemplate}</span>
<Accordion defaultOpenAll={true} className="border-t divide-y">
<AccordionItem className="pt-2 pb-4">
<AccordionHead showArrow={false}>
<span className="font-semibold">Recipient(s)</span>
</AccordionHead>
<AccordionBody>
<div>
<input className="border" />
</div>
</AccordionBody>
</AccordionItem>
<AccordionItem className="pt-2 pb-4">
<AccordionHead showArrow={false}>
<span className="font-semibold">Alerting text</span>
</AccordionHead>
<AccordionBody>
<TextEditor
editorState={editorState}
setEditorState={setEditorState}
showToolbar={true}
placeholder="Start typing your alert template..."
/>
</AccordionBody>
</AccordionItem>
</Accordion>
</div>
);
}
import React, { useState } from 'react';
import { Input } from '../../components';
import { MonitorType } from './Sidebar';
import { Input, Button } from '../../components';
import { MonitorType as InsightType, MonitorType } from './Sidebar';
import { useAppDispatch, useSessionCache } from '../../data-access';
import { addInsight } from '../../data-access/store/insightSharingSlice';
import { wsCreateInsight } from '../../data-access/broker/wsInsightSharing';
import { addError, addSuccess, } from '@graphpolaris/shared/lib/data-access/store/configSlice';
type Props = {
category: MonitorType;
setAdding: (val: false | MonitorType) => void;
setActive: (val: string) => void;
type: InsightType;
};
export function AddItem(props: Props) {
const [value, setValue] = useState<string>('');
const [name, setName] = useState<string>('');
const [description, setDescription] = useState<string>('');
const dispatch = useAppDispatch();
const session = useSessionCache();
const handleSave = async () => {
if (!name.trim()) {
dispatch(addError('Name is required'));
return;
}
const newInsight = {
name,
description,
recipients: [],
template: '',
frequency: props.type === 'report' ? 'Daily' : '',
saveStateId: session.currentSaveState || '',
type: props.type,
};
wsCreateInsight(newInsight, (data: any, status: string) => {
if (status === 'success') {
dispatch(addInsight(data));
props.setActive(data.id);
props.setAdding(false);
dispatch(addSuccess('Succesfully created ' + props.type));
} else {
console.error('Failed to create insight:', data);
dispatch(addError('Failed to create new ' + props.type))
}
});
};
return (
<div>
<span className="text-lg text-secondary-600 font-bold mb-4">Add a new {props.category}ing service</span>
<Input type="text" label="Name" value={value} onChange={setValue} />
<span className="text-lg text-secondary-600 font-bold mb-4">Add a new {props.type}ing service</span>
<Input type="text" label="Name" value={name} onChange={setName} className="mb-2" />
<Input type="text" label="Description" value={description} onChange={setDescription} className="mb-2" />
<Button label="Save" onClick={handleSave} disabled={!name || !description} className="mt-2" />
</div>
);
}
import React from 'react';
import React, { useEffect } from 'react';
import { Button } from '../../components';
import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
import { useAppDispatch, useInsights } from '../../data-access';
import { setInsights } from '../../data-access/store/insightSharingSlice';
import { useSessionCache } from '../../data-access';
import { wsGetInsights } from '../../data-access/broker/wsInsightSharing';
export type MonitorType = 'report' | 'alert';
type SidebarProps = {
reports: string[];
alerts: string[];
changeActive: (category: MonitorType, val: string) => void;
setAdding: (val: boolean) => void;
setActiveCategory: (val: MonitorType | undefined) => void;
setAdding: (val: false | MonitorType) => void;
setActive: (val: string) => void;
};
export function Sidebar(props: SidebarProps) {
const dispatch = useAppDispatch();
const session = useSessionCache();
const insights = useInsights();
useEffect(() => {
if (session.currentSaveState && session.currentSaveState !== '') {
wsGetInsights(session.currentSaveState, (data: any, status: string) => {
dispatch(setInsights(data));
});
}
}, [session.currentSaveState]);
return (
<div className="w-1/4 border-r overflow-auto flex flex-col h-full">
<>
<span className="text-lg text-secondary-700 font-semibold px-2 py-4">Insight Sharing</span>
<Accordion defaultOpenIndex={0}>
<Accordion defaultOpenAll={true}>
<AccordionItem className="">
<AccordionHead className="border-b bg-secondary-50 hover:bg-secondary-100 p-1">
<div className="w-full flex justify-between">
<span className="font-semibold">Reports</span>
<div
onClick={(e) => {
props.setAdding(true);
props.setActiveCategory('report');
props.setAdding('report');
e.stopPropagation();
}}
>
......@@ -41,18 +53,20 @@ export function Sidebar(props: SidebarProps) {
</AccordionHead>
<AccordionBody className="ml-0">
<ul className="space-y-2">
{props.reports.map((name, index) => (
<li
key={index}
className="cursor-pointer p-2 hover:bg-secondary-50"
onClick={() => {
props.changeActive('report', name);
props.setAdding(false);
}}
>
{name}
</li>
))}
{insights
.filter((insight) => insight.type === 'report')
.map((report) => (
<li
key={report.id}
className="cursor-pointer p-2 hover:bg-secondary-50"
onClick={() => {
props.setAdding(false);
props.setActive(report.id);
}}
>
{report.name}
</li>
))}
</ul>
</AccordionBody>
</AccordionItem>
......@@ -62,8 +76,7 @@ export function Sidebar(props: SidebarProps) {
<span className="font-semibold">Alerts</span>
<div
onClick={(e) => {
props.setAdding(true);
props.setActiveCategory('alert');
props.setAdding('alert');
e.stopPropagation();
}}
>
......@@ -80,22 +93,24 @@ export function Sidebar(props: SidebarProps) {
</AccordionHead>
<AccordionBody className="ml-0">
<ul className="space-y-2">
{props.alerts.map((name, index) => (
<li
key={index}
className="cursor-pointer p-2 hover:bg-secondary-50"
onClick={() => {
props.changeActive('alert', name);
props.setAdding(false);
}}
>
{name}
</li>
))}
{insights
.filter((insight) => insight.type === 'alert')
.map((alert) => (
<li
key={alert.id}
className="cursor-pointer p-2 hover:bg-secondary-50"
onClick={() => {
props.setAdding(false);
props.setActive(alert.id);
}}
>
{alert.name}
</li>
))}
</ul>
</AccordionBody>
</AccordionItem>
</Accordion>
</div>
</>
);
}
......@@ -3,13 +3,12 @@ import { Button, Icon } from '../../components';
import { MonitorType } from './Sidebar';
type Props = {
setAdding: (val: boolean) => void;
setActiveCategory: (val: MonitorType | undefined) => void;
setAdding: (val: false | MonitorType) => void;
};
export function StartScreen(props: Props) {
return (
<div className="w-full flex justify-center items-center">
<div className="w-full h-full flex justify-center items-center">
<div className="">
<span className="text-lg text-secondary-700 font-bold mb-4">Start</span>
<div>
......@@ -19,8 +18,7 @@ export function StartScreen(props: Props) {
variant="outline"
className="mb-2"
onClick={() => {
props.setAdding(true);
props.setActiveCategory('report');
props.setAdding('report');
}}
/>
<Button
......@@ -28,8 +26,7 @@ export function StartScreen(props: Props) {
label="New alert"
variant="outline"
onClick={() => {
props.setAdding(true);
props.setActiveCategory('alert');
props.setAdding('alert');
}}
/>
</div>
......
import React, { useState } from 'react';
import { Input, LoadingSpinner } from '../../components';
import { Accordion, AccordionBody, AccordionHead, AccordionItem } from '../../components/accordion';
import { TextEditor } from '../../components/textEditor';
import { EditorState } from 'lexical';
type Props = {
activeTemplate: string;
};
export function ReportingForm(props: Props) {
const [loading, setLoading] = useState(false);
const [editorState, setEditorState] = useState<EditorState | undefined>(undefined);
return loading ? (
<LoadingSpinner />
) : (
props.activeTemplate && (
<div>
<span className="text-lg text-secondary-600 font-bold mb-4">Report ID: {props.activeTemplate}</span>
<Accordion defaultOpenAll={true} className="border-t divide-y">
<AccordionItem className="pt-2 pb-4">
<AccordionHead showArrow={false}>
<span className="font-semibold">Recipient(s)</span>
</AccordionHead>
<AccordionBody>
<div>
<input className="border" />
</div>
</AccordionBody>
</AccordionItem>
<AccordionItem className="pt-2 pb-4">
<AccordionHead showArrow={false}>
<span className="font-semibold">Repeat</span>
</AccordionHead>
<AccordionBody>
<Input label="Frequency" type="dropdown" value={'Daily'} onChange={() => {}} options={['Daily', 'Weekly']} className="mb-1" />
</AccordionBody>
</AccordionItem>
<AccordionItem className="pt-2 pb-4">
<AccordionHead showArrow={false}>
<span className="font-semibold">Email template</span>
</AccordionHead>
<AccordionBody>
<TextEditor
editorState={editorState}
setEditorState={setEditorState}
showToolbar={true}
placeholder="Start typing your report template..."
/>
</AccordionBody>
</AccordionItem>
</Accordion>
</div>
)
);
}
......@@ -17,7 +17,7 @@ import { updateVisualization, addVisualization } from '../../data-access/store/v
import { VisualizationPropTypes, VISComponentType } from '../common';
import { ErrorBoundary } from '../../components/errorBoundary';
import { addError } from '../../data-access/store/configSlice';
import { canViewFeature } from '@graphpolaris/shared/lib/components/featureFlags/featureFlags';
import { canViewFeature } from '../../components/featureFlags';
type PromiseFunc = () => Promise<{ default: VISComponentType<any> }>;
export const Visualizations: Record<string, PromiseFunc> = {
......
......@@ -6,6 +6,7 @@ import { CustomChartPlotly, plotTypeOptions } from './components/CustomChartPlot
import { Input } from '@graphpolaris/shared/lib/components/inputs';
import { EntityPill } from '@graphpolaris/shared/lib/components/pills/Pill';
import { Button } from '@graphpolaris/shared/lib/components/buttons';
import { GraphQueryResult } from '@graphpolaris/shared/lib/data-access';
export interface Vis1DProps {
plotType: (typeof plotTypeOptions)[number]; // plotly plot type
......@@ -13,7 +14,10 @@ export interface Vis1DProps {
selectedEntity: string; // node label to plot
xAxisLabel?: string;
yAxisLabel?: string;
zAxisLabel?: string;
showAxis: boolean;
groupData?: string;
stack: boolean;
}
const defaultSettings: Vis1DProps = {
......@@ -22,13 +26,34 @@ const defaultSettings: Vis1DProps = {
selectedEntity: '',
xAxisLabel: '',
yAxisLabel: '',
zAxisLabel: '',
showAxis: true,
groupData: undefined,
stack: false,
};
export interface Vis1DVisHandle {
exportImageInternal: () => void;
}
export const getAttributeValues = (query: GraphQueryResult, selectedEntity: string, attributeKey: string | number | undefined): any[] => {
if (!selectedEntity || !attributeKey) {
return [];
}
if (attributeKey == ' ') {
return [];
}
return query.nodes
.filter((item) => item.label === selectedEntity)
.map((item) => {
// Check if the attribute exists, return its value if it does, or an empty string otherwise
return item.attributes && attributeKey in item.attributes && item.attributes[attributeKey] != ''
? item.attributes[attributeKey]
: 'NoData';
});
};
const Vis1D = forwardRef<Vis1DVisHandle, VisualizationPropTypes<Vis1DProps>>(({ data, settings }, refExternal) => {
const internalRef = useRef<HTMLDivElement>(null);
......@@ -67,29 +92,33 @@ const Vis1D = forwardRef<Vis1DVisHandle, VisualizationPropTypes<Vis1DProps>>(({
},
}));
const getAttributeValues = (attributeKey: string | number | undefined) => {
if (!settings.selectedEntity || !attributeKey) {
return [];
}
return data.nodes
.filter((item) => item.label === settings.selectedEntity && item.attributes && attributeKey in item.attributes)
.map((item) => item.attributes[attributeKey]);
};
const xAxisData = useMemo(() => getAttributeValues(settings.xAxisLabel), [data, settings.selectedEntity, settings.xAxisLabel]);
const yAxisData = useMemo(() => getAttributeValues(settings.yAxisLabel), [data, settings.selectedEntity, settings.yAxisLabel]);
const xAxisData = useMemo(
() => getAttributeValues(data, settings.selectedEntity, settings.xAxisLabel),
[data, settings.selectedEntity, settings.xAxisLabel],
);
const yAxisData = useMemo(
() => getAttributeValues(data, settings.selectedEntity, settings.yAxisLabel),
[data, settings.selectedEntity, settings.yAxisLabel],
);
const zAxisData = useMemo(
() => getAttributeValues(data, settings.selectedEntity, settings.zAxisLabel),
[data, settings.selectedEntity, settings.zAxisLabel],
);
return (
<div className="h-full w-full flex items-center justify-center overflow-hidden" ref={internalRef}>
<CustomChartPlotly
xAxisData={xAxisData as string[] | number[]}
yAxisData={yAxisData as string[] | number[]}
zAxisData={zAxisData as string[] | number[]}
plotType={settings.plotType}
title={settings.title}
showAxis={settings.showAxis}
xAxisLabel={settings.xAxisLabel}
yAxisLabel={settings.yAxisLabel}
zAxisLabel={settings.zAxisLabel}
groupBy={settings.groupData}
stack={settings.stack}
/>
</div>
);
......@@ -120,7 +149,15 @@ const Vis1DSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
const newAttributeOptions = Object.keys(graphMetadata.nodes.types[settings.selectedEntity].attributes);
if (settings.xAxisLabel === '') {
updateSettings({ xAxisLabel: newAttributeOptions[0] });
// !TODO: instead of contain "datum" chekc type: if it is date
if (newAttributeOptions[0].includes('Datum')) {
updateSettings({ groupData: 'yearly' });
} else {
updateSettings({ groupData: undefined });
}
}
newAttributeOptions.unshift(' ');
setAttributeOptions(newAttributeOptions);
} else {
}
......@@ -158,6 +195,9 @@ const Vis1DSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
options={mutablePlotTypes}
onChange={(value: string | number) => {
updateSettings({ plotType: value as (typeof plotTypeOptions)[number] });
if (value === 'bar' || value === 'histogram' || value === 'pie') {
updateSettings({ yAxisLabel: '' });
}
}}
/>
</div>
......@@ -168,7 +208,14 @@ const Vis1DSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
value={settings.xAxisLabel}
options={attributeOptions}
onChange={(value) => {
updateSettings({ xAxisLabel: value as string });
const valueString = value as string;
updateSettings({ xAxisLabel: valueString });
if (!valueString.includes('Datum')) {
updateSettings({ groupData: undefined });
} else {
updateSettings({ groupData: 'monthly' });
}
}}
/>
</div>
......@@ -185,6 +232,37 @@ const Vis1DSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
/>
</div>
)}
{(settings.plotType === 'bar' || settings.plotType === 'scatter' || settings.plotType === 'histogram') && (
<div className="mb-2">
<Input
type="dropdown"
label="Color:"
value={settings.zAxisLabel}
options={attributeOptions}
onChange={(value) => {
updateSettings({ zAxisLabel: value as string });
}}
/>
</div>
)}
{settings.plotType === 'histogram' && (
<div className="mb-2">
<Input type="boolean" label="Normalize: " value={settings.stack} onChange={(val) => updateSettings({ stack: val })} />
</div>
)}
{settings.xAxisLabel?.includes('Datum') && (
<div className="mb-2">
<Input
type="dropdown"
label="Group Time:"
value={settings.groupData}
options={['', 'monthly', 'quarterly', 'yearly']}
onChange={(value) => {
updateSettings({ groupData: value as string });
}}
/>
</div>
)}
<div className="mb-2">
<Input type="boolean" label="Show axis" value={settings.showAxis} onChange={(val) => updateSettings({ showAxis: val })} />
</div>
......