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
Commits on Source (7)
Showing
with 220 additions and 108 deletions
......@@ -27,6 +27,7 @@ export const Navbar = () => {
const buildInfo = getEnvVariable('GRAPHPOLARIS_VERSION');
const [managementOpen, setManagementOpen] = useState<boolean>(false);
const [current, setCurrent] = useState<ManagementViews>('overview');
const [sharing, setSharing] = useState<boolean>(false);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
......@@ -45,7 +46,19 @@ export const Navbar = () => {
<GpLogo className="h-7" includeText={false} />
<ManagementTrigger managementOpen={managementOpen} setManagementOpen={setManagementOpen} current={current} setCurrent={setCurrent} />
<Button label="Share" variantType="primary" size="sm" onClick={() => auth.newShareRoom()} />
<Button
label="Share"
variantType="primary"
size="sm"
iconComponent={sharing ? 'icon-[line-md--loading-loop]' : ''}
disabled={sharing}
iconPosition="trailing"
onClick={async () => {
setSharing(true);
await auth.shareLink();
setSharing(false);
}}
/>
<div className="ml-auto">
<div className="w-fit" ref={dropdownRef}>
<Popover>
......@@ -68,9 +81,12 @@ export const Navbar = () => {
<>
<FeatureEnabled featureFlag="SHARABLE_EXPLORATION">
<DropdownItem
value="Share"
onClick={() => {
auth.newShareRoom();
value={sharing ? 'Creating Share Link' : 'Share'}
disabled={sharing}
onClick={async () => {
setSharing(true);
await auth.shareLink();
setSharing(false);
}}
/>
</FeatureEnabled>
......@@ -92,6 +108,16 @@ export const Navbar = () => {
location.replace(`${getEnvVariable('GP_AUTH_URL')}/outpost.goauthentik.io/sign_out`);
}}
/>
{authCache.authorization?.demoUser?.R && (
<DropdownItem
value="Impersonate Demo User"
onClick={() => {
const url = new URL(window.location.href);
url.searchParams.append('impersonateID', 'demoUser');
location.replace(url.toString());
}}
/>
)}
</>
) : (
<>
......
......@@ -36,7 +36,7 @@ export const InsertVariablesPlugin = () => {
}
return Object.entries(typeObj.attributes)
.map(([k, v]) => Object.keys(v).map((x) => `${k} ${x}`))
.map(([k, v]) => Object.keys(v.statistics).map((x) => `${k} ${x}`))
.flat();
}
......
......@@ -25,15 +25,15 @@ export function PreviewPlugin({ contentEditable }: { contentEditable: RefObject<
}
const result = useGraphQueryResult();
const attributeValues = useCallback(
(settings: Vis1DProps & VisualizationSettingsType) => {
if (!settings.nodeLabel || !settings.attribute) {
const getAttributeValues = useCallback(
(settings: Vis1DProps & VisualizationSettingsType, attributeKey: string | number) => {
if (!settings.nodeLabel || !attributeKey) {
return [];
}
return result.nodes
.filter((item) => item.label === settings.nodeLabel && item.attributes && settings.attribute! in item.attributes)
.map((item) => item.attributes[settings.attribute!] as string | number);
.filter((item) => item.label === settings.nodeLabel && item.attributes && attributeKey in item.attributes)
.map((item) => item.attributes[attributeKey] as string | number);
},
[result],
);
......@@ -55,22 +55,23 @@ export function PreviewPlugin({ contentEditable }: { contentEditable: RefObject<
case VariableType.statistic:
const [nodeType, feature, statistic] = name.split(' ');
const node = result.metaData?.nodes.types[nodeType];
const attribute = node?.attributes[feature] as any;
const attribute = node?.attributes[feature].statistics as any;
if (attribute == null) return '';
const value = attribute[statistic];
return ` ${value} `;
case VariableType.visualization:
const index = 0;
const activeVisualization = vis.openVisualizationArray[index] as Vis1DProps & VisualizationSettingsType;
const activeVisualization = vis.openVisualizationArray.find(x => x.name == name) as Vis1DProps & VisualizationSettingsType;
if (!activeVisualization) {
throw new Error('Tried to render non-existing visualization');
}
const data = attributeValues(activeVisualization);
const xAxisData = getAttributeValues(activeVisualization, activeVisualization.xAxisLabel!);
const yAxisData = getAttributeValues(activeVisualization, activeVisualization.yAxisLabel!);
debugger;
const plotType = activeVisualization.plotType;
const plotData = getPlotData(data, plotType);
const plotData = getPlotData(xAxisData, plotType, yAxisData);
const plot = await newPlot(document.createElement('div'), plotData, {
width: 600,
......
......@@ -18,11 +18,7 @@ import {
import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { allMLTypes, LinkPredictionInstance, setMLResult } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
import {
QueryBuilderText,
setQueryText,
setQuerybuilderNodes,
} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { QueryBuilderText, setQueryText, setQuerybuilderNodes } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { useEffect } from 'react';
import {
SaveStateI,
......@@ -140,8 +136,8 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
});
unsubs.push(
wsGetStatesSubscription((data) => {
console.debug('Save States updated', data);
wsGetStatesSubscription((data, status) => {
console.debug('Save States updated', data, status);
dispatch(updateSaveStateList(data));
const d = Object.fromEntries(data.map((x) => [x.id, x]));
loadSaveState(session.currentSaveState, d);
......@@ -156,9 +152,13 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
);
unsubs.push(
wsGetStateSubscription((data) => {
wsGetStateSubscription((data, status) => {
if (status !== 'success') {
dispatch(addError('Failed to fetch state'));
return;
}
if (data.id !== nilUUID) {
console.debug('Save State updated', data);
console.debug('Save State updated', data, status);
dispatch(addSaveState(data));
dispatch(selectSaveState(data.id));
loadSaveState(data.id, session.saveStates);
......@@ -251,7 +251,6 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
// Process URL Params
const paramSaveState = getParam(URLParams.saveState);
deleteParam(URLParams.saveState);
if (paramSaveState && paramSaveState !== nilUUID) {
wsGetState(paramSaveState);
}
......
export enum URLParams {
saveState = 'saveState',
impersonateID = 'impersonateID',
}
export function getParam(param: URLParams) {
......
......@@ -25,7 +25,7 @@ export class Broker {
private static singletonInstance: Broker;
private listeners: Record<string, Record<string, Function>> = {};
private catchAllListener: ((data: Record<string, any>, routingKey: string) => void) | undefined;
private catchAllListener: ((data: Record<string, any>, status: string, routingKey: string) => void) | undefined;
private callbackListeners: Record<string, Function> = {};
private webSocket: WebSocket | undefined;
......@@ -134,7 +134,9 @@ export class Broker {
const params = new URLSearchParams(window.location.search);
// Most of these parameters are only really used in DEV
// if (this.authHeader?.userID) params.set('userID', this.authHeader?.userID ?? '');
const impersonateID = params.get('impersonateID');
if (impersonateID) params.set('impersonateID', impersonateID);
if (this.authHeader?.roomID) params.set('roomID', this.authHeader?.roomID ?? '');
if (this.saveStateID) params.set('saveStateID', this.saveStateID ?? '');
if (this.authHeader?.sessionID) params.set('sessionID', this.authHeader?.sessionID ?? '');
......@@ -217,12 +219,13 @@ export class Broker {
let jsonObject: ReceiveMessageI = JSON.parse(event.data);
const routingKey = jsonObject.type;
const data = jsonObject.value;
const status = jsonObject.status;
const uuid = jsonObject.callID;
let stop = false; // check in case there is a specific callback listener and, if its response is true, also call the subscriptions
this.mostRecentMessages[routingKey] = data;
if (uuid in this.callbackListeners) {
stop = this.callbackListeners[uuid](data) !== true;
stop = this.callbackListeners[uuid](data, status) !== true;
console.debug(
'%c' + routingKey + ` WS response WITH CALLBACK`,
'background: #222; color: #DBAB2F',
......@@ -236,15 +239,15 @@ export class Broker {
if (!stop && this.listeners[routingKey] && Object.keys(this.listeners[routingKey]).length != 0) {
if (this.catchAllListener) {
this.catchAllListener(data, routingKey);
this.catchAllListener(data, status, routingKey);
}
Object.values(this.listeners[routingKey]).forEach((listener) => listener(data, routingKey));
Object.values(this.listeners[routingKey]).forEach((listener) => listener(data, status, routingKey));
console.debug('%c' + routingKey + ` WS response`, 'background: #222; color: #DBAB2F', data);
}
// If there are no listeners, log the message
else if (!stop) {
if (this.catchAllListener) {
this.catchAllListener(data, routingKey);
this.catchAllListener(data, status, routingKey);
console.debug(routingKey, `catch all used for message with routing key`, data);
} else {
console.debug('%c' + routingKey + ` no listeners for message with routing key`, 'background: #663322; color: #DBAB2F', data);
......
......@@ -40,7 +40,8 @@ export type subKeyTypeI =
| 'runQuery'
| 'manual'
| 'getPolicy'
| 'policyCheck';
| 'policyCheck'
| 'shareState';
export type SendMessageI = {
key: keyTypeI;
......
......@@ -53,7 +53,7 @@ export type SaveStateI = {
shareState: any;
};
type GetStateResponse = (data: SaveStateI) => void;
type GetStateResponse = (data: SaveStateI, status: string) => void;
export function wsGetState(saveStateId: string, callback?: GetStateResponse) {
Broker.instance().sendMessage(
{
......@@ -71,7 +71,7 @@ export function wsGetStateSubscription(callback: GetStateResponse) {
};
}
type GetStatesResponse = (data: SaveStateI[]) => void | boolean;
type GetStatesResponse = (data: SaveStateI[], status: string) => void | boolean;
export function wsGetStates(callback?: GetStatesResponse) {
Broker.instance().sendMessage(
{
......@@ -82,7 +82,11 @@ export function wsGetStates(callback?: GetStatesResponse) {
);
}
export function wsGetStatesSubscription(callback: GetStatesResponse) {
const id = Broker.instance().subscribe(callback, 'save_states');
const id = Broker.instance().subscribe((data: any, status: string) => {
if (data && status) {
callback(data, status);
}
}, 'save_states');
return () => {
Broker.instance().unSubscribe('save_states', id);
};
......@@ -197,3 +201,32 @@ export function wsStateGetPolicy(saveStateID: string, callback?: StateGetPolicyR
callback,
);
}
type StateSetPolicyRequest = {
saveStateId: string;
users: {
userId: string;
sharing: SaveStateAuthorizationHeaders;
}[];
};
type ShareStateResponse = (data: boolean) => void;
export function wsShareSaveState(request: StateSetPolicyRequest, callback?: ShareStateResponse) {
Broker.instance().sendMessage(
{
key: 'state',
subKey: 'shareState',
body: request,
},
callback,
);
}
export function wsShareStateSubscription(callback: ShareStateResponse) {
const id = Broker.instance().subscribe((data: any) => {
if (data) {
callback(data);
}
}, 'share_save_state');
return () => {
Broker.instance().unSubscribe('share_save_state', id);
};
}
import { getEnvVariable } from 'config';
import { useAppDispatch, useAuthCache } from '../store';
import { authenticated, changeRoom, UserAuthenticationHeader } from '../store/authSlice';
import { useAppDispatch, useAuthCache, useSessionCache } from '../store';
import { authenticated, UserAuthenticationHeader } from '../store/authSlice';
import { addInfo, addError } from '../store/configSlice';
import { wsShareSaveState } from '../api';
import { getParam, URLParams } from '../api/url';
const domain = getEnvVariable('BACKEND_URL');
const userURI = getEnvVariable('BACKEND_USER');
......@@ -14,7 +16,7 @@ export const fetchSettings: RequestInit = {
export const useAuthentication = () => {
const dispatch = useAppDispatch();
const auth = useAuthCache();
const session = useSessionCache();
const handleError = (err: any) => {
console.error(err);
......@@ -22,7 +24,8 @@ export const useAuthentication = () => {
};
const login = () => {
fetch(`${domain}${userURI}/headers`, fetchSettings)
const impersonateID = getParam(URLParams.impersonateID);
fetch(`${domain}${userURI}/headers${impersonateID ? '?impersonateID=' + impersonateID : ''}`, fetchSettings)
.then((res) =>
res
.json()
......@@ -53,32 +56,55 @@ export const useAuthentication = () => {
}
};
const newShareRoom = async () => {
const shareLink = async (): Promise<boolean> => {
if (!session.currentSaveState) {
dispatch(addError('No save state to share'));
return false;
}
try {
// TODO: Implement share room functionality when backend is ready
// fetch(`${domain}${userURI}/share`, { ...fetchSettings, method: 'POST' })
// .then((res) =>
// res
// .json()
// .then((res: { Roomid: string; Sessionid: string }) => {
// dispatch(changeRoom(res.Roomid));
// })
// .catch(handleError),
// )
// .catch(handleError);
return await new Promise((resolve, reject) => {
wsShareSaveState(
{
saveStateId: session.currentSaveState || '',
users: [
{
userId: '*', // Everyone
sharing: {
database: { R: true, W: false },
visualization: { R: true, W: false },
query: { R: true, W: false },
schema: { R: true, W: false },
},
},
],
},
async (data) => {
if (!data) {
dispatch(addError('Failed to share link'));
resolve(false);
} else {
// Authorization done, now just copy the link
const shareUrl = window.location.href;
const copied = await copyToClipboard(shareUrl);
const shareUrl = window.location.href;
const copied = await copyToClipboard(shareUrl);
if (copied) {
dispatch(addInfo('Link copied to clipboard!'));
} else {
throw new Error('Failed to copy to clipboard');
}
if (copied) {
dispatch(addInfo('Link copied to clipboard!'));
resolve(true);
} else {
console.warn('Failed to copy link to clipboard');
dispatch(addError('Failed to copy link to clipboard'));
resolve(false);
}
}
},
);
});
} catch (error: any) {
handleError(error);
return false;
}
};
return { login, newShareRoom };
};
\ No newline at end of file
return { login, shareLink };
};
......@@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { RootState } from './store';
import { cloneDeep } from 'lodash-es';
export const UserAuthorizationObjectsArray = ['savestate'] as const;
export const UserAuthorizationObjectsArray = ['savestate', 'demoUser'] as const;
export type UserAuthorizationObjects = (typeof UserAuthorizationObjectsArray)[number];
export const AuthorizationOperationsArray = ['R', 'W'] as const;
......@@ -19,6 +19,10 @@ export const UserAuthorizationHeadersDefaults: UserAuthorizationHeaders = {
R: false,
W: false,
},
demoUser: { // checks if user can impersonate demoUser to create and share demo save states
R: false,
W: false,
},
};
export type UserAuthenticationHeader = {
......
......@@ -64,6 +64,7 @@ export const sessionSlice = createSlice({
updateSaveStateList: (state: SessionCacheI, action: PayloadAction<SaveStateI[]>) => {
// Does NOT clear the states, just adds in new data
let newState: Record<string, SaveStateI> = {};
if (!action.payload) return;
action.payload.forEach((ss: SaveStateI) => {
newState[ss.id] = ss;
});
......
......@@ -57,6 +57,7 @@ export function Databases({ onClose, saveStates, changeActive, setSelectedSaveSt
const session = useSessionCache();
const databaseHandler = useHandleDatabase();
const [orderBy, setOrderBy] = useState<[DatabaseTableHeaderTypes, 'asc' | 'desc']>(['name', 'desc']);
const [sharing, setSharing] = useState<boolean>(false);
const orderedSaveStates = useMemo(
() =>
......@@ -174,9 +175,12 @@ export function Databases({ onClose, saveStates, changeActive, setSelectedSaveSt
}}
/>
<DropdownItem
value="Share"
onClick={() => {
auth.newShareRoom();
value={sharing ? 'Creating Share Link' : 'Share'}
disabled={sharing}
onClick={async () => {
setSharing(true);
await auth.shareLink();
setSharing(false);
}}
/>
<DropdownItem
......
......@@ -66,12 +66,13 @@ export function QueryLogicPill(node: SchemaReactflowLogicNode) {
return null;
}
console.log('node', node);
return (
<LogicPill
title={
<div className="flex flex-row justify-between items-center">
<span>{connectionsToLeft[0]?.attributes?.sourceHandleData.attributeName}</span>
<DropdownContainer>
<DropdownTrigger size="md">
<Button
......@@ -155,7 +156,7 @@ export function QueryLogicPill(node: SchemaReactflowLogicNode) {
</div>
);
})}
{/* {!!node.data.logic.output && (
{['upper_case', 'lower_case'].includes(node.data.logic.output.name) && (
<Handle
type={'source'}
position={Position.Right}
......@@ -167,7 +168,7 @@ export function QueryLogicPill(node: SchemaReactflowLogicNode) {
})}
className={styleHandleMap?.[output.type]}
></Handle>
)} */}
)}
</div>
</LogicPill>
);
......
......@@ -27,10 +27,6 @@ interface Props {
onRemove?: () => void;
}
const onInit = (reactFlowInstance: ReactFlowInstance) => {
setTimeout(() => reactFlowInstance.fitView(), 100);
};
const graphEntityPillNodeTypes = {
entity: SchemaEntityPill,
relation: SchemaRelationPill,
......@@ -92,9 +88,10 @@ export const Schema = (props: Props) => {
layout.current = layoutFactory.createLayout(settings.layoutName);
}
const maxZoom = 1.2;
const fitView = () => {
if (reactFlowInstanceRef.current) {
reactFlowInstanceRef.current.fitView();
reactFlowInstanceRef.current.fitView({maxZoom});
}
};
......@@ -376,7 +373,7 @@ export const Schema = (props: Props) => {
edges={edges}
onInit={(reactFlowInstance) => {
reactFlowInstanceRef.current = reactFlowInstance;
onInit(reactFlowInstance);
setTimeout(() => fitView(), 100);
}}
onClick={handleOnClick}
proOptions={{ hideAttribution: true }}
......
......@@ -11,7 +11,7 @@ export const SelectionConfig = () => {
if (!selection) return null;
return (
<div className="border-b py-2">
<div className="border-b py-2 overflow-auto">
<div className="flex justify-between items-center px-4 py-2">
<span className="text-xs font-bold">Selection</span>
<Button
......@@ -27,19 +27,19 @@ export const SelectionConfig = () => {
{selection.content.map((item, index) => (
<React.Fragment key={index + 'id'}>
<div className="flex justify-between items-center px-4 py-1 gap-1">
<span className="text-xs font-normal">ID</span>
<span className="text-xs font-semibold pr-2">ID</span>
<span className="text-xs">{item._id}</span>
</div>
<div key={index + 'label'} className="flex justify-between items-center px-4 py-1 gap-1">
<span className="text-xs font-normal">Label</span>
<span className="text-xs font-semibold pr-2">Label</span>
<EntityPill title={item.attributes['labels'] as string}></EntityPill>
</div>
{Object.entries(item.attributes).map(([key, value]) => {
if (key === 'labels' || key === '_id') return null;
if (key === 'labels' || key === '_id' || value instanceof Object) return null;
return (
<div key={index + key} className="flex justify-between items-center px-4 py-1 gap-1">
<span className="text-xs font-normal break-all max-w-[6rem]">{String(key)}</span>
<span className="text-xs break-all">{value as string}</span>
<span className="text-xs font-semibold pr-2 whitespace-nowrap max-w-[6rem]">{String(key)}</span>
<span className="text-xs break-all">{String(value)}</span>
</div>
);
})}
......
......@@ -68,15 +68,16 @@ const Vis0D = forwardRef<Vis0DVisHandle, VisualizationPropTypes<Vis0DProps>>(({
useEffect(() => {
if (settings.selectedEntity != '' && graphMetadata.nodes.types && settings.selectedAttribute != '' && settings.selectedStat != '') {
const nodesLabels = graphMetadata.nodes.labels;
const edgesLabels = graphMetadata.edges.labels;
let attributes = [];
let attributes: string[] = [];
if (nodesLabels.includes(settings.selectedEntity)) {
attributes = Object.keys(graphMetadata.nodes.types[settings.selectedEntity].attributes);
} else {
} else if (edgesLabels.includes(settings.selectedEntity)) {
attributes = Object.keys(graphMetadata.edges.types[settings.selectedEntity].attributes);
}
if (attributes.includes(settings.selectedAttribute)) {
if (attributes.length > 0 && attributes.includes(settings.selectedAttribute)) {
let statsAvailable = [];
if (nodesLabels.includes(settings.selectedEntity)) {
......@@ -126,7 +127,6 @@ const Vis0D = forwardRef<Vis0DVisHandle, VisualizationPropTypes<Vis0DProps>>(({
const Vis0DSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<Vis0DProps>) => {
const [attributeOptions, setAttributeOptions] = useState<string[]>([]);
const [statsOptions, setStatsOptions] = useState<string[]>([]);
useEffect(() => {
if (settings.selectedEntity === '' && graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0) {
const firstEntity = graphMetadata.nodes.labels[0];
......@@ -156,18 +156,23 @@ const Vis0DSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
graphMetadata.nodes.labels.length > 0
) {
const nodesLabels = graphMetadata.nodes.labels;
const edgesLabels = graphMetadata.edges.labels;
// attribute management
let attributesFirstEntity = [];
let attributesFirstEntity: string[] = [];
if (nodesLabels.includes(settings.selectedEntity)) {
attributesFirstEntity = Object.keys(graphMetadata.nodes.types[settings.selectedEntity].attributes);
} else {
} else if (edgesLabels.includes(settings.selectedEntity)) {
attributesFirstEntity = Object.keys(graphMetadata.edges.types[settings.selectedEntity].attributes);
}
setAttributeOptions(attributesFirstEntity);
let selectedAttribute = '';
if (settings.selectedAttribute === '' || !attributesFirstEntity.includes(settings.selectedAttribute)) {
if (
settings.selectedAttribute === '' ||
!attributesFirstEntity.includes(settings.selectedAttribute) ||
attributesFirstEntity.length === 0
) {
selectedAttribute = attributesFirstEntity[0];
updateSettings({ selectedAttribute: selectedAttribute });
} else {
......@@ -182,7 +187,7 @@ const Vis0DSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
string,
number
>;
} else {
} else if (edgesLabels.includes(settings.selectedEntity)) {
attributeSelectedStatistics = graphMetadata.edges.types[settings.selectedEntity].attributes[selectedAttribute].statistics as Record<
string,
number
......
......@@ -10,7 +10,7 @@ import { Button } from '@graphpolaris/shared/lib/components/buttons';
export interface Vis1DProps {
plotType: (typeof plotTypeOptions)[number]; // plotly plot type
title: string; // title of the plot
nodeLabel: string; // node label to plot
selectedEntity: string; // node label to plot
xAxisLabel?: string;
yAxisLabel?: string;
showAxis: boolean;
......@@ -19,7 +19,7 @@ export interface Vis1DProps {
const defaultSettings: Vis1DProps = {
plotType: 'bar',
title: '',
nodeLabel: '',
selectedEntity: '',
xAxisLabel: '',
yAxisLabel: '',
showAxis: true,
......@@ -68,17 +68,17 @@ const Vis1D = forwardRef<Vis1DVisHandle, VisualizationPropTypes<Vis1DProps>>(({
}));
const getAttributeValues = (attributeKey: string | number | undefined) => {
if (!settings.nodeLabel || !attributeKey) {
if (!settings.selectedEntity || !attributeKey) {
return [];
}
return data.nodes
.filter((item) => item.label === settings.nodeLabel && item.attributes && attributeKey in item.attributes)
.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.nodeLabel, settings.xAxisLabel]);
const yAxisData = useMemo(() => getAttributeValues(settings.yAxisLabel), [data, settings.nodeLabel, settings.yAxisLabel]);
const xAxisData = useMemo(() => getAttributeValues(settings.xAxisLabel), [data, settings.selectedEntity, settings.xAxisLabel]);
const yAxisData = useMemo(() => getAttributeValues(settings.yAxisLabel), [data, settings.selectedEntity, settings.yAxisLabel]);
return (
<div className="h-full w-full flex items-center justify-center overflow-hidden" ref={internalRef}>
......@@ -96,26 +96,36 @@ const Vis1D = forwardRef<Vis1DVisHandle, VisualizationPropTypes<Vis1DProps>>(({
});
const Vis1DSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<Vis1DProps>) => {
// !TODO: include relationship
const mutablePlotTypes = [...plotTypeOptions];
const [attributeOptions, setAttributeOptions] = useState<string[]>([]);
useEffect(() => {
if (settings.nodeLabel === '' && graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0) {
const nodeLabelTemp = graphMetadata.nodes.labels[0];
updateSettings({ nodeLabel: nodeLabelTemp });
if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0) {
if (settings.selectedEntity === '') {
updateSettings({ selectedEntity: graphMetadata.nodes.labels[0] });
} else {
const labelNodes = graphMetadata.nodes.labels;
if (!labelNodes.includes(settings.selectedEntity)) {
updateSettings({ selectedEntity: graphMetadata.nodes.labels[0] });
}
}
}
}, [settings.nodeLabel, graphMetadata]);
}, [graphMetadata]);
useEffect(() => {
if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0 && settings.nodeLabel != '') {
const newAttributeOptions = Object.keys(graphMetadata.nodes.types[settings.nodeLabel].attributes);
if (settings.xAxisLabel === '') {
updateSettings({ xAxisLabel: newAttributeOptions[0] });
if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0 && settings.selectedEntity != '') {
const labelNodes = graphMetadata.nodes.labels;
if (labelNodes.includes(settings.selectedEntity)) {
const newAttributeOptions = Object.keys(graphMetadata.nodes.types[settings.selectedEntity].attributes);
if (settings.xAxisLabel === '') {
updateSettings({ xAxisLabel: newAttributeOptions[0] });
}
setAttributeOptions(newAttributeOptions);
} else {
}
// initialize the selected option for creating the dropdown and plots
setAttributeOptions(newAttributeOptions);
}
}, [graphMetadata, settings.nodeLabel]);
}, [graphMetadata, settings.selectedEntity]);
return (
<SettingsContainer>
......@@ -123,14 +133,14 @@ const Vis1DSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
<Input
className="w-full text-justify justify-start mb-2"
type="dropdown"
value={settings.nodeLabel}
value={settings.selectedEntity}
options={graphMetadata.nodes.labels}
onChange={(val) => updateSettings({ nodeLabel: val as string })}
onChange={(val) => updateSettings({ selectedEntity: val as string })}
overrideRender={
<EntityPill
title={
<div className="flex flex-row justify-between items-center cursor-pointer">
<span>{settings.nodeLabel || ''}</span>
<span>{settings.selectedEntity || ''}</span>
<Button variantType="secondary" variant="ghost" size="2xs" iconComponent="icon-[ic--baseline-arrow-drop-down]" />
</div>
}
......