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 (45)
Showing
with 125 additions and 324 deletions
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
#!/usr/bin/env sh
npx --no-install commitlint --edit $1
pnpm dlx commitlint --edit $1
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm push
pnpm push
\ No newline at end of file
......@@ -5,3 +5,6 @@ STAGING=dev
SKIP_LOGIN=true
BACKEND_USER=:3000
GRAPHPOLARIS_VERSION=dev
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
......@@ -5,3 +5,6 @@ VITE_SKIP_LOGIN=true
VITE_BACKEND_USER=:3000
VITE_BACKEND_QUERY=:3003
VITE_BACKEND_SCHEMA=:3002
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
......@@ -4,3 +4,6 @@ BACKEND_WSS_URL=
STAGING=
SKIP_LOGIN=
BACKEND_USER=
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
......@@ -7,3 +7,5 @@ VITE_BACKEND_USER=/user
VITE_BACKEND_QUERY=/query
VITE_BACKEND_SCHEMA=/schema
SENTRY_ENABLED=false
SENTRY_URL=
\ No newline at end of file
......@@ -17,6 +17,7 @@
"dependencies": {
"@graphpolaris/shared": "workspace:*",
"@reduxjs/toolkit": "^2.2.1",
"@sentry/react": "^8.25.0",
"config": "workspace:*",
"graphology": "^0.25.4",
"react": "^18.2.0",
......
......@@ -5,7 +5,6 @@ import {
useML,
useQuerybuilderGraph,
useQuerybuilderSettings,
useSchemaGraph,
useSessionCache,
} from '@graphpolaris/shared/lib/data-access';
import { resetGraphQueryResults, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
......@@ -35,7 +34,6 @@ export function App(props: App) {
const session = useSessionCache();
const dispatch = useAppDispatch();
const queryBuilderSettings = useQuerybuilderSettings();
const schema = useSchemaGraph();
const runQuery = () => {
if (session?.currentSaveState && query) {
......@@ -98,7 +96,6 @@ export function App(props: App) {
<QueryBuilder onRunQuery={runQuery} />
</Resizable>
</Resizable>
{/* <ConfigPanel /> */}
<InspectorPanel />
</Resizable>
</main>
......
import React, { useEffect, useState } from 'react';
import { useAppDispatch, useSchemaGraph, useSessionCache, useAuthorizationCache } from '@graphpolaris/shared/lib/data-access';
import React, { useEffect, useState, useCallback } from 'react';
import {
useAppDispatch,
useSchemaGraph,
useSessionCache,
useAuthorizationCache,
useCheckPermissionPolicy,
} from '@graphpolaris/shared/lib/data-access';
import { deleteSaveState, selectSaveState } from '@graphpolaris/shared/lib/data-access/store/sessionSlice';
import { SettingsForm } from './forms/settings';
import { LoadingSpinner } from '@graphpolaris/shared/lib/components/LoadingSpinner';
......@@ -21,7 +27,6 @@ export default function DatabaseSelector({}) {
const [dbSelectionMenuOpen, setDbSelectionMenuOpen] = useState<boolean>(false);
const [settingsMenuOpen, setSettingsMenuOpen] = useState<'add' | 'update' | undefined>(undefined);
const [selectedSaveState, setSelectedSaveState] = useState<SaveStateI | null>(null);
// const [addDbConnectionFormOpen, setAddDbConnectionFormOpen] = useState<boolean>(false);
useEffect(() => {
if (
......@@ -56,6 +61,29 @@ export default function DatabaseSelector({}) {
};
}, [connecting]);
const { canRead, canWrite } = useCheckPermissionPolicy();
const [readAllowed, setReadAllowed] = useState(false);
const [writeAllowed, setWriteAllowed] = useState(false);
const resource = 'database';
const checkReadPermission = useCallback(async () => {
const result = await canRead(resource);
setReadAllowed(result);
}, [canRead]);
const checkWritePermission = useCallback(async () => {
const result = await canWrite(resource);
setWriteAllowed(result);
}, [canWrite]);
useEffect(() => {
checkReadPermission();
}, [checkReadPermission]);
useEffect(() => {
checkWritePermission();
}, [checkWritePermission]);
return (
<div className="menu-walkthrough">
<TooltipProvider delayDuration={1000}>
......@@ -85,11 +113,14 @@ export default function DatabaseSelector({}) {
>
<DropdownTrigger
onClick={() => {
setDbSelectionMenuOpen(!dbSelectionMenuOpen);
if (connecting || authCache.authorized === false || !!authCache.roomID || writeAllowed) {
console.debug('User blocked from editing query due to being a viewer');
setDbSelectionMenuOpen(!dbSelectionMenuOpen);
}
}}
className="w-[18rem]"
size="md"
disabled={connecting || authCache.authorized === false || !!authCache.roomID}
disabled={connecting || authCache.authorized === false || !!authCache.roomID || !writeAllowed}
title={
<div className="flex items-center">
{connecting && session.currentSaveState && session.currentSaveState in session.saveStates ? (
......@@ -136,7 +167,6 @@ export default function DatabaseSelector({}) {
setConnecting(false);
setSettingsMenuOpen('add');
}}
title="Add new database"
>
{session.saveStates && Object.keys(session.saveStates).length === 0 ? (
<>
......
import React, { useEffect, useState } from 'react';
import {
DatabaseInfo,
DatabaseType,
SaveStateI,
databaseNameMapping,
databaseProtocolMapping,
nilUUID,
} from '@graphpolaris/shared/lib/data-access';
import { DatabaseType, SaveStateI, databaseNameMapping, databaseProtocolMapping, nilUUID } from '@graphpolaris/shared/lib/data-access';
import { Input } from '@graphpolaris/shared/lib/components/inputs';
import { useImmer } from 'use-immer';
import { initialState as qbInitialState } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
......
import React from 'react';
import { DatabaseInfo } from '@graphpolaris/shared/lib/data-access';
import { DatabaseType, SaveStateI, nilUUID } from '@graphpolaris/shared/lib/data-access/broker';
import { initialState as qbInitialState } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
......
......@@ -8,20 +8,24 @@
/* The comment above was added so the code coverage wouldn't count this file towards code coverage.
* We do not test components/renderfunctions/styling files.
* See testing plan for more details.*/
import React, { useState, useRef, useEffect } from 'react';
import logo_white from './gp-logo-white.svg';
import logo from './gp-logo.svg';
import { useAuthorizationCache, useAuth } from '@graphpolaris/shared/lib/data-access';
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { useAuthorizationCache, useAuth, useCheckPermissionPolicy } from '@graphpolaris/shared/lib/data-access';
import DatabaseSelector from './DatabaseManagement/dbConnectionSelector';
import { DropdownItem } from '@graphpolaris/shared/lib/components/dropdowns';
import GpLogo from './gp-logo';
import { Popover, PopoverContent, PopoverTrigger } from '@graphpolaris/shared/lib/components/layout/Popover';
import { useDispatch } from 'react-redux';
import { Dialog, DialogContent, DialogTrigger } from '@graphpolaris/shared/lib/components/layout/Dialog';
import { UserManagementContent } from '@graphpolaris/shared/lib/components/userManagementContent/UserManagementContent';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
export const Navbar = () => {
const dropdownRef = useRef<HTMLDivElement>(null);
const auth = useAuth();
const authCache = useAuthorizationCache();
const [menuOpen, setMenuOpen] = useState(false);
const dispatch = useDispatch();
const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION;
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
......@@ -35,15 +39,43 @@ export const Navbar = () => {
};
}, [menuOpen]);
const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION;
const { canRead, canWrite } = useCheckPermissionPolicy();
const [readAllowed, setReadAllowed] = useState(false);
const [writeAllowed, setWriteAllowed] = useState(false);
const resource = 'policy';
const checkReadPermission = useCallback(async () => {
const result = await canRead(resource);
setReadAllowed(result);
}, [canRead]);
const checkWritePermission = useCallback(async () => {
const result = await canWrite(resource);
setWriteAllowed(result);
}, [canWrite]);
useEffect(() => {
checkReadPermission();
}, [checkReadPermission]);
useEffect(() => {
checkWritePermission();
}, [checkWritePermission]);
const handleConfirmUsers = (users: { name: string; email: string; type: string }[]) => {
//TODO !FIXME: when the user clicks on confirm, users state is ready to be sent to backend
};
const handleClickShareLink = () => {
//TODO !FIXME: add copy link to clipoard functionality
dispatch(addInfo('Link copied to clipboard'));
};
return (
<nav className="w-full px-4 h-12 flex flex-row items-center gap-2 md:gap-3 lg:gap-4">
<a href="https://graphpolaris.com/" target="_blank" className="shrink-0 text-dark">
<GpLogo className="h-7" />
</a>
<DatabaseSelector />
<div className="ml-auto">
<div className="w-fit" ref={dropdownRef}>
<Popover>
......@@ -56,12 +88,12 @@ export const Navbar = () => {
</div>
</PopoverTrigger>
<PopoverContent className="w-56 z-30 bg-white rounded-sm border-[1px] outline-none">
<PopoverContent className="w-56 z-30 bg-light rounded-sm border-[1px] outline-none">
<div className="p-2 text-sm border-b">
<h2 className="font-bold">user: {authCache.username}</h2>
<h3 className="text-xs break-words">session: {authCache.sessionID}</h3>
<h3 className="text-xs break-words">license: Creator</h3>
</div>
{authCache.authorized ? (
<>
<DropdownItem
......@@ -78,7 +110,6 @@ export const Navbar = () => {
<DropdownItem value="Login" onClick={() => {}} />
</>
)}
{authCache?.roomID && (
<div className="p-2 border-b">
<h3 className="text-xs break-words">Share ID: {authCache.roomID}</h3>
......@@ -87,6 +118,20 @@ export const Navbar = () => {
<div className="p-2 border-t">
<h3 className="text-xs">Version: {buildInfo}</h3>
</div>
{writeAllowed && (
<>
<Dialog>
<DialogTrigger className="ml-2 text-sm hover:bg-secondary-200">Manage Viewers Permission</DialogTrigger>
<DialogContent>
<UserManagementContent
sessionId={authCache.sessionID ?? ''}
onConfirm={handleConfirmUsers}
onClickShareLink={handleClickShareLink}
/>
</DialogContent>
</Dialog>
</>
)}
</PopoverContent>
</Popover>
</div>
......
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Provider } from 'react-redux';
import * as Sentry from '@sentry/react';
import { store } from '@graphpolaris/shared/lib/data-access/store';
import App from './app/App';
import { createRoot } from 'react-dom/client';
import './main.css';
if (import.meta.env.SENTRY_ENABLED) {
Sentry.init({
dsn: import.meta.env.SENTRY_URL,
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
}
(window as any).global = window;
const domNode = document.getElementById('root');
......@@ -16,7 +26,6 @@ if (domNode) {
<Provider store={store}>
<Router>
<Routes>
{/* App */}
<Route path="/" element={<App load={undefined} />}></Route>
<Route path="/fraud" element={<App load="5bdf3354-673f-4dec-b6a0-196e67cd211c" />}></Route>
</Routes>
......
......@@ -45,7 +45,7 @@
"postcss.config.js", // excludes PostCSS configuration file
"tsconfig.tsbuildinfo" // excludes TypeScript build info file
],
"include": ["vite.config.ts", "src/**/*"],
"include": ["vite.config.ts", "src/**/*", "../../libs/shared/lib/error-boundary"],
"files": ["vite.config.ts"],
"references": []
}
......@@ -97,6 +97,11 @@
--clr-cat-12: var(--clr-neutral-50);
--clr-cat-13: var(--clr-neutral-50);
--clr-cat-14: var(--clr-neutral-50);
/* Colors pills */
--clr-node: var(--clr-acc);
--clr-relation: var(--clr-pri);
--clr-filter: var(--clr-acc--800);
}
body.light-mode {
......@@ -182,6 +187,7 @@
}
body.dark-mode {
color-scheme: dark;
--clr-light: var(--clr-neutral-100);
--clr-dark: var(--clr-white);
......
import React from 'react';
import type { SVGProps } from 'react';
export function CarbonStringInteger(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path
fill="currentColor"
d="M26 12h-4v2h4v2h-3v2h3v2h-4v2h4a2.003 2.003 0 0 0 2-2v-6a2 2 0 0 0-2-2m-7 10h-6v-4a2 2 0 0 1 2-2h2v-2h-4v-2h4a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-2v2h4zM8 20v-8H6v1H4v2h2v5H4v2h6v-2z"
></path>
</svg>
);
}
export function CarbonStringText(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path
fill="currentColor"
d="M29 22h-5a2.003 2.003 0 0 1-2-2v-6a2 2 0 0 1 2-2h5v2h-5v6h5zM18 12h-4V8h-2v14h6a2.003 2.003 0 0 0 2-2v-6a2 2 0 0 0-2-2m-4 8v-6h4v6zm-6-8H3v2h5v2H4a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h6v-8a2 2 0 0 0-2-2m0 8H4v-2h4z"
></path>
</svg>
);
}
export function CarbonCalendar(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path
fill="currentColor"
d="M26 4h-4V2h-2v2h-8V2h-2v2H6c-1.1 0-2 .9-2 2v20c0 1.1.9 2 2 2h20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 22H6V12h20zm0-16H6V6h4v2h2V6h8v2h2V6h4z"
></path>
</svg>
);
}
export function CarbonBoolean(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path fill="currentColor" d="M23 23a7 7 0 1 1 7-7a7.01 7.01 0 0 1-7 7m0-12a5 5 0 1 0 5 5a5.006 5.006 0 0 0-5-5"></path>
<circle cx={9} cy={16} r={7} fill="currentColor"></circle>
</svg>
);
}
export function CarbonUndefined(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" viewBox="0 0 32 32" {...props}>
<path fill="currentColor" d="M11 14h10v4H11z"></path>
<path
fill="currentColor"
d="M29.391 14.527L17.473 2.609A2.08 2.08 0 0 0 16 2c-.533 0-1.067.203-1.473.609L2.609 14.527C2.203 14.933 2 15.466 2 16s.203 1.067.609 1.473L14.526 29.39c.407.407.941.61 1.474.61s1.067-.203 1.473-.609L29.39 17.474c.407-.407.61-.94.61-1.474s-.203-1.067-.609-1.473M16 28.036L3.965 16L16 3.964L28.036 16z"
></path>
</svg>
);
}
import React from 'react';
import { Icon } from '@graphpolaris/shared/lib/components/icon';
import styles from './cardtooltipvis.module.scss';
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@graphpolaris/shared/lib/components/tooltip';
import {
CarbonStringInteger,
CarbonStringText,
CarbonCalendar,
CarbonBoolean,
CarbonUndefined,
} from '@graphpolaris/shared/lib/assets/carbonIcons/carbonIcons';
export type CardToolTipVisProps = {
type: string;
name: string;
data: Record<string, any>;
typeOfSchema?: string;
colorHeader: string;
connectedTo?: string;
connectedFrom?: string;
maxVisibleItems?: number;
numberOfElements?: number;
};
const formatNumber = (number: number) => {
return number.toLocaleString('de-DE'); // Format number with dots as thousand separators
};
export const VisualizationTooltip: React.FC<CardToolTipVisProps> = ({
type,
name,
data,
colorHeader,
maxVisibleItems = 5,
connectedFrom,
connectedTo,
typeOfSchema,
numberOfElements,
}) => {
const itemsToShow = Object.entries(data).slice(0, maxVisibleItems);
return (
<div className="border-1 border-sec-200 bg-white w-[12rem] -mx-2 -my-1">
<div className="flex m-0 justify-start items-stretch border-b border-sec-200 relative">
<div className="left-0 top-0 h-auto w-1.5" style={{ backgroundColor: colorHeader }}></div>
<div className="px-2.5 py-1 truncate flex">
<Tooltip>
<TooltipTrigger className={'flex max-w-full'}>
<span className="text-base font-semibold truncate">{name}</span>
</TooltipTrigger>
<TooltipContent side={'top'}>
<span>{name}</span>
</TooltipContent>
</Tooltip>
</div>
{/*
<div className="flex-shrink-0 ml-2">
<Button variantType="secondary" variant="ghost" size="xs" rounded={true} iconComponent={<Close />} onClick={() => {}} />
</div>
*/}
</div>
{type === 'schema' && numberOfElements && (
<div className="px-4 py-1 border-b border-sec-200">
<div className="flex flex-row gap-1 items-center justify-between">
<Icon component="icon-[ic--baseline-numbers]" size={24} />{' '}
<span className="ml-auto text-right">{formatNumber(numberOfElements)}</span>
</div>
</div>
)}
{type === 'schema' && typeOfSchema === 'relationship' && (
<div className="px-4 py-1 border-b border-sec-200">
<div className="flex flex-row gap-3 items-center justify-between">
<span className="font-semibold">From</span>
<span className="ml-auto text-right">{connectedFrom}</span>
</div>
<div className="flex flex-row gap-1 items-center justify-between">
<span className="font-semibold">To</span>
<span className="ml-auto text-right">{connectedTo}</span>
</div>
</div>
)}
<TooltipProvider delayDuration={300}>
<div className={`px-3 py-1.5 ${data.length > maxVisibleItems ? 'max-h-20 overflow-y-auto' : ''}`}>
{data && Object.keys(data).length === 0 ? (
<div className="flex justify-center items-center h-full">
<span>No attributes</span>
</div>
) : (
Object.entries(data).map(([k, v]) => (
<Tooltip key={k}>
<div className="flex flex-row gap-1 items-center min-h-6">
<span className={`font-semibold truncate ${type === 'schema' ? 'w-[90%]' : 'min-w-[40%]'}`}>{k}</span>
<TooltipTrigger asChild>
<span className="ml-auto text-right truncate grow-1 flex items-center">
{type === 'schema' ? (
<Icon
className="ml-auto text-right flex-shrink-0"
component={
v === 'int' || v === 'float' ? (
<CarbonStringInteger />
) : v === 'string' ? (
<CarbonStringText />
) : v === 'boolean' ? (
<CarbonBoolean />
) : v === 'date' ? (
<CarbonCalendar />
) : v === 'undefined' ? (
<CarbonUndefined />
) : (
<CarbonUndefined />
)
}
color="hsl(var(--clr-sec--400))"
size={24}
/>
) : v !== undefined && (typeof v !== 'object' || Array.isArray(v)) && v != '' ? (
<span className="ml-auto text-right truncate">{typeof v === 'number' ? formatNumber(v) : v.toString()}</span>
) : (
<div className={`ml-auto mt-auto h-4 w-12 ${styles['diagonal-lines']}`}></div>
)}
</span>
</TooltipTrigger>
<TooltipContent side="right">
<div className="max-w-[18rem] break-all line-clamp-6">
{v !== undefined && (typeof v !== 'object' || Array.isArray(v)) && v != '' ? v : 'noData'}
</div>
</TooltipContent>
</div>
</Tooltip>
))
)}
</div>
</TooltipProvider>
</div>
);
};
.diagonal-lines {
border: 1px solid lightgray;
background:
repeating-linear-gradient(-45deg, transparent, transparent 6px, #eaeaea 6px, #eaeaea 8px),
/* Gray diagonal lines */ linear-gradient(to bottom, transparent, transparent); /* Vertical gradient */
}
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { VisualizationTooltip, CardToolTipVisProps } from '@graphpolaris/shared/lib/components/CardToolTipVis';
const metaCardToolTipVis: Meta<typeof VisualizationTooltip> = {
component: VisualizationTooltip,
title: 'Components/CardToolTipVis',
};
export default metaCardToolTipVis;
type Story = StoryObj<typeof VisualizationTooltip>;
export const SchemaNode: Story = {
render: (args) => {
return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} />
</div>
);
},
args: {
type: 'schema',
typeOfSchema: 'node',
name: 'Person',
data: {
born: 'int',
name: 'string',
description: 'string',
},
colorHeader: '#fb7b04',
numberOfElements: 1000,
},
};
export const SchemaRelationship: Story = {
render: (args) => {
return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} />
</div>
);
},
args: {
type: 'schema',
typeOfSchema: 'relationship',
name: 'Directed',
data: {
born: 'int',
name: 'string',
description: 'string',
imdb: 'string',
imdbVotes: 'int',
},
colorHeader: '#0676C1',
connectedTo: 'Person',
numberOfElements: 231230,
connectedFrom: 'Movie',
},
};
export const PopUpVis: Story = {
render: (args) => {
return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} />
</div>
);
},
args: {
name: 'Person',
type: 'popupvis',
data: {
bio: 'From wikipedia was born in usa from a firefighter father',
name: 'Charlotte Henry',
born: {},
imdbRank: 21213,
imdbVotes: 1213,
poster: 'https://image.tmdb.org/t/p/w440_and_h660_face/kTKiREs37qd8GUlNI4Koiupwy6W.jpg',
tmdbId: '94105',
country: undefined,
labels: ['Actor', 'Person', 'Human'],
},
colorHeader: '#B69AEf',
},
};
export * from './VisualizationTooltip';