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 (17)
Showing
with 151 additions and 29 deletions
......@@ -7,3 +7,14 @@ include:
- project: 'graphpolaris/pipelines'
ref: main
file: 'docker-build-push.yml'
patch-and-commit--staging-azure:
extends: patch-and-commit
variables:
IMAGE_TAG_HELM_FILE: "helm/staging-azure/graphpolaris-frontend/values.yaml"
IMAGE_TAG_HELM_FILE_VARIABLE: "image.tag"
needs:
# Make sure we get veriables from previous steps
- !reference [patch-and-commit, needs]
# Run sequentially to avoid race conditions
- patch-and-commit
\ No newline at end of file
......@@ -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',
......
......@@ -6,6 +6,7 @@ import { store } from '@graphpolaris/shared/lib/data-access/store';
import App from './app/App';
import { createRoot } from 'react-dom/client';
import './main.css';
import { ErrorBoundary } from '@graphpolaris/shared/lib/components/errorBoundary';
if (import.meta.env.SENTRY_ENABLED) {
Sentry.init({
......@@ -25,10 +26,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>,
);
......
......@@ -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>
);
};
......@@ -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}
/>
......
......@@ -29,7 +29,8 @@ export type SchemaAttributeTypes =
| 'datetime'
| 'duration'
| 'number'
| 'location';
| 'location'
| 'array';
export type DimensionType = 'categorical' | 'numerical' | 'temporal' | 'spatial';
......
......@@ -276,6 +276,7 @@ export const Schema = (props: Props) => {
return (
<Panel
title="Schema"
className="schema-panel"
tooltips={
<>
<Tooltip>
......@@ -351,7 +352,7 @@ export const Schema = (props: Props) => {
</>
}
>
<div className="schema-panel w-full h-full flex flex-col justify-between" ref={reactFlowRef}>
<div className="w-full h-full flex flex-col justify-between" ref={reactFlowRef}>
{schema.loading ? (
<div className="h-full flex flex-col items-center justify-center">
<Icon component="icon-[mingcute--loading-line]" size={56} className="w-15 h-15 animate-spin " />
......
......@@ -7,12 +7,17 @@ export type SideNavTab = 'Schema' | 'Search' | undefined;
const tabs: Array<{
name: SideNavTab;
icon: string | undefined;
className?: string;
}> = [
{
name: 'Search',
icon: 'icon-[ic--outline-search]',
className: 'searchbar',
},
{
name: 'Schema',
icon: 'icon-[ic--baseline-schema]',
},
{ name: 'Schema', icon: 'icon-[ic--baseline-schema]' },
];
export function Sidebar({
......@@ -46,7 +51,7 @@ export function Sidebar({
onTab(t.name);
}
}}
className={tab === t.name ? 'bg-secondary-100' : ''}
className={`${t.className || ''} ${tab === t.name ? 'bg-secondary-100' : ''}`}
/>
</TooltipTrigger>
<TooltipContent>{t.name}</TooltipContent>
......
......@@ -10,12 +10,14 @@ const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics
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 = getNodeLabel(node);
if (!metaData.nodes.labels.includes(nodeType)) {
......@@ -25,7 +27,6 @@ const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics
if (!metaData.nodes.types[nodeType]) {
metaData.nodes.types[nodeType] = { count: 0, attributes: {} };
}
metaData.nodes.types[nodeType].count++;
Object.entries(node.attributes).forEach(([attributeId, attributeValue]) => {
......@@ -34,7 +35,6 @@ const getGraphStatistics = (graph: GraphQueryResultFromBackend): GraphStatistics
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);
});
});
......