Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30,218 changes: 15,897 additions & 14,321 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* global module, jest */
module.exports = {
useCoAgent: jest.fn(() => ({
isLoading: false,
error: null,
run: jest.fn(),
result: null,
})),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* global module, require */
const React = require('react');

module.exports = {
CopilotSidebar: ({ children }) =>
React.createElement(
'div',
{ 'data-testid': 'copilot-sidebar' },
children,
),
};
4 changes: 4 additions & 0 deletions packages/orchestrator-ui-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
"dev": "npm run build -- --watch"
},
"dependencies": {
"@ag-ui/client": "^0.0.37",
"@copilotkit/react-core": "^1.3.18",
"@copilotkit/react-ui": "^1.3.18",
"@copilotkit/runtime": "^1.3.18",
"@elastic/eui": "101.3.0",
"@emotion/css": "^11.11.2",
"@emotion/react": "^11.11.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { UseEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';

export const getFilterDisplayStyles = (euiTheme: UseEuiTheme['euiTheme']) => ({
wrap: css({
display: 'flex',
flexWrap: 'wrap',
gap: euiTheme.size.s,
}),

columnGroupWrap: css({
display: 'flex',
flexDirection: 'column',
gap: euiTheme.size.s,
alignItems: 'flex-start',
}),

chip: css({
display: 'inline-flex',
alignItems: 'center',
borderRadius: euiTheme.size.xl,
border: `1px solid ${euiTheme.border.color}`,
background: euiTheme.colors.body,
padding: `${euiTheme.size.s} ${euiTheme.size.m}`,
lineHeight: 1.1,
gap: euiTheme.size.s,
}),

group: css({
border: `1px solid ${euiTheme.colors.borderBaseSubdued}`,
borderRadius: euiTheme.border.radius.medium,
padding: euiTheme.size.s,
margin: euiTheme.size.xs,
background: euiTheme.colors.body,
}),

operator: css({
fontFamily: euiTheme.font.familyCode,
padding: `${euiTheme.size.xs} ${euiTheme.size.s}`,
borderRadius: euiTheme.size.s,
background: euiTheme.colors.primary,
color: euiTheme.colors.plainLight,
fontSize: euiTheme.size.m,
fontWeight: 'bold',
margin: `${euiTheme.size.xs} 0`,
}),

value: css({
fontWeight: 600,
color: euiTheme.colors.warning,
}),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React from 'react';

import {
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiSpacer,
EuiText,
useEuiTheme,
} from '@elastic/eui';

import { WfoPathBreadcrumb } from '@/components/WfoSearchPage/WfoSearchResults/WfoPathBreadcrumb';
import {
getOperatorDisplay,
isCondition,
} from '@/components/WfoSearchPage/utils';
import { AnySearchParameters, Condition, Group, PathDataType } from '@/types';

import { getFilterDisplayStyles } from './FilterDisplay.styles';

const DEPTH_INDENT = 16;

type FilterDisplayProps = {
parameters: {
action?: AnySearchParameters['action'] | string;
entity_type?: AnySearchParameters['entity_type'] | string;
filters?: Group | null;
query?: string | null;
};
};

interface BetweenValue {
start?: string | number;
end?: string | number;
from?: string | number;
to?: string | number;
}

export function FilterDisplay({ parameters }: FilterDisplayProps) {
const { euiTheme } = useEuiTheme();
const { action, entity_type, filters, query } = parameters ?? {};

if (!parameters || Object.keys(parameters).length === 0) return null;

const styles = getFilterDisplayStyles(euiTheme);

const sectionTitle = (text: string) => (
<EuiText size="xs" color="subdued">
<strong>{text}</strong>
</EuiText>
);

const formatFilterValue = (condition: Condition['condition']): string => {
if ('value' in condition && condition.value !== undefined) {
if (
condition.op === 'between' &&
condition.value &&
typeof condition.value === 'object'
) {
const { start, end, from, to } =
condition.value as BetweenValue;
const fromVal = start || from;
const toVal = end || to;
return `${fromVal} … ${toVal}`;
}
return String(condition.value);
}
return '—';
};

const renderFilterGroup = (group: Group, depth = 0): React.ReactNode => {
if (!group.children || group.children.length === 0) {
return (
<EuiText size="s" color="subdued">
<em>Empty group</em>
</EuiText>
);
}

const areChildrenGroups =
group.children.length > 0 && !isCondition(group.children[0]);

return (
<div
css={styles.group}
style={{ marginLeft: depth * DEPTH_INDENT }}
>
<div css={styles.operator}>{group.op}</div>
<div
css={
areChildrenGroups ? styles.columnGroupWrap : styles.wrap
}
>
{group.children.map((child, i) => (
<div key={i}>
{isCondition(child) ? (
<div css={styles.chip}>
<WfoPathBreadcrumb
path={child.path}
size="s"
showArrows={true}
/>
<EuiBadge color="hollow">
{
getOperatorDisplay(
child.condition.op,
child.value_kind
? {
path: child.path,
type: child.value_kind as PathDataType,
operators: [],
valueSchema: {},
group: 'leaf' as const,
}
: undefined,
).symbol
}
</EuiBadge>
<span css={styles.value}>
{formatFilterValue(child.condition)}
</span>
</div>
) : (
renderFilterGroup(child, depth + 1)
)}
</div>
))}
</div>
</div>
);
};

return (
<EuiPanel hasBorder paddingSize="m">
<EuiFlexGroup gutterSize="m" wrap responsive>
<EuiFlexItem grow={false}>
{sectionTitle('Action')}
<EuiSpacer size="xs" />
<EuiBadge color="hollow">{action || 'N/A'}</EuiBadge>
</EuiFlexItem>

<EuiFlexItem grow={false}>
{sectionTitle('Entity Type')}
<EuiSpacer size="xs" />
<EuiBadge color="hollow">{entity_type || 'N/A'}</EuiBadge>
</EuiFlexItem>

{query ? (
<EuiFlexItem grow={false}>
{sectionTitle('Search Query')}
<EuiSpacer size="xs" />
<EuiText size="s">
<em>"{query}"</em>
</EuiText>
</EuiFlexItem>
) : null}
</EuiFlexGroup>

<EuiSpacer size="m" />
{sectionTitle('Active Filters')}
<EuiSpacer size="s" />

{filters && filters.children && filters.children.length > 0 ? (
renderFilterGroup(filters)
) : (
<EuiText size="s" color="subdued">
<em>No filters applied.</em>
</EuiText>
)}
</EuiPanel>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './FilterDisplay';
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';

import { useCoAgent } from '@copilotkit/react-core';
import { CopilotSidebar } from '@copilotkit/react-ui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';

import { WfoSearchResults } from '@/components/WfoSearchPage/WfoSearchResults';
import { AnySearchParameters, AnySearchResult, PathFilter } from '@/types';

import { FilterDisplay } from '../FilterDisplay';

type SearchState = {
parameters: AnySearchParameters;
results: AnySearchResult[];
};

const initialState: SearchState = {
parameters: {
action: 'select',
entity_type: 'SUBSCRIPTION',
filters: [] as PathFilter[],
query: null,
},
results: [],
};

export function WfoAgent() {
const { state } = useCoAgent<SearchState>({
name: 'query_agent',
initialState,
});
const { parameters, results } = state;

const hasStarted = !!(
state.parameters &&
Array.isArray(state.parameters.filters) &&
state.parameters.filters.length > 0
);

const isLoadingResults =
hasStarted && (!state.results || state.results.length === 0);

const displayParameters = parameters && {
...parameters,
filters: Array.isArray(parameters.filters)
? { op: 'AND' as const, children: parameters.filters }
: parameters.filters,
};

return (
<EuiFlexGroup gutterSize="l" alignItems="stretch">
<EuiFlexItem grow={2}>
<EuiText>
<h1>Search results</h1>
</EuiText>

<EuiSpacer size="m" />
<EuiText size="s">
<h2>Filled parameters</h2>
</EuiText>
<EuiSpacer size="s" />
{displayParameters && (
<FilterDisplay parameters={displayParameters} />
)}

<EuiSpacer size="m" />
<EuiText size="s">
<h2>Results {results ? `(${results.length})` : ''}</h2>
</EuiText>
<EuiSpacer size="s" />

<WfoSearchResults
results={results ?? []}
loading={isLoadingResults}
selectedRecordIndex={-1}
onRecordSelect={() => {}}
/>
</EuiFlexItem>

<EuiFlexItem grow={1}>
<CopilotSidebar
defaultOpen
clickOutsideToClose={false}
labels={{
title: 'Database assistant',
initial:
'Ask me things such as:\n' +
'• *Find active subscriptions for Surf*\n' +
'• *Show terminated workflows”*\n\n' +
'The filled template and results will appear on the left.',
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './WfoAgent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './WfoAgent';
export * from './FilterDisplay';
Loading