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 (15)
Showing
with 264 additions and 114 deletions
......@@ -11,14 +11,21 @@ SENTRY_URL=
GP_AUTH_URL=
WIP_TABLEVIS=false
WIP_NODELINKVIS=false
WIP_RAWJSONVIS=false
WIP_PAOHVIS=true
WIP_MATRIXVIS=true
WIP_SEMANTICSUBSTRATESVIS=true
WIP_MAPVIS=true
LINK_PREDICTION=true
CENTRALITY=true
COMMUNITY_DETECTION=true
SHORTEST_PATH=true
WIP_INSIGHT_SHARING=true
WIP_VIEWER_PERMISSIONS=true
WIP_SHARABLE_EXPLORATION=true
TABLEVIS=true
NODELINKVIS=true
RAWJSONVIS=true
PAOHVIS=true
MATRIXVIS=true
SEMANTICSUBSTRATESVIS=true
MAPVIS=true
VIS0D=true
VIS1D=true
INSIGHT_SHARING=true
VIEWER_PERMISSIONS=true
SHARABLE_EXPLORATION=true
......@@ -11,14 +11,21 @@ SENTRY_URL=
GP_AUTH_URL=
WIP_TABLEVIS=false
WIP_NODELINKVIS=false
WIP_RAWJSONVIS=false
WIP_PAOHVIS=true
WIP_MATRIXVIS=true
WIP_SEMANTICSUBSTRATESVIS=true
WIP_MAPVIS=true
LINK_PREDICTION=true
CENTRALITY=true
COMMUNITY_DETECTION=true
SHORTEST_PATH=true
WIP_INSIGHT_SHARING=true
WIP_VIEWER_PERMISSIONS=true
WIP_SHARABLE_EXPLORATION=true
\ No newline at end of file
TABLEVIS=true
NODELINKVIS=true
RAWJSONVIS=true
PAOHVIS=true
MATRIXVIS=true
SEMANTICSUBSTRATESVIS=true
MAPVIS=true
VIS0D=true
VIS1D=true
INSIGHT_SHARING=true
VIEWER_PERMISSIONS=true
SHARABLE_EXPLORATION=true
......@@ -10,14 +10,21 @@ SENTRY_URL=
GP_AUTH_URL=
WIP_TABLEVIS=false
WIP_NODELINKVIS=false
WIP_RAWJSONVIS=false
WIP_PAOHVIS=true
WIP_MATRIXVIS=true
WIP_SEMANTICSUBSTRATESVIS=true
WIP_MAPVIS=true
LINK_PREDICTION=true
CENTRALITY=true
COMMUNITY_DETECTION=true
SHORTEST_PATH=true
WIP_INSIGHT_SHARING=true
WIP_VIEWER_PERMISSIONS=true
WIP_SHARABLE_EXPLORATION=true
\ No newline at end of file
TABLEVIS=true
NODELINKVIS=true
RAWJSONVIS=true
PAOHVIS=true
MATRIXVIS=true
SEMANTICSUBSTRATESVIS=true
MAPVIS=true
VIS0D=true
VIS1D=true
INSIGHT_SHARING=true
VIEWER_PERMISSIONS=true
SHARABLE_EXPLORATION=true
......@@ -12,14 +12,21 @@ SENTRY_URL=
GP_AUTH_URL=https://auth.staging.graphpolaris.com/
WIP_TABLEVIS=false
WIP_NODELINKVIS=false
WIP_RAWJSONVIS=false
WIP_PAOHVIS=true
WIP_MATRIXVIS=true
WIP_SEMANTICSUBSTRATESVIS=true
WIP_MAPVIS=true
LINK_PREDICTION=true
CENTRALITY=true
COMMUNITY_DETECTION=true
SHORTEST_PATH=true
WIP_INSIGHT_SHARING=true
WIP_VIEWER_PERMISSIONS=true
WIP_SHARABLE_EXPLORATION=true
\ No newline at end of file
TABLEVIS=true
NODELINKVIS=true
RAWJSONVIS=true
PAOHVIS=false
MATRIXVIS=false
SEMANTICSUBSTRATESVIS=false
MAPVIS=false
VIS0D=false
VIS1D=false
INSIGHT_SHARING=false
VIEWER_PERMISSIONS=false
SHARABLE_EXPLORATION=false
......@@ -6,7 +6,6 @@ import {
useQuerybuilderGraph,
useQuerybuilderSettings,
useSessionCache,
useQuerybuilderUnionTypes,
} from '@graphpolaris/shared/lib/data-access';
import { addError, setCurrentTheme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { resetGraphQueryResults, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
......@@ -39,7 +38,6 @@ export function App(props: App) {
const dispatch = useAppDispatch();
const queryBuilderSettings = useQuerybuilderSettings();
const [monitoringOpen, setMonitoringOpen] = useState<boolean>(false);
const unionTypes = useQuerybuilderUnionTypes();
const runQuery = () => {
if (session?.currentSaveState && query) {
......@@ -47,7 +45,7 @@ export function App(props: App) {
dispatch(resetGraphQueryResults());
} else {
dispatch(queryingBackend());
wsQueryRequest(Query2BackendQuery(session.currentSaveState, query, queryBuilderSettings, ml, unionTypes));
wsQueryRequest(Query2BackendQuery(session.currentSaveState, query, queryBuilderSettings, ml, queryBuilderSettings.unionTypes));
}
}
};
......
......@@ -13,11 +13,10 @@ 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 { FeatureEnabled } from '@graphpolaris/shared/lib/components/featureFlags';
import { Button, useActiveSaveStateAuthorization } from '@graphpolaris/shared';
import { ManagementTrigger, ManagementViews } from '@graphpolaris/shared/lib/management';
const AuthURL = import.meta.env.GP_AUTH_URL;
import { getEnvVariable } from 'config';
export const Navbar = () => {
const dropdownRef = useRef<HTMLDivElement>(null);
......@@ -25,7 +24,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');
......@@ -67,28 +66,30 @@ export const Navbar = () => {
</div>
{authCache.authentication?.authenticated ? (
<>
{showSharableExploration() && (
<FeatureEnabled featureFlag="SHARABLE_EXPLORATION">
<DropdownItem
value="Share"
onClick={() => {
auth.newShareRoom();
}}
/>
)}
{authCache.authorization?.savestate?.W && authorization.database.W && (
<DropdownItem
value="Viewer Permissions"
onClick={() => {
setManagementOpen(true);
setCurrent('members');
}}
/>
)}
</FeatureEnabled>
<FeatureEnabled featureFlag="VIEWER_PERMISSIONS">
{authCache.authorization?.savestate?.W && authorization.database.W && (
<DropdownItem
value="Viewer Permissions"
onClick={() => {
setManagementOpen(true);
setCurrent('members');
}}
/>
)}
</FeatureEnabled>
<DropdownItem value="Settings" onClick={() => {}} />
<DropdownItem
value="Log out"
onClick={() => {
location.replace(`${AuthURL}/outpost.goauthentik.io/sign_out`);
location.replace(`${getEnvVariable('GP_AUTH_URL')}/outpost.goauthentik.io/sign_out`);
}}
/>
</>
......@@ -97,6 +98,7 @@ export const Navbar = () => {
<DropdownItem value="Login" onClick={() => {}} />
</>
)}
{authCache.authentication?.roomID && (
<div className="p-2 border-b">
<h3 className="text-xs break-words">Share ID: {authCache.authentication?.roomID}</h3>
......
......@@ -7,10 +7,11 @@ import App from './app/App';
import { createRoot } from 'react-dom/client';
import './main.css';
import { ErrorBoundary } from '@graphpolaris/shared/lib/components/errorBoundary';
import { getEnvVariable } from 'config';
if (import.meta.env.SENTRY_ENABLED) {
if (getEnvVariable('SENTRY_ENABLED') === 'true') {
Sentry.init({
dsn: import.meta.env.SENTRY_URL,
dsn: getEnvVariable('SENTRY_URL'),
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
......
......@@ -45,7 +45,12 @@
"postcss.config.js", // excludes PostCSS configuration file
"tsconfig.tsbuildinfo" // excludes TypeScript build info file
],
"include": ["vite.config.ts", "src/**/*", "../../libs/shared/lib/error-boundary"],
"include": [
"vite.config.ts",
"src/**/*",
"../../libs/shared/lib/error-boundary",
"../../libs/shared/lib/components/featureFlags/featureFlags.ts"
],
"files": ["vite.config.ts"],
"references": []
}
/// <reference types="vite/client" />
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,
SENTRY_ENABLED: import.meta.env.SENTRY_ENABLED,
SENTRY_URL: import.meta.env.SENTRY_URL,
GP_AUTH_URL: import.meta.env.GP_AUTH_URL,
LINK_PREDICTION: import.meta.env.LINK_PREDICTION,
CENTRALITY: import.meta.env.CENTRALITY,
COMMUNITY_DETECTION: import.meta.env.COMMUNITY_DETECTION,
SHORTEST_PATH: import.meta.env.SHORTEST_PATH,
TABLEVIS: import.meta.env.TABLEVIS,
NODELINKVIS: import.meta.env.NODELINKVIS,
RAWJSONVIS: import.meta.env.RAWJSONVIS,
PAOHVIS: import.meta.env.PAOHVIS,
MATRIXVIS: import.meta.env.MATRIXVIS,
SEMANTICSUBSTRATESVIS: import.meta.env.SEMANTICSUBSTRATESVIS,
MAPVIS: import.meta.env.MAPVIS,
VIS0D: import.meta.env.VIS0D,
VIS1D: import.meta.env.VIS1D,
INSIGHT_SHARING: import.meta.env.INSIGHT_SHARING,
VIEWER_PERMISSIONS: import.meta.env.VIEWER_PERMISSIONS,
SHARABLE_EXPLORATION: import.meta.env.SHARABLE_EXPLORATION,
};
type EnvVarKey = keyof typeof envVar;
function getEnvVariable(key: EnvVarKey): string | undefined {
const value = envVar[key];
if (value === undefined) {
console.error(`Environment variable ${key} is not defined`);
return;
}
return value;
}
export { getEnvVariable };
export type { EnvVarKey };
// Safely retrieve environment variable values with a default fallback
const getEnvVariable = (key: string, defaultValue: string = 'false'): string => {
return import.meta.env[key] ?? defaultValue;
};
// Check if the environment is production
const isProduction = (): boolean => {
return getEnvVariable('GRAPHPOLARIS_VERSION', 'dev') === 'prod';
};
// Check if the Manage Permissions feature is enabled
const showManagePermissions = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_VIEWER_PERMISSIONS') === 'false');
};
// Check if the Insight Sharing feature is enabled
const showInsightSharing = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_INSIGHT_SHARING') === 'false');
};
// Check if the Insight Sharing feature is enabled
const showSharableExploration = (): boolean => {
return !isProduction() || (isProduction() && getEnvVariable('WIP_SHARABLE_EXPLORATION') === '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');
};
export { isProduction, showManagePermissions, showInsightSharing, showSharableExploration, isVisualizationReleased };
export * from './colors';
export * from './featureFlags';
export * from './envVariables';
......@@ -20,7 +20,7 @@
}
padding: 0.25em 0.75em;
span {
overflow: hidden;
overflow: visible;
text-overflow: ellipsis;
line-height: 1em;
}
......@@ -144,3 +144,9 @@
.btn-rounded {
@apply rounded-full;
}
.exported {
span {
transform: translate(0px, -8px);
}
}
\ No newline at end of file
......@@ -16,5 +16,6 @@ declare const classNames: {
readonly 'btn-ghost': 'btn-ghost';
readonly 'btn-block': 'btn-block';
readonly 'btn-rounded': 'btn-rounded';
readonly exported: 'exported';
};
export = classNames;
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { Button } from '../buttons';
import { useAppDispatch } from '@graphpolaris/shared/lib/data-access';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
dispatch?: ReturnType<typeof useAppDispatch>;
}
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
errorInfo?: ErrorInfo;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
this.handleRetry = this.handleRetry.bind(this);
this.handleCopyError = this.handleCopyError.bind(this);
}
static getDerivedStateFromError(): ErrorBoundaryState {
......@@ -22,20 +29,48 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState({ error, errorInfo });
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}
handleRetry() {
this.setState({ hasError: false });
this.setState({ hasError: false, error: undefined, errorInfo: undefined });
}
handleCopyError() {
const errorText = `Error: ${this.state.error?.toString()}\n\nStack trace: ${this.state.errorInfo?.componentStack}`;
navigator.clipboard.writeText(errorText);
if (this.props.dispatch) {
this.props.dispatch(addInfo('Stack trace copied to clipboard'));
}
}
render() {
if (this.state.hasError) {
return (
<div>
<div className="flex flex-col w-full h-full">
{this.props.fallback || <div>Something went wrong. Please try again later.</div>}
{import.meta.env.GRAPHPOLARIS_VERSION === 'dev' && (
<div className="overflow-auto max-h-[500px] p-2">
<div className="flex justify-end mb-2">
<Button
label="Copy Stack Trace"
variant="outline"
variantType="secondary"
size="xs"
onClick={this.handleCopyError}
/>
</div>
<pre className="whitespace-pre-wrap break-words text-sm">
{this.state.error?.toString()}
</pre>
<pre className="whitespace-pre-wrap break-words text-sm mt-2">
{this.state.errorInfo?.componentStack}
</pre>
</div>
)}
<Button label="Retry now" variant="outline" variantType="primary" onClick={this.handleRetry} />
</div>
);
......@@ -45,4 +80,9 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
}
}
export { ErrorBoundary };
function ErrorBoundaryWithDispatch(props: Omit<ErrorBoundaryProps, 'dispatch'>) {
const dispatch = useAppDispatch();
return <ErrorBoundary {...props} dispatch={dispatch} />;
}
export { ErrorBoundaryWithDispatch as ErrorBoundary };
\ No newline at end of file
import { canViewFeature, FeatureFlagName } from '.';
import { ReactNode } from 'react';
export function FeatureEnabled({ featureFlag, children }: { featureFlag: FeatureFlagName; children: ReactNode }) {
return canViewFeature(featureFlag) ? children : null;
}
import { getEnvVariable, EnvVarKey } from 'config';
const getFeatureVariable = (flagId: EnvVarKey): boolean => {
// TODO: move feature flag storage to database instead of env variables
const value = getEnvVariable(flagId);
if (value === undefined) {
return false;
}
return String(value) === 'true';
};
export const ML_FLAGS = {
LINK_PREDICTION: getFeatureVariable('LINK_PREDICTION'),
CENTRALITY: getFeatureVariable('CENTRALITY'),
COMMUNITY_DETECTION: getFeatureVariable('COMMUNITY_DETECTION'),
SHORTEST_PATH: getFeatureVariable('SHORTEST_PATH'),
};
export const VISUALIZATIONS_FLAGS = {
RAWJSONVIS: getFeatureVariable('RAWJSONVIS'),
NODELINKVIS: getFeatureVariable('NODELINKVIS'),
TABLEVIS: getFeatureVariable('TABLEVIS'),
PAOHVIS: getFeatureVariable('PAOHVIS'),
MATRIXVIS: getFeatureVariable('MATRIXVIS'),
SEMANTICSUBSTRATESVIS: getFeatureVariable('SEMANTICSUBSTRATESVIS'),
MAPVIS: getFeatureVariable('MAPVIS'),
VIS0D: getFeatureVariable('VIS0D'),
VIS1D: getFeatureVariable('VIS1D'),
};
export const FEATURE_FLAGS = {
...ML_FLAGS,
...VISUALIZATIONS_FLAGS,
INSIGHT_SHARING: getFeatureVariable('INSIGHT_SHARING'),
VIEWER_PERMISSIONS: getFeatureVariable('VIEWER_PERMISSIONS'),
SHARABLE_EXPLORATION: getFeatureVariable('SHARABLE_EXPLORATION'),
} as const satisfies Record<string, boolean>;
export type FeatureFlagName = keyof typeof FEATURE_FLAGS;
const canViewFeature = (flagId: FeatureFlagName): boolean => {
return FEATURE_FLAGS[flagId];
};
export { canViewFeature };
export * from './featureFlags';
export * from './FeatureEnabled';
......@@ -36,15 +36,18 @@ export const Pagination: React.FC<PaginationProps> = ({
}
};
const backIcon = arrangement === 'vertical' ? 'icon-[ic--baseline-arrow-upward]' : 'icon-[ic--baseline-arrow-back]';
const forwardIcon = arrangement === 'vertical' ? 'icon-[ic--baseline-arrow-downward]' : 'icon-[ic--baseline-arrow-forward]';
const backIcon = <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/>
</svg>
const forwardIcon = <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/>
</svg>
return (
<div
className={`self-center table-pagination flex flex-col items-center py-2 gap-1.5 ${arrangement === 'vertical' ? 'flex-col' : 'flex-row'} ${className}`}
>
<div className={`self-center table-pagination flex flex-col items-center py-2 gap-1.5 ${className}`}>
<div className="inline-block text-sm">
<span className="font-semibold">{`${firstItem} - ${numItemsArrayReal}`}</span> of {totalItems}
<span className="font-semibold">{`${firstItem} - ${firstItem + numItemsArrayReal - 1}`}</span>
<span className="ml-1">of {totalItems}</span>
</div>
<div className={`grid ${arrangement === 'vertical' ? 'grid-rows-3' : 'grid-cols-2'} gap-2`}>
<Button
......
......@@ -64,9 +64,9 @@ export const TableUI = <T extends Record<string, any>>({ data, fieldConfigs, dro
return (
<div className="mt-2 w-full overflow-x-auto">
<table className="min-w-full bg-white border border-gray-300 rounded-md">
<table className="min-w-full bg-light border border-secondary-300 rounded-md">
<thead>
<tr className="bg-gray-100 border-b">
<tr className="bg-secondary-100 border-b">
{fieldConfigs.map((field) => (
<th key={field.key.toString()} className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
{field.label}
......@@ -79,7 +79,7 @@ export const TableUI = <T extends Record<string, any>>({ data, fieldConfigs, dro
{data.map((item, index) => (
<tr key={index} className="border-b rounded-md">
{fieldConfigs.map((field) => (
<td key={field.key.toString()} className="px-6 py-4 text-sm text-gray-700">
<td key={field.key.toString()} className="px-6 py-4 text-sm text-light-700">
{editingIndex === index ? (
field.type === 'dropdown' ? (
<Input
......@@ -103,7 +103,7 @@ export const TableUI = <T extends Record<string, any>>({ data, fieldConfigs, dro
)}
</td>
))}
<td className="px-6 py-4 text-sm text-gray-700">
<td className="px-6 py-4 text-sm text-secondary-700">
<div className="flex space-x-2">
{editingIndex === index ? (
<>
......
......@@ -14,14 +14,12 @@ import {
wsSchemaSubscription,
useQuerybuilderAttributesShown,
wsSchemaStatsRequest,
useQuerybuilderUnionTypes,
} from '@graphpolaris/shared/lib/data-access';
import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { allMLTypes, LinkPredictionInstance, setMLResult } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
import {
QueryBuilderText,
queryUnionTypes,
setQueryText,
setQuerybuilderNodes,
} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
......@@ -70,7 +68,6 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
const mlHash = useMLEnabledHash();
const visState = useVisualization();
const queryBuilderSettings = useQuerybuilderSettings();
const unionTypes = useQuerybuilderUnionTypes();
function loadSaveState(saveStateID: string | undefined, saveStates: Record<string, SaveStateI>) {
if (saveStateID && saveStates && saveStateID in saveStates) {
......@@ -291,7 +288,7 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function }
// body: { type: 'query_builder_state', status: '', value: queryBuilder },
// });
}
}, [queryHash, mlHash, queryBuilderSettings, unionTypes]);
}, [queryHash, mlHash, queryBuilderSettings, queryBuilderSettings.unionTypes]);
return <div className="hide"></div>;
};