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 (12)
Showing
with 181 additions and 55 deletions
......@@ -13,8 +13,9 @@ import { useAuthCache, useAuthentication } from '@graphpolaris/shared/lib/data-a
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 { showSharableExploration } from 'config';
import { Button, useActiveSaveStateAuthorization } from '@graphpolaris/shared';
import { useDispatch } from 'react-redux';
import { getEnvVariable, showManagePermissions, showSharableExploration } from 'config';
import { Button, Dialog, DialogContent, DialogTrigger, useActiveSaveStateAuthorization, useSessionCache } from '@graphpolaris/shared';
import { ManagementTrigger, ManagementViews } from '@graphpolaris/shared/lib/management';
const AuthURL = import.meta.env.GP_AUTH_URL;
......@@ -25,7 +26,7 @@ export const Navbar = () => {
const authCache = useAuthCache();
const authorization = useActiveSaveStateAuthorization();
const [menuOpen, setMenuOpen] = useState(false);
const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION;
const buildInfo = getEnvVariable('GRAPHPOLARIS_VERSION');
const [managementOpen, setManagementOpen] = useState<boolean>(false);
const [current, setCurrent] = useState<ManagementViews>('overview');
......@@ -88,7 +89,7 @@ export const Navbar = () => {
<DropdownItem
value="Log out"
onClick={() => {
location.replace(`${AuthURL}/outpost.goauthentik.io/sign_out`);
location.replace(`${getEnvVariable('GP_AUTH_URL')}/outpost.goauthentik.io/sign_out`);
}}
/>
</>
......
......@@ -4,6 +4,7 @@ import { useLocation } from 'react-router-dom';
import { Button } from '@graphpolaris/shared/lib/components/buttons';
import { useCases } from './use-cases';
import { useAuthCache } from '@graphpolaris/shared/lib/data-access';
import { OnboardingTooltip } from './tooltip';
interface OnboardingState {
run?: boolean;
......@@ -54,7 +55,7 @@ export function Onboarding({}) {
return (
<div>
{showWalkthrough && (
<div className="bg-accent-light alert absolute bottom-5 left-5 w-fit cursor-pointer z-50">
<div className="bg-light alert absolute bottom-5 left-5 w-fit cursor-pointer z-50">
<Button onClick={startWalkThrough} label={'Start a Tour'} variant="ghost" />
<Button onClick={() => addWalkthroughCookie()} iconComponent="icon-[ic--baseline-close]" variant="ghost" rounded />
</div>
......@@ -68,6 +69,7 @@ export function Onboarding({}) {
showSkipButton={true}
hideCloseButton={true}
callback={handleJoyrideCallback}
tooltipComponent={OnboardingTooltip}
styles={{
options: {
primaryColor: '#FF7D00',
......
import React from 'react';
import { TooltipRenderProps } from 'react-joyride';
const OnboardingTooltip = (props: TooltipRenderProps) => {
const { backProps, closeProps, continuous, index, primaryProps, skipProps, step, tooltipProps } = props;
return (
<div className="bg-light p-4 rounded-lg shadow-lg" {...tooltipProps}>
<button className="absolute top-2 right-2 text-secondary-500 hover:text-secondary-700" {...closeProps}>
&times;
</button>
{step.title && <h4 className="text-lg font-semibold mb-2">{step.title}</h4>}
<div className="mb-4">{step.content}</div>
<div className="flex justify-between items-center">
<button className="text-sm text-gray-500 hover:text-gray-700" {...skipProps}>
{skipProps.title}
</button>
<div className="flex space-x-2">
{index > 0 && (
<button className="bg-light-200 text-light-700 px-3 py-1 rounded hover:bg-light-300" {...backProps}>
{backProps.title}
</button>
)}
{continuous && (
<button className="bg-primary text-light px-3 py-1 rounded hover:bg-primary-dark" {...primaryProps}>
{primaryProps.title}
</button>
)}
</div>
</div>
</div>
);
};
export { OnboardingTooltip };
......@@ -48,7 +48,7 @@ export const generalScript: GPStep[] = [
disableBeacon: true,
},
{
target: '.menu-walkthrough',
target: '.database-menu',
title: 'Menu',
content: 'In the menu you can manage databases, switch between them, and perform other actions.',
placement: 'bottom',
......
......@@ -5,11 +5,13 @@ 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 { getEnvVariable } from 'config';
import './main.css';
import { ErrorBoundary } from '@graphpolaris/shared/lib/components/errorBoundary';
if (import.meta.env.SENTRY_ENABLED) {
if (getEnvVariable('SENTRY_ENABLED')) {
Sentry.init({
dsn: import.meta.env.SENTRY_URL,
dsn: getEnvVariable('SENTRY_URL'),
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
......@@ -25,10 +27,12 @@ if (domNode) {
root.render(
<Provider store={store}>
<Router>
<Routes>
<Route path="/" element={<App load={undefined} />}></Route>
<Route path="/fraud" element={<App load="5bdf3354-673f-4dec-b6a0-196e67cd211c" />}></Route>
</Routes>
<ErrorBoundary fallback={<div>Oops! Something went wrong. Please try again.</div>}>
<Routes>
<Route path="/" element={<App load={undefined} />}></Route>
<Route path="/fraud" element={<App load="5bdf3354-673f-4dec-b6a0-196e67cd211c" />}></Route>
</Routes>
</ErrorBoundary>
</Router>
</Provider>,
);
......
// Safely retrieve environment variable values with a default fallback
const getEnvVariable = (key: string, defaultValue: string = 'false'): string => {
return import.meta.env[key] ?? defaultValue;
const envFallbacks: Record<string, any> = {
SENTRY_ENABLED: false,
SENTRY_URL: '',
GRAPHPOLARIS_VERSION: 'prod',
GP_AUTH_URL: '',
BACKEND_WSS_URL: '',
BACKEND_URL: '',
BACKEND_USER: '',
WIP_TABLEVIS: true,
WIP_NODELINKVIS: true,
WIP_RAWJSONVIS: true,
WIP_PAOHVIS: true,
WIP_MATRIXVIS: true,
WIP_SEMANTICSUBSTRATESVIS: true,
WIP_MAPVIS: true,
WIP_INSIGHT_SHARING: true,
WIP_VIEWER_PERMISSIONS: true,
WIP_SHARABLE_EXPLORATION: true,
};
// Check if the environment is production
const isProduction = (): boolean => {
return getEnvVariable('GRAPHPOLARIS_VERSION', 'dev') === 'prod';
};
type EnvFallbackKey = keyof typeof envFallbacks;
// Check if the Manage Permissions feature is enabled
const showManagePermissions = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_VIEWER_PERMISSIONS') === 'false');
// Safely retrieve environment variable values with a default fallback
const getEnvVariable = (key: EnvFallbackKey): any => {
const value = import.meta.env[key];
if (value === undefined) {
console.error(`Environment variable ${key} is missing, using fallback value.`);
return envFallbacks[key];
}
return value;
};
// Check if the Insight Sharing feature is enabled
const showInsightSharing = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_INSIGHT_SHARING') === 'false');
// Check if the environment is production
const isProduction = (): boolean => {
return getEnvVariable('GRAPHPOLARIS_VERSION') === 'prod';
};
// Check if the Insight Sharing feature is enabled
const showSharableExploration = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_SHARABLE_EXPLORATION') === 'false');
// Utility to check if a WIP feature is enabled
const isWIPFeatureEnabled = (featureKey: string): boolean => {
return getEnvVariable(`WIP_${featureKey.toUpperCase()}`) === 'false';
};
// Utility to check if a specific visualization is released based on environment variables
const isVisualizationReleased = (visualizationName: string): boolean => {
const visualizationFlag = getEnvVariable(`WIP_${visualizationName.toUpperCase()}`, 'false');
return !isProduction() || (isProduction() && visualizationFlag === 'false');
};
// Feature flags with checks for production and WIP feature flags
const showManagePermissions = (): boolean => !isProduction() || isWIPFeatureEnabled('viewer_permissions');
const showInsightSharing = (): boolean => !isProduction() || isWIPFeatureEnabled('insight_sharing');
const showSharableExploration = (): boolean => !isProduction() || isWIPFeatureEnabled('sharable_exploration');
const isVisualizationReleased = (visualizationName: string): boolean => !isProduction() || isWIPFeatureEnabled(visualizationName);
export { isProduction, showManagePermissions, showInsightSharing, showSharableExploration, isVisualizationReleased };
export { getEnvVariable, isProduction, showManagePermissions, showInsightSharing, showSharableExploration, isVisualizationReleased };
......@@ -13,6 +13,7 @@ const IconMap: Record<SchemaAttributeTypes, string> = {
duration: 'icon-[carbon--calendar]',
number: 'icon-[carbon--string-integer]',
location: 'icon-[carbon--location]',
array: 'icon-[ic--baseline-data-array]',
};
function getDataTypeIcon(data_type?: SchemaAttributeTypes): string {
......
......@@ -130,7 +130,7 @@ export function DropdownItem({ value, disabled, className, onClick, submenu, sel
onMouseLeave={() => setIsSubmenuOpen(false)}
>
{value}
{submenu != null ? <Icon component='icon-[ic--baseline-arrow-right] ms-2' size={14} /> : ''}
{submenu != null ? <Icon component="icon-[ic--baseline-arrow-right] ms-2" size={14} /> : ''}
{submenu && isSubmenuOpen && <DropdownSubmenuContainer ref={submenuRef}>{submenu}</DropdownSubmenuContainer>}
{children}
</li>
......@@ -143,7 +143,11 @@ type DropdownSubmenuContainerProps = {
export const DropdownSubmenuContainer = React.forwardRef<HTMLDivElement, DropdownSubmenuContainerProps>(({ children }, ref) => {
return (
<div ref={ref} className="absolute bg-light p-1 rounded max-h-60 overflow-auto focus:outline-none shadow-sm border left-[97%]" style={{transform: 'translate(0px, calc(50% - 19px))'}}>
<div
ref={ref}
className="absolute bg-light p-1 rounded max-h-60 overflow-auto focus:outline-none shadow-sm border left-[95%]"
style={{ transform: 'translate(0px, calc(50% - 19px))' }}
>
<ul className="text-sm text-secondary-700">{children}</ul>
</div>
);
......
......@@ -448,7 +448,7 @@ export const CheckboxInput = ({ label, value, options, onChange, tooltip }: Chec
export const BooleanInput = ({ label, value, onChange, tooltip, info, size, required, className }: BooleanProps) => {
return (
<Tooltip>
<TooltipTrigger className={className + 'w-full flex justify-between'}>
<TooltipTrigger className={className + ' w-full flex justify-between'}>
{label && (
<label className="label p-0">
<span
......
......@@ -5,11 +5,12 @@ export type Panel = {
title: string | React.ReactNode;
tooltips?: React.ReactNode;
children: React.ReactNode;
className?: string;
};
export function Panel(props: Panel) {
return (
<div className="flex flex-col border w-full h-full bg-light">
<div className={`flex flex-col border w-full h-full bg-light ${props.className || ''}`}>
<div className="sticky shrink-0 top-0 flex items-stretch justify-between h-7 bg-secondary-100 border-b border-secondary-200 max-w-full">
<div className="flex items-center">
<h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">{props.title}</h1>
......
......@@ -30,7 +30,6 @@ export const Tabs = (props: { children: React.ReactNode; tabType?: TabTypes }) =
export const Tab = ({
activeTab,
text,
variant = 'ghost',
className = '',
...props
}: ButtonProps & {
......@@ -43,7 +42,7 @@ export const Tab = ({
if (context.tabType === 'inline') {
className += ` pl-2 pr-1 gap-1 relative h-full text-secondary-500 border-secondary-200 before:content-['']
before:absolute before:left-0 before:bottom-0 before:h-[2px] before:w-full
${activeTab ? 'before:bg-primary-500' : 'before:bg-transparent hover:before:bg-secondary-300 hover:bg-secondary-200'}`;
${activeTab ? 'before:bg-primary-500 hover:bg-inherit' : 'before:bg-transparent hover:before:bg-secondary-300 hover:text-dark hover:bg-secondary-200'}`;
} else if (context.tabType === 'rounded') {
className += ` -mb-px py-4 px-4 text-sm text-secondary-500 text-center border rounded-t-lg rounded-b-none
${activeTab ? 'active text-secondary-950 border-l-secondary-300 border-r-secondary-300 border-t-secondary-300 border-b-white' : ''}
......@@ -53,10 +52,10 @@ export const Tab = ({
}
return (
<Button className={className} label={text} variant={variant} size="xs" {...props}>
{/* <p className={`text-xs font-semibold ${activeTab && 'text-secondary-950'}`}>{text}</p> */}
<div className={`btn btn-ghost btn-xs rounded-none ${className}`} {...props}>
<span>{text}</span>
{props.children}
</Button>
</div>
);
};
......@@ -4,6 +4,7 @@
* © Copyright Utrecht University (Department of Information and Computing Sciences)
*/
import { getEnvVariable } from 'config';
import { UseIsAuthorizedState } from '../store/authSlice';
import { ReceiveMessageI, SendMessageI, SendMessageWithSessionI } from './types';
......@@ -38,7 +39,7 @@ export class Broker {
/** Get the singleton instance of the Broker. */
public static instance(): Broker {
if (!this.singletonInstance) this.singletonInstance = new Broker(import.meta.env.BACKEND_WSS_URL);
if (!this.singletonInstance) this.singletonInstance = new Broker(getEnvVariable('BACKEND_WSS_URL'));
return this.singletonInstance;
}
......
import { getEnvVariable } from 'config';
import { useAppDispatch, useAuthCache } from '../store';
import { authenticated, changeRoom, UserAuthenticationHeader } from '../store/authSlice';
const domain = import.meta.env.BACKEND_URL;
const userURI = import.meta.env.BACKEND_USER;
const domain = getEnvVariable('BACKEND_URL');
const userURI = getEnvVariable('BACKEND_USER');
export const fetchSettings: RequestInit = {
method: 'GET',
......
......@@ -9,9 +9,10 @@ import { SelectionConfig } from '../vis/components/config/SelectionConfig';
import { SchemaSettings } from '../schema/panel/SchemaSettings';
import { QuerySettings } from '../querybuilder/panel/querysidepanel/QuerySettings';
import { useActiveVisualization } from '@graphpolaris/shared/lib/data-access';
import { getEnvVariable } from 'config';
export function InspectorPanel(props: { children?: React.ReactNode }) {
const buildInfo = import.meta.env.GRAPHPOLARIS_VERSION;
const buildInfo = getEnvVariable('GRAPHPOLARIS_VERSION');
const selection = useSelection();
const focus = useFocus();
const dispatch = useDispatch();
......
......@@ -41,7 +41,7 @@ export function ManagementTrigger({ managementOpen, setManagementOpen, current,
}, [connecting]);
return (
<div>
<div className="database-menu">
<ManagementDialog
open={managementOpen}
onClose={() => setManagementOpen(!managementOpen)}
......
......@@ -9,6 +9,7 @@ const mockDataArray = [
'mockLargeQueryResults',
'mockMobilityQueryResult',
'typesMockQueryResults',
'testUnMatchHeadersDataResults',
'smallFlightsQueryResults',
'smallVillainQueryResults',
'smallVillainDoubleArchQueryResults',
......
{
"edges": [
{ "from": "worker/1", "_id": "onderdeel_van/1100", "_key": "1100", "_rev": "_cYl_jTO--O", "to": "worker/3", "attributes": {} },
{ "from": "worker/3", "_id": "onderdeel_van/662", "_key": "662", "_rev": "_cYl_jR2--G", "to": "worker/1", "attributes": {} }
],
"nodes": [
{
"_id": "worker/1",
"_key": "1",
"_rev": "_cYl-Qmq-_H",
"attributes": {
"name": "John Doe",
"age": 30,
"height": 170.2
}
},
{
"_id": "worker/3",
"_key": "2",
"_rev": "_cYl-Qmq--5",
"attributes": {
"name": "Bob Johnson",
"age": 35 }
},
{
"_id": "worker/2",
"_key": "2",
"_rev": "_cYl-Qmq--5",
"attributes": {
"age": 38,
"height": 195.2
}
}
]
}
......@@ -76,6 +76,14 @@ export const ContextMenu = (props: {
}
function removeNode() {
if (!props.node) return;
const connectedLogicPills = graphologyGraph.neighbors(props.node.id);
connectedLogicPills.forEach((pill) => {
const attributes = graphologyGraph.getNodeAttributes(pill);
if (attributes.type === 'logic') {
graphologyGraph.dropNode(pill);
}
});
graphologyGraph.dropNode(props.node.id);
dispatch(setQuerybuilderGraphology(graphologyGraph));
props.onClose();
......
......@@ -45,6 +45,7 @@ import { ConnectingNodeDataI } from './utils/connectorDrop';
import { resultSetFocus } from '../../data-access/store/interactionSlice';
import { QueryBuilderDispatcherContext } from './QueryBuilderDispatcher';
import { QueryBuilderNav, QueryBuilderToggleSettings } from './QueryBuilderNav';
import html2canvas from 'html2canvas';
import { ContextMenu } from './ContextMenu';
export type QueryBuilderProps = {
......@@ -57,6 +58,7 @@ export type QueryBuilderProps = {
export const QueryBuilderInner = (props: QueryBuilderProps) => {
const [toggleSettings, setToggleSettings] = useState<QueryBuilderToggleSettings>();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const reactFlowRef = useRef<HTMLDivElement>(null);
const queryBuilderSettings = useQuerybuilderSettings();
const saveStateAuthorization = useActiveSaveStateAuthorization();
......@@ -504,6 +506,21 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
}
}, [queryBuilderSettings]);
const handleScreenshot = async () => {
if (reactFlowRef.current) {
try {
const canvas = await html2canvas(reactFlowRef.current);
const screenshotUrl = canvas.toDataURL('image/jpeg');
const link = document.createElement('a');
link.href = screenshotUrl;
link.download = 'querybuilder.png';
link.click();
} catch (error) {
dispatch(addError('Screenshot failed.'));
}
}
};
return (
<QueryBuilderDispatcherContext.Provider
value={{
......@@ -543,7 +560,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
onRunQuery={() => {
if (props.onRunQuery) props.onRunQuery();
}}
onScreenshot={() => {}}
onScreenshot={handleScreenshot}
onLogic={() => {
if (toggleSettings === 'logic') setToggleSettings(undefined);
else setToggleSettings('logic');
......@@ -602,6 +619,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
</defs>
</svg>
<ReactFlow
ref={reactFlowRef}
edges={elements.edges}
nodes={elements.nodes}
snapGrid={[10, 10]}
......
......@@ -19,11 +19,7 @@ import { EntityPill } from '@graphpolaris/shared/lib/components/pills';
import { Handle, Position, useUpdateNodeInternals } from 'reactflow';
import { NodeAttribute, SchemaReactflowEntityNode, toHandleId } from '../../../model';
import { PillAttributes } from '../../pillattributes/PillAttributes';
import { DropdownTrigger, DropdownContainer, DropdownItemContainer, DropdownItem } from '@graphpolaris/shared/lib/components/dropdowns';
import { PopoverContext } from '@graphpolaris/shared/lib/components/layout/Popover';
import { useDispatch } from 'react-redux';
import { isEqual } from 'lodash-es';
import { getDataTypeIcon } from '@graphpolaris/shared/lib/components/DataTypeIcon';
import { uniqBy } from 'lodash-es';
/**
* Component to render an entity flow element
......@@ -43,6 +39,7 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => {
[graph],
);
const uniqueAttributes = useMemo(() => uniqBy(data.attributes, (attr) => attr.handleData.attributeName), [data.attributes]);
const unionType = useQuerybuilderUnionTypes()[node.id];
return (
......@@ -75,7 +72,7 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => {
{data?.attributes && (
<PillAttributes
node={node}
attributes={data.attributes}
attributes={uniqueAttributes}
attributeEdges={attributeEdges.map((edge) => edge?.attributes)}
unionType={unionType}
/>
......