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 (4)
Showing
with 161 additions and 275 deletions
......@@ -29,3 +29,4 @@ VIS1D=true
INSIGHT_SHARING=true
VIEWER_PERMISSIONS=true
SHARABLE_EXPLORATION=true
VITE_SHARABLE_EXPLORATION=true
GRAPHPOLARIS_VERSION=
BACKEND_URL=
BACKEND_WSS_URL=
STAGING=
SKIP_LOGIN=
BACKEND_USER=
......
......@@ -141,7 +141,7 @@ export function App(props: App) {
</div>
<FrozenOverlay>
{!auth.authentication?.authenticated && <span>Not Authenticated</span>}
{!auth.authorization.savestate.W && !session.currentSaveState && (
{!auth.authorization.savestate?.W && !session.currentSaveState && (
<span>Viewer account not authorized. Please load a shared exploration.</span>
)}
</FrozenOverlay>
......
......@@ -75,7 +75,7 @@ export const Navbar = () => {
/>
</FeatureEnabled>
<FeatureEnabled featureFlag="VIEWER_PERMISSIONS">
{authCache.authorization?.savestate?.W && authorization.database.W && (
{authCache.authorization?.savestate?.W && authorization.database?.W && (
<DropdownItem
value="Viewer Permissions"
onClick={() => {
......
......@@ -4,7 +4,6 @@ export const envVar = {
GRAPHPOLARIS_VERSION: import.meta.env.GRAPHPOLARIS_VERSION,
BACKEND_URL: import.meta.env.BACKEND_URL,
BACKEND_WSS_URL: import.meta.env.BACKEND_WSS_URL,
STAGING: import.meta.env.STAGING,
SKIP_LOGIN: import.meta.env.SKIP_LOGIN,
BACKEND_USER: import.meta.env.BACKEND_USER,
......
......@@ -15,13 +15,13 @@ export type NodeAttributes = { [key: string]: unknown };
export interface GraphQueryResultFromBackend {
nodes: {
_id: string;
label?: string;
label: string;
attributes: NodeAttributes;
}[];
edges: {
_id: string;
label?: string;
label: string;
attributes: NodeAttributes;
from: string;
to: string;
......@@ -78,29 +78,6 @@ export const graphQueryBackend2graphQuery = (payload: GraphQueryResultFromBacken
let nodes = [...nodeIDs].map((nodeID) => payload.nodes.find((node) => node._id === nodeID) as unknown as Node);
let edges = [...edgeIDs].map((edgeID) => payload.edges.find((edge) => edge._id === edgeID) as unknown as Edge);
nodes = nodes.map((node) => {
let _node = { ...node };
// TODO!: only works for neo4j
let nodeType: string = node._id.split('/')[0];
let innerLabels = node?.attributes?.labels as string[];
if (innerLabels?.length > 0) nodeType = innerLabels[0] as string;
_node.label = nodeType;
return _node;
});
edges = edges.map((edge) => {
let _edge = { ...edge };
// TODO!: only works for neo4j
let edgeType: string = edge._id.split('/')[0];
if (!_edge._id.includes('/')) edgeType = edge.attributes.Type as string;
_edge.label = edgeType;
return _edge;
});
const metaData = getGraphStatistics(payload);
return { metaData, nodes, edges };
......
......@@ -27,6 +27,13 @@ const defaultStateAuthorizationHeaders: SaveStateAuthorizationHeaders = {
schema: { W: false, R: false },
};
const newStateAuthorizationHeaders: SaveStateAuthorizationHeaders = {
query: { W: true, R: true },
database: { W: true, R: true },
visualization: { W: true, R: true },
schema: { W: true, R: true },
};
// Define the initial state using that type
export const initialState: SessionCacheI = {
currentSaveState: undefined,
......@@ -91,7 +98,7 @@ export const sessionSlice = createSlice({
state.saveStates[action.payload.id] = action.payload;
state.currentSaveState = action.payload.id;
if (!state.saveStatesAuthorization[action.payload.id]) {
state.saveStatesAuthorization[action.payload.id] = cloneDeep(defaultStateAuthorizationHeaders);
state.saveStatesAuthorization[action.payload.id] = cloneDeep(newStateAuthorizationHeaders);
}
},
deleteSaveState: (state: SessionCacheI, action: PayloadAction<string>) => {
......
......@@ -42,13 +42,13 @@ export interface BackendQueryFormat {
/** Interface for an entity in the JSON for the query. */
export interface QueryStruct {
ID: string;
id: string;
node: NodeStruct;
}
export interface NodeStruct {
label?: string;
ID?: string;
id?: string;
logic?: AllLogicStatement;
filter?: FilterStruct[];
relation?: RelationStruct;
......@@ -57,12 +57,12 @@ export interface NodeStruct {
}
export interface ExportNodeStruct {
ID: string;
id: string;
attribute: string;
}
export interface LogicStruct {
ID: string;
id: string;
attribute: string;
operation: LogicOperationType;
}
......@@ -74,7 +74,7 @@ export interface FilterStruct {
}
export interface RelationStruct {
ID?: string;
id?: string;
label?: string;
depth: QuerySearchDepthStruct;
direction: 'TO' | 'FROM' | 'BOTH';
......@@ -92,14 +92,14 @@ export type LogicOperationType = 'AND' | 'OR';
/** Interface for an entity in the JSON for the query. */
export interface Entity {
name: string;
ID: number;
id: number;
constraints: Constraint[];
}
/** Interface for an relation in the JSON for the query. */
export interface Relation {
name: string;
ID: number;
id: number;
fromType: string;
fromID: number;
toType: string;
......@@ -111,7 +111,7 @@ export interface Relation {
// /** Interface for an relation in the JSON for the query. */
// export interface Relation {
// name: string;
// ID: number;
// id: number;
// fromType: string;
// fromID: number;
// toType: string;
......@@ -153,7 +153,7 @@ export interface Constraint {
// /** Interface for a function in the JSON for the query. */
// export interface GroupBy {
// ID: number;
// id: number;
// groupType: string;
// groupID: number[];
......@@ -170,7 +170,7 @@ export interface Constraint {
// /** Interface for Machine Learning algorithm */
export interface MachineLearning {
ID?: number;
id?: number;
type: MLTypes;
parameters?: string[];
}
......
......@@ -29,7 +29,6 @@ export const PillAttributes = (props: PillAttributesProps) => {
{attributesOfInterest &&
attributesOfInterest.map((showing, i) => {
if (!showing) return null;
console.log('props.attributes', props.attributes);
return (
<PillAttributesItem
key={props.attributes[i].handleData.attributeName || i}
......
......@@ -21,6 +21,7 @@ const defaultSettings: QueryBuilderSettings = {
depth: { min: 0, max: 1 },
layout: 'manual',
autocompleteRelation: true,
unionTypes: {},
};
describe('QueryUtils Entity and Relations', () => {
......@@ -67,16 +68,16 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: '0',
id: '0',
label: 'Airport 1',
relation: {
ID: '1',
id: '1',
label: 'Flight between airports',
direction: 'TO',
node: {
ID: '10',
id: '10',
label: 'Airport 2',
relation: undefined,
},
......@@ -131,16 +132,16 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e0',
id: 'e0',
label: 'Airport 1',
relation: {
ID: 'r1',
id: 'r1',
label: 'Flight between airports',
direction: 'TO',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 2',
relation: undefined,
},
......@@ -148,16 +149,16 @@ describe('QueryUtils Entity and Relations', () => {
},
},
{
ID: 'path_1',
id: 'path_1',
node: {
ID: 'e12',
id: 'e12',
label: 'Airport 12',
relation: {
ID: 'r12',
id: 'r12',
label: 'Flight between airports 2',
direction: 'TO',
node: {
ID: 'e22',
id: 'e22',
label: 'Airport 22',
relation: undefined,
},
......@@ -200,35 +201,35 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e2', label: 'Airport 2' } },
},
},
{
ID: 'path_1',
id: 'path_1',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e22', label: 'Airport 22' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e22', label: 'Airport 22' } },
},
},
{
ID: 'path_2',
id: 'path_2',
node: {
ID: 'e12',
id: 'e12',
label: 'Airport 12',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e2', label: 'Airport 2' } },
},
},
{
ID: 'path_3',
id: 'path_3',
node: {
ID: 'e12',
id: 'e12',
label: 'Airport 12',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e22', label: 'Airport 22' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e22', label: 'Airport 22' } },
},
},
],
......@@ -263,15 +264,15 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e0',
id: 'e0',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 2' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e1', label: 'Airport 2' } },
},
},
{ ID: 'path_1', node: { ID: 'e12', label: 'Airport 12' } },
{ ID: 'path_2', node: { ID: 'e22', label: 'Airport 22' } },
{ id: 'path_1', node: { id: 'e12', label: 'Airport 12' } },
{ id: 'path_2', node: { id: 'e22', label: 'Airport 22' } },
],
};
let ret = Query2BackendQuery('database', graph.export(), defaultSettings);
......@@ -312,16 +313,16 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e0',
id: 'e0',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 2' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e1', label: 'Airport 2' } },
},
},
{
ID: 'path_1',
node: { relation: { ID: 'r2', label: 'Flight between airports 2', direction: 'TO', node: {} } },
id: 'path_1',
node: { relation: { id: 'r2', label: 'Flight between airports 2', direction: 'TO', node: {} } },
},
],
};
......@@ -351,11 +352,11 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e0',
id: 'e0',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: {} },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: {} },
},
},
],
......@@ -386,9 +387,9 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e0', label: 'Airport 2' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e0', label: 'Airport 2' } },
},
},
],
......@@ -434,19 +435,19 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e2', label: 'Airport 2' } },
},
},
{
ID: 'path_1',
id: 'path_1',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { ID: 'r2', label: 'Flight between airports 2', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
relation: { id: 'r2', label: 'Flight between airports 2', direction: 'TO', node: { id: 'e2', label: 'Airport 2' } },
},
},
],
......@@ -478,11 +479,11 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e1', label: 'Airport 1' } },
},
},
],
......@@ -516,24 +517,24 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: {
ID: 'r1',
id: 'r1',
label: 'Flight between airports',
direction: 'TO',
node: { ID: 'e2', label: 'Airport 1', relation: undefined },
node: { id: 'e2', label: 'Airport 1', relation: undefined },
},
},
},
{
ID: 'path_1',
id: 'path_1',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e1', label: 'Airport 1' } },
},
},
],
......@@ -567,19 +568,19 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e1', label: 'Airport 1' } },
},
},
{
ID: 'path_1',
id: 'path_1',
node: {
ID: 'e2',
id: 'e2',
label: 'Airport 1',
relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
relation: { id: 'r1', label: 'Flight between airports', direction: 'TO', node: { id: 'e1', label: 'Airport 1' } },
},
},
],
......@@ -600,11 +601,11 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: { direction: 'BOTH', node: { ID: 'e2', label: 'Airport 1' } },
relation: { direction: 'BOTH', node: { id: 'e2', label: 'Airport 1' } },
},
},
],
......@@ -644,13 +645,13 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
relation: {
ID: 'r1',
id: 'r1',
label: 'Flight between airports',
direction: 'TO',
node: { relation: { ID: 'r2', label: 'Flight between airports 2', direction: 'TO', node: {} } },
node: { relation: { id: 'r2', label: 'Flight between airports 2', direction: 'TO', node: {} } },
},
},
},
......@@ -673,17 +674,17 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: {
direction: 'BOTH',
node: {
ID: 'e2', label: 'Airport 2',
id: 'e2', label: 'Airport 2',
relation: {
direction: 'BOTH',
node: { ID: 'e1', label: 'Airport 1' }
node: { id: 'e1', label: 'Airport 1' }
},
}
},
......@@ -706,12 +707,12 @@ describe('QueryUtils Entity and Relations', () => {
...defaultQuery,
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1', label: 'Airport 1',
id: 'e1', label: 'Airport 1',
relation: {
direction: 'BOTH',
node: { ID: 'e1', label: 'Airport 1' }
node: { id: 'e1', label: 'Airport 1' }
}
},
},
......@@ -792,9 +793,9 @@ describe('QueryUtils with Logic', () => {
machineLearning: [],
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: undefined,
},
......@@ -865,17 +866,17 @@ describe('QueryUtils with Logic', () => {
machineLearning: [],
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: undefined,
},
},
{
ID: 'path_1',
id: 'path_1',
node: {
ID: 'e2',
id: 'e2',
label: 'Airport 2',
relation: undefined,
},
......@@ -933,9 +934,9 @@ describe('QueryUtils with Logic', () => {
machineLearning: [],
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: undefined,
},
......@@ -984,9 +985,9 @@ describe('QueryUtils with Logic', () => {
machineLearning: [],
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: undefined,
},
......@@ -1045,26 +1046,26 @@ it('should no connections between entities and relations', () => {
machineLearning: [],
query: [
{
ID: 'path_0',
id: 'path_0',
node: {
ID: 'e1',
id: 'e1',
label: 'Airport 1',
relation: undefined,
},
},
{
ID: 'path_1',
id: 'path_1',
node: {
ID: 'e2',
id: 'e2',
label: 'Airport 2',
relation: undefined,
},
},
{
ID: 'path_2',
id: 'path_2',
node: {
relation: {
ID: 'r1',
id: 'r1',
label: 'Relation 1',
direction: 'TO',
node: {},
......
......@@ -268,7 +268,7 @@ export function Query2BackendQuery(
if (currNode.type === QueryElementTypes.Relation) {
const _currNode = currNode as RelationNodeAttributes;
const ret: RelationStruct = {
ID: _currNode.id,
id: _currNode.id,
label: _currNode.name || undefined,
depth: _currNode.depth,
direction: !_currNode.direction || _currNode.direction === 'right' ? 'TO' : _currNode.direction === 'left' ? 'FROM' : 'BOTH',
......@@ -277,7 +277,7 @@ export function Query2BackendQuery(
return ret;
} else if (currNode.type === QueryElementTypes.Entity) {
const ret: NodeStruct = {
ID: currNode?.id,
id: currNode?.id,
label: currNode?.name,
// logic: LogicStruct[];
// subQuery?: QueryStruct;
......@@ -292,7 +292,7 @@ export function Query2BackendQuery(
query.query = graphSequenceChunks.map((chunk, i) => {
const ret: QueryStruct = {
ID: 'path_' + i, //TODO: chunk[0].id ||
id: 'path_' + i, //TODO: chunk[0].id ||
node: processConnection(chunk, 0),
};
return ret;
......
import { GraphQueryResultFromBackend } from '../data-access/store/graphQueryResultSlice';
import { GraphStatistics } from './statistics.types';
import { getAttributeType, getEdgeType, getNodeLabel, initializeStatistics, updateStatistics } from './utils';
import { getAttributeType, initializeStatistics, updateStatistics } from './utils';
const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics => {
const { nodes, edges } = graph;
......@@ -19,7 +19,7 @@ const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics
// attributes based statistics
nodes.forEach((node) => {
const nodeType = getNodeLabel(node);
const nodeType = node.label;
if (!metaData.nodes.labels.includes(nodeType)) {
metaData.nodes.labels.push(nodeType);
}
......@@ -41,7 +41,7 @@ const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics
// Process edges
edges.forEach((edge) => {
const edgeType = getEdgeType(edge);
const edgeType = edge.label;
if (!metaData.edges.labels.includes(edgeType)) {
metaData.edges.labels.push(edgeType);
}
......
import { describe, it, expect } from 'vitest';
import { getNodeLabel, getEdgeType } from '../utils/getNodeOrEdgeType';
import { GraphQueryResultFromBackend } from '../../data-access/store/graphQueryResultSlice';
describe('getNodeLabel', () => {
it('should return node type based on _id', () => {
const node: GraphQueryResultFromBackend['nodes'][number] = {
_id: 'Person/123',
attributes: {},
};
const label = getNodeLabel(node);
expect(label).toBe('Person');
});
it('should return node label if present', () => {
const node: GraphQueryResultFromBackend['nodes'][number] = {
_id: 'Person/123',
label: 'Student',
attributes: {},
};
const label = getNodeLabel(node);
expect(label).toBe('Student');
});
it('should return first label from attributes if labels array is present', () => {
const node: GraphQueryResultFromBackend['nodes'][number] = {
_id: 'Person/123',
attributes: { labels: ['Teacher', 'Mentor'] },
};
const label = getNodeLabel(node);
expect(label).toBe('Teacher');
});
it('should return _id-based node type if labels array is empty', () => {
const node: GraphQueryResultFromBackend['nodes'][number] = {
_id: 'Person/123',
attributes: { labels: [] },
};
const label = getNodeLabel(node);
expect(label).toBe('Person');
});
});
describe('getEdgeType', () => {
it('should return edge type based on _id', () => {
const edge: GraphQueryResultFromBackend['edges'][number] = {
_id: 'Relationship/456',
attributes: {},
from: 'Person/123',
to: 'Person/456',
};
const edgeType = getEdgeType(edge);
expect(edgeType).toBe('Relationship');
});
it('should return edge type from attributes if _id does not contain /', () => {
const edge: GraphQueryResultFromBackend['edges'][number] = {
_id: '456',
attributes: { Type: 'FRIENDS_WITH' },
from: 'Person/123',
to: 'Person/456',
};
const edgeType = getEdgeType(edge);
expect(edgeType).toBe('FRIENDS_WITH');
});
});
import { GraphQueryResultFromBackend } from '../../data-access/store/graphQueryResultSlice';
// Get node type based on _id or label
const getNodeLabel = (node: GraphQueryResultFromBackend['nodes'][number]): string => {
let nodeType = node._id.split('/')[0];
if (node.label) nodeType = node.label;
else if (Array.isArray(node.attributes?.labels) && node.attributes.labels.length > 0) {
nodeType = node.attributes.labels[0]; // Safely access first label
}
return nodeType;
};
// Get edge type based on _id or attributes
const getEdgeType = (edge: GraphQueryResultFromBackend['edges'][number]): string => {
let edgeType = edge._id.split('/')[0];
if (!edge._id.includes('/')) {
edgeType = edge.attributes.Type as string;
}
if (edge.label) edgeType = edge.label;
return edgeType;
};
export { getNodeLabel, getEdgeType };
export * from './getAttributeType';
export * from './getNodeOrEdgeType';
export * from './attributeStats';
export * from './updateStatistics';
......@@ -75,7 +75,7 @@ export default function VisualizationTabBar(props: { fullSize: () => void; expor
<Button
variantType="secondary"
variant="ghost"
disabled={!saveStateAuthorization.database.W}
disabled={!saveStateAuthorization.database?.W}
rounded
size="2xs"
iconComponent="icon-[ic--baseline-close]"
......@@ -93,7 +93,7 @@ export default function VisualizationTabBar(props: { fullSize: () => void; expor
<Tooltip>
<TooltipTrigger>
<DropdownContainer open={open} onOpenChange={setOpen}>
<DropdownTrigger disabled={!saveStateAuthorization.database.W} onClick={() => setOpen((v) => !v)}>
<DropdownTrigger disabled={!saveStateAuthorization.database?.W} onClick={() => setOpen((v) => !v)}>
<Button
as={'a'}
variantType="secondary"
......
......@@ -161,18 +161,8 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options:
const nodeId = queryResult.nodes[i]._id;
// for datasets without label, label is included in id. eg. "kamerleden/112"
//const entityType = queryResult.nodes[i].label;
let entityType: string;
const node = queryResult.nodes[i];
if (node.label === undefined) {
if (node.attributes.labels !== undefined && Array.isArray(node.attributes.labels)) {
entityType = node.attributes.labels[0];
} else {
entityType = node._id.split('/')[0];
}
} else {
entityType = node.label;
}
const entityType: string = node.label;
// The preferred text to be shown on top of the node
let preferredText = nodeId;
......@@ -264,7 +254,7 @@ export function parseQueryResult(queryResult: GraphQueryResult, ml: ML, options:
name: uniqueEdges[i].attributes.Type,
mlEdge: false,
color: 0x000000,
attributes: uniqueEdges[i].attributes
attributes: uniqueEdges[i].attributes,
};
ret.links[toAdd.id] = toAdd;
}
......
......@@ -152,7 +152,8 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte
firstRowData.type[dataColumn] === 'date' ||
firstRowData.type[dataColumn] === 'duration' ||
firstRowData.type[dataColumn] === 'datetime' ||
firstRowData.type[dataColumn] === 'time'
firstRowData.type[dataColumn] === 'time' ||
typeof data[0].attribute[dataColumn] === 'string'
) {
const groupedData = group(data, (d) => {
const value = d.attribute[dataColumn];
......@@ -236,6 +237,7 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte
return newData2Render;
});
const _data2RenderSorted = _data2Render.sort((a, b) => a.name.localeCompare(b.name));
setData2Render(_data2RenderSorted);
}, [currentPage, data, sortedData, selectedEntity, maxBarsCount, showAttributes]);
......@@ -307,10 +309,10 @@ export const Table = ({ data, itemsPerPage, showBarPlot, showAttributes, selecte
)
) : (
<div className="font-normal mx-auto flex flex-row items-start justify-center w-full gap-1 text-center text-secondary-700 p-1">
<div className="flex items-center space-x-1 whitespace-nowrap">
<span className="text-2xs">Unique values:</span>
<span className="text-xs font-medium">{data2Render[index]?.numElements}</span>
</div>
<div className="flex items-center space-x-1 whitespace-nowrap">
<span className="text-2xs">Unique values:</span>
<span className="text-xs font-medium">{data2Render[index]?.numElements}</span>
</div>
</div>
))}
</div>
......
......@@ -5,7 +5,7 @@ import { EntityPill, RelationPill } from '@graphpolaris/shared/lib/components/pi
import { useSearchResultData } from '@graphpolaris/shared/lib/data-access';
import { SettingsContainer } from '@graphpolaris/shared/lib/vis/components/config';
import html2canvas from 'html2canvas';
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
import { VISComponentType, VisualizationPropTypes, VisualizationSettingsPropTypes } from '../../common';
import { AugmentedNodeAttributes, Table } from './components/Table';
import styles from '../../../components/buttons/buttons.module.scss';
......@@ -58,7 +58,6 @@ export const TableVis = forwardRef<TableVisHandle, VisualizationPropTypes<TableP
} else {
dataNodes = data.edges;
}
return (
dataNodes
.filter((node) => {
......@@ -114,10 +113,10 @@ export const TableVis = forwardRef<TableVisHandle, VisualizationPropTypes<TableP
const clonedElement = ref.current.cloneNode(true) as HTMLDivElement;
clonedElement.classList.add(styles.exported);
document.body.appendChild(clonedElement);
document.body.appendChild(clonedElement);
html2canvas(clonedElement).then((canvas) => {
document.body.removeChild(clonedElement);
document.body.removeChild(clonedElement);
const pngData = canvas.toDataURL('image/png');
const a = document.createElement('a');
a.href = pngData;
......@@ -151,38 +150,41 @@ export const TableVis = forwardRef<TableVisHandle, VisualizationPropTypes<TableP
);
const TableSettings = ({ settings, graphMetadata, updateSettings }: VisualizationSettingsPropTypes<TableProps>) => {
const [attributes, setAttributes] = useState<string[]>([]);
//!TODO: displayAttributes settings are not preserved when loading state
useEffect(() => {
if (settings.selectedEntity === '' && graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0) {
const firstEntity = graphMetadata.nodes.labels[0];
const attributesFirstEntity = Object.keys(graphMetadata.nodes.types[firstEntity].attributes);
updateSettings({ selectedEntity: graphMetadata.nodes.labels[0], displayAttributes: attributesFirstEntity });
setAttributes(attributesFirstEntity);
if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0 && settings.selectedEntity === '') {
updateSettings({ selectedEntity: graphMetadata.nodes.labels[0] });
}
}, [graphMetadata]);
useEffect(() => {
if (
graphMetadata &&
graphMetadata.nodes &&
graphMetadata.nodes.labels.length > 0 &&
settings.selectedEntity != '' &&
settings.displayAttributes.length != 0
) {
const nodesLabels = graphMetadata.nodes.labels;
let attributesFirstEntity = [];
if (nodesLabels.includes(settings.selectedEntity)) {
attributesFirstEntity = Object.keys(graphMetadata.nodes.types[settings.selectedEntity].attributes);
} else {
attributesFirstEntity = Object.keys(graphMetadata.edges.types[settings.selectedEntity].attributes);
}
setAttributes(attributesFirstEntity);
const selectedNodeAttributes = useMemo(() => {
if (settings.selectedEntity) {
const labelNodes = graphMetadata.nodes.labels;
const labelRelationship = graphMetadata.edges.labels;
if (labelNodes.includes(settings.selectedEntity)) {
const nodeType = graphMetadata.nodes.types[settings.selectedEntity];
updateSettings({ displayAttributes: attributesFirstEntity });
if (nodeType && nodeType.attributes) {
return Object.keys(nodeType.attributes).sort((a, b) => a.localeCompare(b));
}
} else if (labelRelationship.includes(settings.selectedEntity)) {
const edgesType = graphMetadata.edges.types[settings.selectedEntity];
if (edgesType && edgesType.attributes) {
return Object.keys(edgesType.attributes).sort((a, b) => a.localeCompare(b));
}
}
}
return [];
}, [settings.selectedEntity, graphMetadata]);
useEffect(() => {
if (graphMetadata && graphMetadata.nodes && graphMetadata.nodes.labels.length > 0) {
updateSettings({ displayAttributes: selectedNodeAttributes });
}
}, [selectedNodeAttributes, graphMetadata]);
return (
<SettingsContainer>
<div className="my-2">
......@@ -248,7 +250,7 @@ const TableSettings = ({ settings, graphMetadata, updateSettings }: Visualizatio
<Input
type="checkbox"
value={settings.displayAttributes}
options={attributes}
options={selectedNodeAttributes}
onChange={(val: string[] | string) => {
const updatedVal = Array.isArray(val) ? val : [val];
updateSettings({ displayAttributes: updatedVal });
......