From 8ca5b11aecd4e6713afa4fe5906247f84f940aed Mon Sep 17 00:00:00 2001 From: Leonardo <leomilho@gmail.com> Date: Wed, 15 Jan 2025 18:06:32 +0100 Subject: [PATCH] feat: frontend inclusion also had to refactor so that neo4j, redis and rabbitMQ are not globally exported. Also used the opportunity to run prettier --- bun.lockb | Bin 64603 -> 64603 bytes eslint.config.js | 16 +-- index.ts | 2 +- {src/neo4jConnection => neo4j}/index.ts | 4 +- package.json | 8 +- {src/brokerReader => rabbitMq}/index.ts | 0 .../rabbitmqBroker.ts | 6 +- .../rabbitmqConnection.ts | 2 +- {src/redisConnection => redis}/index.ts | 2 +- src/index.ts | 3 - src/logger/logger.ts | 3 +- src/model/graphology.ts | 2 + src/model/index.ts | 3 + src/model/insight.ts | 20 ++- src/model/query/index.ts | 4 +- src/model/query/logic/boolFilters.ts | 37 ++++++ src/model/query/logic/general.ts | 5 + src/model/query/logic/index.ts | 2 + ...Aggregations.tsx => numberAggregations.ts} | 0 .../{numberFilters.tsx => numberFilters.ts} | 0 ...numberFunctions.tsx => numberFunctions.ts} | 0 .../{stringFilters.tsx => stringFilters.ts} | 0 ...stringFunctions.tsx => stringFunctions.ts} | 0 src/model/query/machineLearningModel.ts | 29 +++-- src/model/query/queryResultModel.ts | 2 +- src/model/query/statistics/graphStatistics.ts | 71 ++++++++++ src/model/query/statistics/index.ts | 2 + .../{ => statistics}/statisticsResultModel.ts | 61 ++++----- .../statistics/tests/attributeStats.spec.ts | 107 ++++++++++++++++ .../statistics/tests/getAttributeType.spec.ts | 65 ++++++++++ .../statistics/tests/graphStatistics.spec.ts | 121 ++++++++++++++++++ .../statistics/utils/attributeStats/array.ts | 6 + .../utils/attributeStats/boolean.ts | 9 ++ .../utils/attributeStats/categorical.ts | 15 +++ .../statistics/utils/attributeStats/index.ts | 7 + .../utils/attributeStats/initialize.ts | 45 +++++++ .../utils/attributeStats/numerical.ts | 9 ++ .../statistics/utils/attributeStats/object.ts | 5 + .../utils/attributeStats/temporal.ts | 10 ++ .../statistics/utils/getAttributeType.ts | 76 +++++++++++ src/model/query/statistics/utils/index.ts | 3 + .../statistics/utils/updateStatistics.ts | 45 +++++++ src/model/reactflow.ts | 17 +++ src/model/saveStateModel.ts | 3 +- src/model/schemaModel.ts | 6 +- src/model/utils.ts | 5 + src/model/webSocket/graphResult.ts | 10 +- src/model/webSocket/index.ts | 2 + src/model/webSocket/message2Backend.ts | 2 +- src/model/webSocket/message2Frontend.ts | 10 +- src/model/webSocket/model.ts | 1 - src/model/webSocket/policy.ts | 20 ++- 52 files changed, 795 insertions(+), 88 deletions(-) rename {src/neo4jConnection => neo4j}/index.ts (92%) rename {src/brokerReader => rabbitMq}/index.ts (100%) rename {src/brokerReader => rabbitMq}/rabbitmqBroker.ts (97%) rename {src/brokerReader => rabbitMq}/rabbitmqConnection.ts (97%) rename {src/redisConnection => redis}/index.ts (98%) create mode 100644 src/model/query/logic/boolFilters.ts rename src/model/query/logic/{numberAggregations.tsx => numberAggregations.ts} (100%) rename src/model/query/logic/{numberFilters.tsx => numberFilters.ts} (100%) rename src/model/query/logic/{numberFunctions.tsx => numberFunctions.ts} (100%) rename src/model/query/logic/{stringFilters.tsx => stringFilters.ts} (100%) rename src/model/query/logic/{stringFunctions.tsx => stringFunctions.ts} (100%) create mode 100644 src/model/query/statistics/graphStatistics.ts create mode 100644 src/model/query/statistics/index.ts rename src/model/query/{ => statistics}/statisticsResultModel.ts (56%) create mode 100644 src/model/query/statistics/tests/attributeStats.spec.ts create mode 100644 src/model/query/statistics/tests/getAttributeType.spec.ts create mode 100644 src/model/query/statistics/tests/graphStatistics.spec.ts create mode 100644 src/model/query/statistics/utils/attributeStats/array.ts create mode 100644 src/model/query/statistics/utils/attributeStats/boolean.ts create mode 100644 src/model/query/statistics/utils/attributeStats/categorical.ts create mode 100644 src/model/query/statistics/utils/attributeStats/index.ts create mode 100644 src/model/query/statistics/utils/attributeStats/initialize.ts create mode 100644 src/model/query/statistics/utils/attributeStats/numerical.ts create mode 100644 src/model/query/statistics/utils/attributeStats/object.ts create mode 100644 src/model/query/statistics/utils/attributeStats/temporal.ts create mode 100644 src/model/query/statistics/utils/getAttributeType.ts create mode 100644 src/model/query/statistics/utils/index.ts create mode 100644 src/model/query/statistics/utils/updateStatistics.ts create mode 100644 src/model/utils.ts diff --git a/bun.lockb b/bun.lockb index ff60b54747a65e9deaa18ce00b1089ed3bf2c76a..b1ce2cd440ec2c66722e45c4062ef2c1c528fcb9 100755 GIT binary patch delta 11422 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 P`?PO8_k~2KzFGg@5lrF6 delta 11554 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 diff --git a/eslint.config.js b/eslint.config.js index 3c5557f..7a97a5d 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,20 +1,20 @@ -import js from "@eslint/js"; -import globals from "globals"; -import tseslint from "typescript-eslint"; +import js from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; export default tseslint.config( - { ignores: ["dist"] }, + { ignores: ['dist'] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ["**/*.{ts,tsx}"], + files: ['**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: {}, rules: { - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', }, - } + }, ); diff --git a/index.ts b/index.ts index e6918d5..8420b10 100644 --- a/index.ts +++ b/index.ts @@ -1 +1 @@ -export * from './src' \ No newline at end of file +export * from './src'; diff --git a/src/neo4jConnection/index.ts b/neo4j/index.ts similarity index 92% rename from src/neo4jConnection/index.ts rename to neo4j/index.ts index 008c4c9..feca82f 100644 --- a/src/neo4jConnection/index.ts +++ b/neo4j/index.ts @@ -1,6 +1,6 @@ import neo4j, { Driver, type QueryResult, type RecordShape } from 'neo4j-driver'; -import type { DbConnection } from '../model/saveStateModel'; -import { formatTimeDifference, log } from '../logger/logger'; +import type { DbConnection } from '../src/model/saveStateModel'; +import { formatTimeDifference, log } from '../src/logger/logger'; export class Neo4jConnection { private driver: Driver; diff --git a/package.json b/package.json index 13083f9..81d8d74 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,13 @@ "main": "./index.ts", "description": "", "type": "module", - "files": [], + "files": [ + "index.ts", + "src" + ], "scripts": { - "lint": "eslint src/**/* --no-error-on-unmatched-pattern" + "lint": "eslint src/**/* --no-error-on-unmatched-pattern", + "type": "tsc --noEmit --skipLibCheck" }, "devDependencies": { "@types/amqplib": "^0.10.5", diff --git a/src/brokerReader/index.ts b/rabbitMq/index.ts similarity index 100% rename from src/brokerReader/index.ts rename to rabbitMq/index.ts diff --git a/src/brokerReader/rabbitmqBroker.ts b/rabbitMq/rabbitmqBroker.ts similarity index 97% rename from src/brokerReader/rabbitmqBroker.ts rename to rabbitMq/rabbitmqBroker.ts index 4b57d9c..dd9dcc2 100644 --- a/src/brokerReader/rabbitmqBroker.ts +++ b/rabbitMq/rabbitmqBroker.ts @@ -1,7 +1,7 @@ import { type Channel, type Connection, type ConsumeMessage, type Options } from 'amqplib'; -import type { BackendMessageHeader, SessionData } from '../model/headerModel'; -import { formatTimeDifference, log } from '../logger/logger'; -import type { WsMessageBackend2Frontend, WsMessageBody } from '../model/webSocket'; +import type { BackendMessageHeader, SessionData } from '../src/model/headerModel'; +import { formatTimeDifference, log } from '../src/logger/logger'; +import type { WsMessageBackend2Frontend, WsMessageBody } from '../src/model/webSocket'; import type { RabbitMqConnection } from './rabbitmqConnection'; export class RabbitMqBroker { diff --git a/src/brokerReader/rabbitmqConnection.ts b/rabbitMq/rabbitmqConnection.ts similarity index 97% rename from src/brokerReader/rabbitmqConnection.ts rename to rabbitMq/rabbitmqConnection.ts index f6b616f..cbcb839 100644 --- a/src/brokerReader/rabbitmqConnection.ts +++ b/rabbitMq/rabbitmqConnection.ts @@ -1,5 +1,5 @@ import { connect, type Connection, type Options } from 'amqplib'; -import { log } from '../logger/logger'; +import { log } from '../src/logger/logger'; export class RabbitMqConnection { private connection?: Connection; diff --git a/src/redisConnection/index.ts b/redis/index.ts similarity index 98% rename from src/redisConnection/index.ts rename to redis/index.ts index ac3dede..83ccbe4 100644 --- a/src/redisConnection/index.ts +++ b/redis/index.ts @@ -1,6 +1,6 @@ import { createClient, type RedisClientType } from 'redis'; import { lock, tryLock, type ReleaseFunc, type TryLockOptions } from 'simple-redis-mutex'; -import { log } from '../logger/logger'; +import { log } from '../src/logger/logger'; export type Routing = { queueID: string; diff --git a/src/index.ts b/src/index.ts index 61ea6d8..767a4e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,3 @@ -export * from './neo4jConnection'; -export * from './redisConnection'; -export * from './brokerReader'; export * from './model'; export * from './userManagementServiceAPI'; export * from './logger'; diff --git a/src/logger/logger.ts b/src/logger/logger.ts index c9d9a0f..f64b1e8 100644 --- a/src/logger/logger.ts +++ b/src/logger/logger.ts @@ -1,6 +1,7 @@ import { Logger } from '.'; -export const log = new Logger(parseInt(process.env.LOG_LEVEL || '1') as any, 'ts-common'); +const logLevel = typeof process !== 'undefined' && process.env.LOG_LEVEL ? process.env.LOG_LEVEL : '1'; +export const log = new Logger(parseInt(logLevel) as any, 'ts-common'); export function formatTimeDifference(milliseconds: number): string { const seconds = Math.floor((milliseconds / 1000) % 60); diff --git a/src/model/graphology.ts b/src/model/graphology.ts index 62f419f..afeee7f 100644 --- a/src/model/graphology.ts +++ b/src/model/graphology.ts @@ -78,3 +78,5 @@ export type QueryGraphEdges = { }; export type QueryMultiGraph = SerializedGraph<QueryGraphNodes, QueryGraphEdges, GAttributes>; + +export type QueryGraphEdgesOpt = Partial<QueryGraphEdges>; diff --git a/src/model/index.ts b/src/model/index.ts index da0824c..2e1338a 100644 --- a/src/model/index.ts +++ b/src/model/index.ts @@ -4,3 +4,6 @@ export * from './query'; export * from './saveStateModel'; export * from './insight'; export * from './webSocket'; +export * from './graphology'; +export * from './reactflow'; +export * from './utils'; diff --git a/src/model/insight.ts b/src/model/insight.ts index 0d70a38..70bf652 100644 --- a/src/model/insight.ts +++ b/src/model/insight.ts @@ -9,7 +9,7 @@ export type InsightRequest = { saveStateId: string; userId?: number; status?: boolean; - type: 'report' | 'alert'; + type: InsightType; alarmMode: 'disabled' | 'diff' | 'conditional' | 'always'; }; @@ -27,3 +27,21 @@ export type InsightModel = { value: number; }[]; } & InsightRequest; + +export const InsightConditionMap: Record<string, InsightCondition['operator']> = { + 'Greater than': '>', + 'Equal than': '==', + 'Smaller than': '<', + 'Not equal': '!=', + 'Greater or equal': '>=', + 'Smaller or equal': '<=', +}; +export const InsightConditionMapReverse = Object.fromEntries(Object.entries(InsightConditionMap).map(([key, value]) => [value, key])); + +export type InsightCondition = { + nodeLabel: string; + property: string; + statistic: string; + operator: '>' | '==' | '<' | '!=' | '>=' | '<='; + value: number; +}; diff --git a/src/model/query/index.ts b/src/model/query/index.ts index 7188f79..e89aad1 100644 --- a/src/model/query/index.ts +++ b/src/model/query/index.ts @@ -1,5 +1,7 @@ export * from './machineLearningModel'; export * from './queryRequestModel'; +export * from './queryBuilderModel'; export * from './queryResultModel'; export * from './mlModel'; -export * from './statisticsResultModel'; +export * from './statistics'; +export * from './logic'; diff --git a/src/model/query/logic/boolFilters.ts b/src/model/query/logic/boolFilters.ts new file mode 100644 index 0000000..9ccb849 --- /dev/null +++ b/src/model/query/logic/boolFilters.ts @@ -0,0 +1,37 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +import { BoolFilterTypes, type GeneralDescription } from './general'; + +export const BoolFilters: Record<BoolFilterTypes, GeneralDescription<BoolFilterTypes>> = { + [BoolFilterTypes.EQUAL]: { + key: 'boolFilterEqual', + name: 'Equal', + type: BoolFilterTypes.EQUAL, + description: 'Equal to another value', + input: { name: 'Value', type: 'bool', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'bool', default: 0 }], + output: { name: '==', type: 'bool' }, + logic: ['==', '@i', '@1'], + icon: 'icon-[ic--baseline-equals]', + }, + [BoolFilterTypes.NOT_EQUAL]: { + key: 'boolFilterNotEqual', + name: 'Not Equal', + type: BoolFilterTypes.NOT_EQUAL, + description: 'Not equal to another value', + input: { name: 'Value', type: 'bool', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'bool', default: 0 }], + output: { name: '!=', type: 'bool' }, + logic: ['!=', '@i', '@1'], + icon: 'icon-[ic--baseline-not-equal]', + }, +}; + +/** All available functions in the function bar. */ +export const BoolFilterArray: Array<GeneralDescription<BoolFilterTypes>> = Object.values(BoolFilters); diff --git a/src/model/query/logic/general.ts b/src/model/query/logic/general.ts index 063bb32..e6b25a3 100644 --- a/src/model/query/logic/general.ts +++ b/src/model/query/logic/general.ts @@ -2,6 +2,11 @@ export type InputNodeTypeTypes = string | number | boolean; export type InputNodeType = 'string' | 'float' | 'int' | 'bool' | 'date' | 'time' | 'datetime' | 'duration'; export type InputNodeDimension = 'categorical' | 'numerical' | 'temporal' | 'spatial'; +export enum BoolFilterTypes { + EQUAL = '==', + NOT_EQUAL = '!=', +} + export enum NumberFilterTypes { EQUAL = '==', NOT_EQUAL = '!=', diff --git a/src/model/query/logic/index.ts b/src/model/query/logic/index.ts index 18429b6..c4b0ea8 100644 --- a/src/model/query/logic/index.ts +++ b/src/model/query/logic/index.ts @@ -27,3 +27,5 @@ export * from './numberFunctions'; export * from './numberFilters'; export * from './stringFunctions'; export * from './stringFilters'; +export * from './boolFilters'; +export * from './general'; diff --git a/src/model/query/logic/numberAggregations.tsx b/src/model/query/logic/numberAggregations.ts similarity index 100% rename from src/model/query/logic/numberAggregations.tsx rename to src/model/query/logic/numberAggregations.ts diff --git a/src/model/query/logic/numberFilters.tsx b/src/model/query/logic/numberFilters.ts similarity index 100% rename from src/model/query/logic/numberFilters.tsx rename to src/model/query/logic/numberFilters.ts diff --git a/src/model/query/logic/numberFunctions.tsx b/src/model/query/logic/numberFunctions.ts similarity index 100% rename from src/model/query/logic/numberFunctions.tsx rename to src/model/query/logic/numberFunctions.ts diff --git a/src/model/query/logic/stringFilters.tsx b/src/model/query/logic/stringFilters.ts similarity index 100% rename from src/model/query/logic/stringFilters.tsx rename to src/model/query/logic/stringFilters.ts diff --git a/src/model/query/logic/stringFunctions.tsx b/src/model/query/logic/stringFunctions.ts similarity index 100% rename from src/model/query/logic/stringFunctions.tsx rename to src/model/query/logic/stringFunctions.ts diff --git a/src/model/query/machineLearningModel.ts b/src/model/query/machineLearningModel.ts index ee64ba3..fbf9bc7 100644 --- a/src/model/query/machineLearningModel.ts +++ b/src/model/query/machineLearningModel.ts @@ -1,12 +1,19 @@ -export type MLTypes = 'centrality' | 'linkPrediction' | 'communityDetection' | 'shortestPath'; -export const allMLTypes: MLTypes[] = ['centrality', 'linkPrediction', 'communityDetection', 'shortestPath']; export enum MLTypesEnum { - CENTRALITY = 'centrality', - LINK_PREDICTION = 'linkPrediction', - COMMUNITY_DETECTION = 'communityDetection', - SHORTEST_PATH = 'shortestPath', + centrality = 'centrality', + linkPrediction = 'linkPrediction', + communityDetection = 'communityDetection', + shortestPath = 'shortestPath', } +export const allMLTypes = [ + MLTypesEnum.centrality, + MLTypesEnum.linkPrediction, + MLTypesEnum.communityDetection, + MLTypesEnum.shortestPath, +] as const; + +export type MLTypes = keyof typeof MLTypesEnum; + export type LinkPredictionInstance = { attributes: { jaccard_coefficient: number }; from: string; @@ -16,7 +23,7 @@ export type LinkPredictionInstance = { export type CommunityDetectionInstance = string[]; // set of ids -export type MLInstance<T> = { +export type MLInstance<T = object> = { enabled: boolean; result: T; }; @@ -33,10 +40,10 @@ export type ShortestPath = { }; export type ML = { - [MLTypesEnum.LINK_PREDICTION]: MLInstance<LinkPredictionInstance[]>; - [MLTypesEnum.CENTRALITY]: MLInstance<Record<string, number>>; - [MLTypesEnum.COMMUNITY_DETECTION]: CommunityDetection; - [MLTypesEnum.SHORTEST_PATH]: ShortestPath; + [MLTypesEnum.linkPrediction]: MLInstance<LinkPredictionInstance[]>; + [MLTypesEnum.centrality]: MLInstance<Record<string, number>>; + [MLTypesEnum.communityDetection]: CommunityDetection; + [MLTypesEnum.shortestPath]: ShortestPath; }; export type MLInstanceTypes = ML[keyof ML]; diff --git a/src/model/query/queryResultModel.ts b/src/model/query/queryResultModel.ts index c1d9930..cff277f 100644 --- a/src/model/query/queryResultModel.ts +++ b/src/model/query/queryResultModel.ts @@ -1,4 +1,4 @@ -import type { GraphStatistics } from './statisticsResultModel'; +import type { GraphStatistics } from './statistics/statisticsResultModel'; export type NodeAttributes = { [key: string]: unknown }; diff --git a/src/model/query/statistics/graphStatistics.ts b/src/model/query/statistics/graphStatistics.ts new file mode 100644 index 0000000..05f9852 --- /dev/null +++ b/src/model/query/statistics/graphStatistics.ts @@ -0,0 +1,71 @@ +import type { GraphQueryResultFromBackend } from '../../webSocket'; +import type { GraphStatistics } from './statisticsResultModel'; +import { getAttributeType, initializeStatistics, updateStatistics } from './utils'; + +const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics => { + const { nodes, edges } = graph; + + const n_nodes = nodes.length; + const n_edges = edges.length; + + const density = n_nodes < 2 ? 0 : (n_edges * 2) / (n_nodes * (n_nodes - 1)); + + // general nodes and edges statistics + const metaData: GraphStatistics = { + topological: { density, self_loops: 0 }, + nodes: { labels: [], count: n_nodes, types: {} }, + edges: { labels: [], count: n_edges, types: {} }, + }; + + // attributes based statistics + nodes.forEach(node => { + const nodeType = node.label; + if (!metaData.nodes.labels.includes(nodeType)) { + metaData.nodes.labels.push(nodeType); + } + + if (!metaData.nodes.types[nodeType]) { + metaData.nodes.types[nodeType] = { count: 0, attributes: {} }; + } + metaData.nodes.types[nodeType].count++; + + Object.entries(node.attributes).forEach(([attributeId, attributeValue]) => { + const attributeType = getAttributeType(attributeValue); + + if (!metaData.nodes.types[nodeType].attributes[attributeId]) { + metaData.nodes.types[nodeType].attributes[attributeId] = { attributeType, statistics: initializeStatistics(attributeType) }; + } + updateStatistics(metaData.nodes.types[nodeType].attributes[attributeId], attributeValue); + }); + }); + + // Process edges + edges.forEach(edge => { + const edgeType = edge.label; + if (!metaData.edges.labels.includes(edgeType)) { + metaData.edges.labels.push(edgeType); + } + + if (!metaData.edges.types[edgeType]) { + metaData.edges.types[edgeType] = { count: 0, attributes: {} }; + } + + metaData.edges.types[edgeType].count++; + + if (edge.from === edge.to) metaData.topological.self_loops++; + + Object.entries(edge.attributes).forEach(([attributeId, attributeValue]) => { + const attributeType = getAttributeType(attributeValue); + + if (!metaData.edges.types[edgeType].attributes[attributeId]) { + metaData.edges.types[edgeType].attributes[attributeId] = { attributeType, statistics: initializeStatistics(attributeType) }; + } + + updateStatistics(metaData.edges.types[edgeType].attributes[attributeId], attributeValue); + }); + }); + + return metaData; +}; + +export { getGraphStatistics }; diff --git a/src/model/query/statistics/index.ts b/src/model/query/statistics/index.ts new file mode 100644 index 0000000..aa1cac6 --- /dev/null +++ b/src/model/query/statistics/index.ts @@ -0,0 +1,2 @@ +export * from './graphStatistics'; +export * from './statisticsResultModel'; diff --git a/src/model/query/statisticsResultModel.ts b/src/model/query/statistics/statisticsResultModel.ts similarity index 56% rename from src/model/query/statisticsResultModel.ts rename to src/model/query/statistics/statisticsResultModel.ts index cdc6e18..f429445 100644 --- a/src/model/query/statisticsResultModel.ts +++ b/src/model/query/statistics/statisticsResultModel.ts @@ -1,10 +1,10 @@ -type GraphStatistics = { +export type GraphStatistics = { topological: TopologicalStats; - nodes: NodeOrEdgeStats; - edges: NodeOrEdgeStats; + nodes: QueryNodeOrEdgeStats; + edges: QueryNodeOrEdgeStats; }; -type NodeOrEdgeStats = { +export type QueryNodeOrEdgeStats = { count: number; labels: string[]; types: { @@ -19,28 +19,28 @@ type NodeOrEdgeStats = { }; }; -type AttributeStats<T extends AttributeType> = { +export type AttributeStats<T extends AttributeType> = { attributeType: T; statistics: AttributeTypeStats<T>; }; -type AttributeTypeStats<T extends AttributeType> = T extends 'string' +export type AttributeTypeStats<T extends AttributeType> = T extends 'string' ? CategoricalStats : T extends 'boolean' - ? BooleanStats - : T extends 'number' - ? NumericalStats - : T extends 'date' | 'time' | 'datetime' | 'timestamp' - ? TemporalStats - : T extends 'array' - ? ArrayStats - : T extends 'object' - ? ObjectStats - : never; + ? BooleanStats + : T extends 'number' + ? NumericalStats + : T extends 'date' | 'time' | 'datetime' | 'timestamp' + ? TemporalStats + : T extends 'array' + ? ArrayStats + : T extends 'object' + ? ObjectStats + : never; -type AttributeType = 'string' | 'boolean' | 'number' | 'array' | 'object' | TemporalType; +export type AttributeType = 'string' | 'boolean' | 'number' | 'array' | 'object' | TemporalType; -type TemporalType = 'date' | 'time' | 'datetime' | 'timestamp'; +export type TemporalType = 'date' | 'time' | 'datetime' | 'timestamp'; // Date: Date in the YYYY-MM-DD format (ISO 8601 syntax) (e.g., 2021-09-28) // Time: Time in the hh:mm:ss format for the time of day, time since an event, or time interval between events (e.g., 12:00:59) // Datetime: Date and time together in the YYYY-MM-DD hh:mm:ss format (e.g., 2021-09-28 12:00:59) @@ -51,49 +51,36 @@ type TopologicalStats = { self_loops: number; }; -type NumericalStats = { +export type NumericalStats = { min: number; max: number; average: number; count: number; }; -type BooleanStats = { +export type BooleanStats = { true: number; false: number; }; -type CategoricalStats = { +export type CategoricalStats = { uniqueItems: number; values: string[]; mode: string; count: number; }; -type TemporalStats = { +export type TemporalStats = { min: number; max: number; range: number; }; -type ArrayStats = { +export type ArrayStats = { length: number; count: number; }; -type ObjectStats = { +export type ObjectStats = { length: number; }; - -export type { - GraphStatistics, - AttributeStats, - NumericalStats, - CategoricalStats, - BooleanStats, - TemporalStats, - AttributeType, - AttributeTypeStats, - ArrayStats, - ObjectStats, -}; diff --git a/src/model/query/statistics/tests/attributeStats.spec.ts b/src/model/query/statistics/tests/attributeStats.spec.ts new file mode 100644 index 0000000..bd7175b --- /dev/null +++ b/src/model/query/statistics/tests/attributeStats.spec.ts @@ -0,0 +1,107 @@ +import { describe, it, expect } from 'bun:test'; +import { + updateArrayStats, + updateBooleanStats, + updateCategoricalStats, + updateNumericalStats, + updateTemporalStats, + updateObjectStats, + initializeStatistics, +} from '../utils/attributeStats'; +import type { ArrayStats, BooleanStats, CategoricalStats, NumericalStats, TemporalStats, ObjectStats } from '../statisticsResultModel'; + +describe('updateArrayStats', () => { + it('should update the length of the array', () => { + const stats: ArrayStats = { length: 0, count: 0 }; + const value = [1, 2, 3]; + updateArrayStats(stats, value); + expect(stats.length).toBe(3); + expect(stats.count).toBe(1); + }); +}); + +describe('updateBooleanStats', () => { + it('should update true count when value is true', () => { + const stats: BooleanStats = { true: 0, false: 0 }; + updateBooleanStats(stats, true); + expect(stats.true).toBe(1); + expect(stats.false).toBe(0); + }); + + it('should update false count when value is false', () => { + const stats: BooleanStats = { true: 0, false: 0 }; + updateBooleanStats(stats, false); + expect(stats.false).toBe(1); + expect(stats.true).toBe(0); + }); +}); + +describe('updateCategoricalStats', () => { + it('should update mode and unique items count', () => { + const stats: CategoricalStats = { uniqueItems: 0, values: [], mode: '', count: 0 }; + updateCategoricalStats(stats, 'apple'); + updateCategoricalStats(stats, 'banana'); + updateCategoricalStats(stats, 'apple'); + + expect(stats.values).toEqual(['apple', 'banana', 'apple']); + expect(stats.uniqueItems).toBe(2); + expect(stats.mode).toBe('apple'); + expect(stats.count).toBe(3); + }); +}); + +describe('updateNumericalStats', () => { + it('should update min, max, average, and count', () => { + const stats: NumericalStats = { min: Infinity, max: -Infinity, average: 0, count: 0 }; + updateNumericalStats(stats, 10); + updateNumericalStats(stats, 20); + updateNumericalStats(stats, 5); + + expect(stats.min).toBe(5); + expect(stats.max).toBe(20); + expect(stats.average).toBeCloseTo(11.67, 2); + expect(stats.count).toBe(3); + }); +}); + +describe('updateTemporalStats', () => { + it('should update min, max, and range for temporal values', () => { + const stats: TemporalStats = { min: Infinity, max: -Infinity, range: 0 }; + updateTemporalStats(stats, '2022-01-01'); + updateTemporalStats(stats, '2022-01-05'); + + expect(stats.min).toBe(new Date('2022-01-01').getTime()); + expect(stats.max).toBe(new Date('2022-01-05').getTime()); + expect(stats.range).toBe(new Date('2022-01-05').getTime() - new Date('2022-01-01').getTime()); + }); +}); + +describe('updateObjectStats', () => { + it('should update the length of the object keys', () => { + const stats: ObjectStats = { length: 0 }; + const value = { key1: 'value1', key2: 'value2' }; + updateObjectStats(stats, value); + expect(stats.length).toBe(2); + }); +}); + +describe('initializeStatistics', () => { + it('should initialize statistics for string type', () => { + const stats = initializeStatistics('string'); + expect(stats).toEqual({ uniqueItems: 0, values: [], mode: '', count: 0 }); + }); + + it('should initialize statistics for boolean type', () => { + const stats = initializeStatistics('boolean'); + expect(stats).toEqual({ true: 0, false: 0 }); + }); + + it('should initialize statistics for number type', () => { + const stats = initializeStatistics('number'); + expect(stats).toEqual({ min: Infinity, max: -Infinity, average: 0, count: 0 }); + }); + + it('should throw an error for an unknown type', () => { + expect(() => initializeStatistics('unknown' as any)).toThrow('Unknown attribute type: unknown'); + }); +}); diff --git a/src/model/query/statistics/tests/getAttributeType.spec.ts b/src/model/query/statistics/tests/getAttributeType.spec.ts new file mode 100644 index 0000000..6b8923c --- /dev/null +++ b/src/model/query/statistics/tests/getAttributeType.spec.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'bun:test'; +import { getAttributeType } from '../utils/getAttributeType'; + +// Sample values for testing +const invalidDate = '2023-13-03'; +const validTime = '12:30:45'; +const invalidTime = '25:61:61'; +const invalidDatetime = '2023-10-03 25:61:61'; +const validNumber = '123.45'; +const invalidNumber = 'abc123'; +const booleanTrue = true; +const booleanFalse = false; +const numberValue = 123; +const arrayValue = [1, 2, 3]; +const objectValue = { key: 'value' }; +const dateInstance = new Date('2023-10-03T12:30:45'); + +// Unit tests for getAttributeType function +describe('getAttributeType', () => { + it('should correctly identify numbers as type "number"', () => { + expect(getAttributeType(validNumber)).toBe('number'); + expect(getAttributeType(numberValue)).toBe('number'); + }); + + it('should correctly identify strings as valid "time"', () => { + expect(getAttributeType(validTime)).toBe('time'); + }); + + it('should identify invalid datetime strings as "string"', () => { + expect(getAttributeType(invalidDatetime)).toBe('string'); + }); + + it('should identify invalid date strings as "string"', () => { + expect(getAttributeType(invalidDate)).toBe('string'); + }); + + it('should identify invalid time strings as "string"', () => { + expect(getAttributeType(invalidTime)).toBe('string'); + }); + + it('should correctly identify boolean values as type "boolean"', () => { + expect(getAttributeType(booleanTrue)).toBe('boolean'); + expect(getAttributeType(booleanFalse)).toBe('boolean'); + }); + + it('should correctly identify arrays as type "array"', () => { + expect(getAttributeType(arrayValue)).toBe('array'); + }); + + it('should correctly identify objects as type "object"', () => { + expect(getAttributeType(objectValue)).toBe('object'); + }); + + it('should correctly identify Date instances as type "datetime"', () => { + expect(getAttributeType(dateInstance)).toBe('datetime'); + }); + + it('should identify string representations of invalid numbers as "string"', () => { + expect(getAttributeType(invalidNumber)).toBe('string'); + }); + + it('should identify a regular string as type "string"', () => { + expect(getAttributeType('random string')).toBe('string'); + }); +}); diff --git a/src/model/query/statistics/tests/graphStatistics.spec.ts b/src/model/query/statistics/tests/graphStatistics.spec.ts new file mode 100644 index 0000000..70ea8da --- /dev/null +++ b/src/model/query/statistics/tests/graphStatistics.spec.ts @@ -0,0 +1,121 @@ +import { describe, it, expect } from 'bun:test'; +import { getGraphStatistics } from '../graphStatistics'; +import type { GraphQueryResultFromBackend } from '../../../webSocket'; + +describe('getGraphStatistics', () => { + it('should return correct statistics for a graph with no nodes and edges', () => { + const graph: GraphQueryResultFromBackend = { + nodes: [], + edges: [], + }; + + const stats = getGraphStatistics(graph); + + expect(stats).toEqual({ + topological: { density: 0, self_loops: 0 }, + nodes: { labels: [], count: 0, types: {} }, + edges: { labels: [], count: 0, types: {} }, + }); + }); + + it('should return correct statistics for a graph with nodes and no edges', () => { + const graph: GraphQueryResultFromBackend = { + nodes: [ + { _id: '1', label: 'Person', attributes: { age: 25 } }, + { _id: '2', label: 'Person', attributes: { age: 30, city: 'New York' } }, + ], + edges: [], + }; + + const stats = getGraphStatistics(graph); + + expect(stats).toEqual({ + topological: { density: 0, self_loops: 0 }, + nodes: { + labels: ['Person'], // Assuming default label + count: 2, + types: { + Person: { + count: 2, + attributes: { + age: { attributeType: 'number', statistics: expect.any(Object) }, + city: { attributeType: 'string', statistics: expect.any(Object) }, + }, + }, + }, + }, + edges: { labels: [], count: 0, types: {} }, + }); + }); + + it('should return correct statistics for a graph with edges and nodes', () => { + const graph: GraphQueryResultFromBackend = { + nodes: [ + { _id: '1', label: 'Person', attributes: { age: 25 } }, + { _id: '2', label: 'Person', attributes: { age: 30 } }, + ], + edges: [ + { _id: 'e1', label: 'Relationship', attributes: { weight: 5 }, from: '1', to: '2' }, + { _id: 'e2', label: 'Relationship', attributes: { weight: 10 }, from: '2', to: '2' }, // self-loop + ], + }; + + const stats = getGraphStatistics(graph); + + expect(stats).toEqual({ + topological: { density: 2, self_loops: 1 }, + nodes: { + labels: ['Person'], // Assuming default label + count: 2, + types: { + Person: { + count: 2, + attributes: { + age: { attributeType: 'number', statistics: expect.any(Object) }, + }, + }, + }, + }, + edges: { + labels: ['Relationship'], // Assuming default edge type + count: 2, + types: { + Relationship: { + count: 2, + attributes: { + weight: { attributeType: 'number', statistics: expect.any(Object) }, + }, + }, + }, + }, + }); + }); + + it('should correctly count self-loops', () => { + const graph: GraphQueryResultFromBackend = { + nodes: [{ _id: '1', attributes: {}, label: 'Person' }], + edges: [ + { _id: 'e1', attributes: {}, from: '1', to: '1', label: 'Person' }, // self-loop + ], + }; + + const stats = getGraphStatistics(graph); + + expect(stats.topological.self_loops).toBe(1); + }); + + it('should correctly compute density for a graph with nodes and edges', () => { + const graph: GraphQueryResultFromBackend = { + nodes: [ + { _id: '1', attributes: {}, label: 'Person' }, + { _id: '2', attributes: {}, label: 'Person' }, + ], + edges: [{ _id: 'e1', attributes: {}, from: '1', to: '2', label: 'Person' }], + }; + + const stats = getGraphStatistics(graph); + + // Density = (n_edges * 2) / (n_nodes * (n_nodes - 1)) = (1 * 2) / (2 * 1) = 1 + expect(stats.topological.density).toBe(1); + }); +}); diff --git a/src/model/query/statistics/utils/attributeStats/array.ts b/src/model/query/statistics/utils/attributeStats/array.ts new file mode 100644 index 0000000..915e56d --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/array.ts @@ -0,0 +1,6 @@ +import type { ArrayStats } from '../../statisticsResultModel'; + +export const updateArrayStats = (stats: ArrayStats, value: any[]) => { + stats.length = value.length; + stats.count++; +}; diff --git a/src/model/query/statistics/utils/attributeStats/boolean.ts b/src/model/query/statistics/utils/attributeStats/boolean.ts new file mode 100644 index 0000000..952aea8 --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/boolean.ts @@ -0,0 +1,9 @@ +import type { BooleanStats } from '../../statisticsResultModel'; + +export const updateBooleanStats = (stats: BooleanStats, value: boolean) => { + if (value) { + stats.true += 1; + } else { + stats.false += 1; + } +}; diff --git a/src/model/query/statistics/utils/attributeStats/categorical.ts b/src/model/query/statistics/utils/attributeStats/categorical.ts new file mode 100644 index 0000000..1932817 --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/categorical.ts @@ -0,0 +1,15 @@ +import { type CategoricalStats } from '../../statisticsResultModel'; + +export const updateCategoricalStats = (stats: CategoricalStats, value: string | boolean) => { + if (!stats.values) stats.values = []; + stats.values.push(value.toString()); + + stats.uniqueItems = new Set(stats.values).size; + + const frequencyMap: { [key: string]: number } = {}; + stats.values.forEach(val => { + frequencyMap[val] = (frequencyMap[val] || 0) + 1; + }); + stats.mode = Object.keys(frequencyMap).reduce((a, b) => (frequencyMap[a] > frequencyMap[b] ? a : b)); + stats.count++; +}; diff --git a/src/model/query/statistics/utils/attributeStats/index.ts b/src/model/query/statistics/utils/attributeStats/index.ts new file mode 100644 index 0000000..42c4501 --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/index.ts @@ -0,0 +1,7 @@ +export * from './array'; +export * from './categorical'; +export * from './numerical'; +export * from './object'; +export * from './temporal'; +export * from './boolean'; +export * from './initialize'; diff --git a/src/model/query/statistics/utils/attributeStats/initialize.ts b/src/model/query/statistics/utils/attributeStats/initialize.ts new file mode 100644 index 0000000..fcc6238 --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/initialize.ts @@ -0,0 +1,45 @@ +import type { AttributeType, AttributeTypeStats } from '../../statisticsResultModel'; + +export const initializeStatistics = <T extends AttributeType>(type: T): AttributeTypeStats<T> => { + switch (type) { + case 'string': + return { + uniqueItems: 0, + values: [], + mode: '', + count: 0, + } as unknown as AttributeTypeStats<T>; + case 'boolean': + return { + true: 0, + false: 0, + } as unknown as AttributeTypeStats<T>; + case 'number': + return { + min: Infinity, + max: -Infinity, + average: 0, + count: 0, + } as unknown as AttributeTypeStats<T>; + case 'date': + case 'time': + case 'datetime': + case 'timestamp': + return { + min: Infinity, + max: -Infinity, + range: 0, + } as unknown as AttributeTypeStats<T>; + case 'array': + return { + length: 0, + count: 0, + } as unknown as AttributeTypeStats<T>; + case 'object': + return { + length: 0, + } as unknown as AttributeTypeStats<T>; + default: + throw new Error(`Unknown attribute type: ${type}`); + } +}; diff --git a/src/model/query/statistics/utils/attributeStats/numerical.ts b/src/model/query/statistics/utils/attributeStats/numerical.ts new file mode 100644 index 0000000..d845d66 --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/numerical.ts @@ -0,0 +1,9 @@ +import type { NumericalStats } from '../../statisticsResultModel'; + +export const updateNumericalStats = (stats: NumericalStats, value: number) => { + if (stats.min === undefined || value < stats.min) stats.min = value; + if (stats.max === undefined || value > stats.max) stats.max = value; + + stats.count++; + stats.average = (stats.average * (stats.count - 1) + value) / stats.count; +}; diff --git a/src/model/query/statistics/utils/attributeStats/object.ts b/src/model/query/statistics/utils/attributeStats/object.ts new file mode 100644 index 0000000..26ed4ec --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/object.ts @@ -0,0 +1,5 @@ +import type { ObjectStats } from '../../statisticsResultModel'; + +export const updateObjectStats = (stats: ObjectStats, value: object) => { + stats.length = Object.keys(value).length; +}; diff --git a/src/model/query/statistics/utils/attributeStats/temporal.ts b/src/model/query/statistics/utils/attributeStats/temporal.ts new file mode 100644 index 0000000..f2a2a11 --- /dev/null +++ b/src/model/query/statistics/utils/attributeStats/temporal.ts @@ -0,0 +1,10 @@ +import type { TemporalStats } from '../../statisticsResultModel'; + +export const updateTemporalStats = (stats: TemporalStats, value: string | Date) => { + const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime(); + + if (stats.min === undefined || timestamp < stats.min) stats.min = timestamp; + if (stats.max === undefined || timestamp > stats.max) stats.max = timestamp; + + stats.range = stats.max - stats.min; +}; diff --git a/src/model/query/statistics/utils/getAttributeType.ts b/src/model/query/statistics/utils/getAttributeType.ts new file mode 100644 index 0000000..f017b2e --- /dev/null +++ b/src/model/query/statistics/utils/getAttributeType.ts @@ -0,0 +1,76 @@ +import type { AttributeType } from '../statisticsResultModel'; + +// Check if a string is a valid date in the YYYY-MM-DD format +const isValidDate = (value: string): boolean => { + const dateRegex = /^\d{4}-\d{2}-\d{2}$/; // Matches YYYY-MM-DD format + const [year, month, day] = value.split('-').map(Number); + const date = new Date(value); + + // Check if the regex matches, and if the date is valid (correct month/day conversion) + return ( + dateRegex.test(value) && + date.getFullYear() === year && + date.getMonth() + 1 === month && // Months are 0-based in JS Date + date.getDate() === day + ); +}; + +// Check if a string is a valid time in the hh:mm:ss format +const isValidTime = (value: string): boolean => { + const timeRegex = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/; + return timeRegex.test(value); +}; + +// Check if a string is a valid datetime in the YYYY-MM-DD hh:mm:ss format +const isValidDatetime = (value: string): boolean => { + const datetimeRegex = /^\d{4}-\d{2}-\d{2} ([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/; + const [date, time] = value.split(' '); + + return datetimeRegex.test(value) && isValidDate(date) && isValidTime(time); +}; + +// Check if a string is a valid number +const isValidNumber = (value: string): boolean => { + return !isNaN(Number(value)) && !isNaN(parseFloat(value)); +}; + +// Determines the type of an attribute +export const getAttributeType = (value: any): AttributeType => { + if (typeof value === 'string') { + if (isValidNumber(value)) { + return 'number'; + } + if (isValidDatetime(value)) { + return 'datetime'; + } + if (isValidDate(value)) { + return 'date'; + } + if (isValidTime(value)) { + return 'time'; + } + return 'string'; + } + + if (typeof value === 'boolean') { + return 'boolean'; + } + + if (typeof value === 'number') { + return 'number'; + } + + if (Array.isArray(value)) { + return 'array'; + } + + if (value instanceof Date) { + return 'datetime'; + } + + if (typeof value === 'object' && value !== null) { + return 'object'; + } + + return 'string'; +}; diff --git a/src/model/query/statistics/utils/index.ts b/src/model/query/statistics/utils/index.ts new file mode 100644 index 0000000..4fb895d --- /dev/null +++ b/src/model/query/statistics/utils/index.ts @@ -0,0 +1,3 @@ +export * from './getAttributeType'; +export * from './attributeStats'; +export * from './updateStatistics'; diff --git a/src/model/query/statistics/utils/updateStatistics.ts b/src/model/query/statistics/utils/updateStatistics.ts new file mode 100644 index 0000000..82cab15 --- /dev/null +++ b/src/model/query/statistics/utils/updateStatistics.ts @@ -0,0 +1,45 @@ +import type { + AttributeStats, + AttributeType, + NumericalStats, + CategoricalStats, + BooleanStats, + TemporalStats, + ArrayStats, + ObjectStats, +} from '../statisticsResultModel'; +import { + updateArrayStats, + updateCategoricalStats, + updateNumericalStats, + updateObjectStats, + updateTemporalStats, + updateBooleanStats, +} from './attributeStats'; + +// Update statistics based on attribute type and value +export const updateStatistics = (attribute: AttributeStats<AttributeType>, value: any) => { + switch (attribute.attributeType) { + case 'number': + updateNumericalStats(attribute.statistics as NumericalStats, value); + break; + case 'string': + updateCategoricalStats(attribute.statistics as CategoricalStats, value); + break; + case 'boolean': + updateBooleanStats(attribute.statistics as BooleanStats, value); + break; + case 'datetime': + case 'timestamp': + case 'date': + case 'time': + updateTemporalStats(attribute.statistics as TemporalStats, value); + break; + case 'array': + updateArrayStats(attribute.statistics as ArrayStats, value); + break; + case 'object': + updateObjectStats(attribute.statistics as ObjectStats, value); + break; + } +}; diff --git a/src/model/reactflow.ts b/src/model/reactflow.ts index 1f72892..5f90c9d 100644 --- a/src/model/reactflow.ts +++ b/src/model/reactflow.ts @@ -21,3 +21,20 @@ export enum QueryElementTypes { // Function = 'function', Logic = 'logic', } + +export function isRelationHandle(handle: Handles): boolean { + return handle.startsWith(Handles.RelationLeft) || handle.startsWith(Handles.RelationRight); +} + +export function isEntityHandle(handle: Handles): boolean { + return handle.startsWith(Handles.EntityLeft) || handle.startsWith(Handles.EntityRight); +} + +export function isLogicHandle(handle: Handles): boolean { + return ( + handle.startsWith(Handles.LogicLeft) || + handle.startsWith(Handles.LogicRight) || + handle.startsWith(Handles.RelationAttribute) || + handle.startsWith(Handles.EntityAttribute) + ); +} diff --git a/src/model/saveStateModel.ts b/src/model/saveStateModel.ts index 25b3829..bf2890d 100644 --- a/src/model/saveStateModel.ts +++ b/src/model/saveStateModel.ts @@ -1,3 +1,4 @@ +import type { QueryGraphEdgeHandle } from './graphology'; import type { QueryBuilderSettings } from './query/queryBuilderModel'; export type DbConnection = { @@ -22,7 +23,7 @@ export type Query = { id?: number; graph: Graph; settings: QueryBuilderSettings; - attributesBeingShown: any[]; + attributesBeingShown: QueryGraphEdgeHandle[]; }; export type Visualization = { diff --git a/src/model/schemaModel.ts b/src/model/schemaModel.ts index f107e4d..e115c8c 100644 --- a/src/model/schemaModel.ts +++ b/src/model/schemaModel.ts @@ -54,15 +54,15 @@ export type SchemaGraphInference = { export type SchemaGraphStats = { nodes: { count: number; - stats: Record<string, NodeOrEdgeStats>; // node key -> Stats + stats: Record<string, SchemaNodeOrEdgeStats>; // node key -> Stats }; edges: { count: number; - stats: Record<string, NodeOrEdgeStats>; // edge key -> Stats + stats: Record<string, SchemaNodeOrEdgeStats>; // edge key -> Stats }; }; -export type NodeOrEdgeStats = { +export type SchemaNodeOrEdgeStats = { key: string; type?: string; count: number; diff --git a/src/model/utils.ts b/src/model/utils.ts new file mode 100644 index 0000000..9fa144f --- /dev/null +++ b/src/model/utils.ts @@ -0,0 +1,5 @@ +export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never; + +// Example usage: +// type Union = { a: string } | { b: number }; +// type Intersection = UnionToIntersection<Union>; // { a: string } & { b: number } diff --git a/src/model/webSocket/graphResult.ts b/src/model/webSocket/graphResult.ts index deebcf0..5da8126 100644 --- a/src/model/webSocket/graphResult.ts +++ b/src/model/webSocket/graphResult.ts @@ -1,21 +1,19 @@ import type { NodeQueryResult, EdgeQueryResult, GraphStatistics } from '../query'; -export interface GraphQueryResultFromBackend { +export type GraphQueryResultFromBackend = { nodes: NodeQueryResult[]; edges: EdgeQueryResult[]; // TODO: Also include type in node and edge // TODO: The backend should send all the different entitytypes and relationtypes in the result -} +}; -export interface GraphQueryResultMetaFromBackend { - nodes: NodeQueryResult[]; - edges: EdgeQueryResult[]; +export type GraphQueryResultMetaFromBackend = GraphQueryResultFromBackend & { metaData: GraphStatistics; // TODO: Also include type in node and edge // TODO: The backend should send all the different entitytypes and relationtypes in the result -} +}; export type GraphQueryTranslationResultMessage = { queryID: string; diff --git a/src/model/webSocket/index.ts b/src/model/webSocket/index.ts index 0d903cf..af4a8c1 100644 --- a/src/model/webSocket/index.ts +++ b/src/model/webSocket/index.ts @@ -2,3 +2,5 @@ export * from './message2Backend'; export * from './message2Frontend'; export * from './schema'; export * from './graphResult'; +export * from './policy'; +export * from './model'; diff --git a/src/model/webSocket/message2Backend.ts b/src/model/webSocket/message2Backend.ts index 77b559e..d6e4732 100644 --- a/src/model/webSocket/message2Backend.ts +++ b/src/model/webSocket/message2Backend.ts @@ -63,7 +63,7 @@ export type WsMessageBody = | WsMessageBodyI<wsKeys.schema, wsSubKeys.get, { cached: boolean; saveStateID: string }> | WsMessageBodyI<wsKeys.schema, wsSubKeys.getSchemaStats, { cached: boolean; saveStateID: string }> // Query - | WsMessageBodyI<wsKeys.query, wsSubKeys.get, { saveStateID: string; ml: MLInstanceTypes[]; cached: boolean }> + | WsMessageBodyI<wsKeys.query, wsSubKeys.get, { saveStateID: string; ml: MLInstanceTypes[]; cached: boolean; queryID: string }> | WsMessageBodyI<wsKeys.query, wsSubKeys.manual, string> // Insights | WsMessageBodyI<wsKeys.insight, wsSubKeys.create, InsightRequest> diff --git a/src/model/webSocket/message2Frontend.ts b/src/model/webSocket/message2Frontend.ts index af0e116..8d8fde3 100644 --- a/src/model/webSocket/message2Frontend.ts +++ b/src/model/webSocket/message2Frontend.ts @@ -4,6 +4,7 @@ import type { DBConnectionResult } from './dbConnection'; import type { GraphQueryTranslationResultMessage, QueryStatusResult } from './graphResult'; import type { InsightModel } from '../insight'; import type { SaveStateAuthorizationHeaders, UserAuthorizationHeaders } from './policy'; +import type { MLTypesEnum } from '../query'; export enum wsReturnKey { testedConnection = 'tested_connection', @@ -25,8 +26,10 @@ export enum wsReturnKey { userPolicy = 'user_policy', error = 'error', reconnect = 'reconnect', + schemaInference = 'schema_inference', } export type WsReturnKey = keyof typeof wsReturnKey; +export type wsReturnKeyWithML = wsReturnKey | MLTypesEnum; type WsMessageBody2FrontendI<T extends wsReturnKey, V> = { callID: string; @@ -65,4 +68,9 @@ export type WsMessageBackend2Frontend = // Reconnect | WsMessageBody2FrontendI<wsReturnKey.reconnect, { sessionID: string }>; -export type ResponseCallback = (data: WsMessageBackend2Frontend['value'], status: string) => void; +export type ResponseCallback<T extends wsReturnKey> = ( + data: Extract<WsMessageBackend2Frontend, { type: T }>['value'] | null, + status: string, +) => void; + +export type WsFrontendCall<T, R extends wsReturnKey = never> = (params: T, callback?: ResponseCallback<R>) => void; diff --git a/src/model/webSocket/model.ts b/src/model/webSocket/model.ts index 8b13789..e69de29 100644 --- a/src/model/webSocket/model.ts +++ b/src/model/webSocket/model.ts @@ -1 +0,0 @@ - diff --git a/src/model/webSocket/policy.ts b/src/model/webSocket/policy.ts index 6c40dbd..22c074e 100644 --- a/src/model/webSocket/policy.ts +++ b/src/model/webSocket/policy.ts @@ -1,5 +1,3 @@ -export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never; - export enum UserAuthorizationObjectsEnum { savestate = 'savestate', demoUser = 'demoUser', @@ -48,3 +46,21 @@ export type PolicyItem = { obj: string; act: string; }; + +export interface UserPolicy { + name: string; + email: string; + type: string; +} + +export type UserAuthenticationHeader = { + username: string; + userID: number; + roomID: string; + jwt: string; +}; + +export interface PolicyResourcesState { + read: string[]; + write: string[]; +} -- GitLab