Skip to content
Draft
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
2 changes: 1 addition & 1 deletion apps/wfo-ui
Submodule wfo-ui updated 1 files
+2 −0 pages/_app.tsx
41,148 changes: 25,754 additions & 15,394 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/orchestrator-ui-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
"dev": "npm run build -- --watch"
},
"dependencies": {
"@copilotkit/react-core": "^1.10.2",
"@copilotkit/react-ui": "^1.10.2",
"@copilotkit/runtime": "^1.10.2",
"@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,229 @@
import {
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';

import { AnySearchParameters, Condition, Group } from '@/types';

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

const isCondition = (item: Group | Condition): item is Condition => {
return 'path' in item && 'condition' in item;
};

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 wrapCss = css({
display: 'flex',
flexWrap: 'wrap',
gap: euiTheme.size.s,
});

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

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

const groupCss = css({
border: `1px solid ${euiTheme.colors.lightShade}`,
borderRadius: euiTheme.border.radius.medium,
padding: euiTheme.size.s,
margin: euiTheme.size.xs,
background: euiTheme.colors.emptyShade,
});

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

const crumbCss = css({
display: 'inline-flex',
alignItems: 'center',
color: euiTheme.colors.subduedText,
'> span + span': {
display: 'inline-flex',
alignItems: 'center',
marginLeft: euiTheme.size.xs,
'::before': {
content: '""',
display: 'inline-block',
marginRight: euiTheme.size.xs,
},
},
});

const opCss = css({
fontFamily: euiTheme.font.familyCode,
padding: `0 ${euiTheme.size.s}`,
borderRadius: euiTheme.size.s,
background: euiTheme.colors.emptyShade,
color: euiTheme.colors.text,
});

const valueCss = css({
fontWeight: 600,
color: euiTheme.colors.warning,
});

const sectionTitle = (text: string) => (
<EuiText size="xs" color="subdued">

Check failure on line 107 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
<strong>{text}</strong>

Check failure on line 108 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
</EuiText>
);

const formatFilterValue = (condition: Condition['condition']): string => {
if ('value' in condition) {
if (
condition.op === 'between' &&
condition.value &&
typeof condition.value === 'object'
) {
const { start, end, from, to } = condition.value as any;

Check failure on line 119 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

Unexpected any. Specify a different type
const fromVal = start || from;
const toVal = end || to;
return `${fromVal} … ${toVal}`;
}
return String(condition.value);
}
return '—';
};

const renderPathCrumbs = (path: string) => {
const parts = path.split('.');
return (
<span css={crumbCss} title={`Full path: ${path}`}>

Check failure on line 132 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
{parts.map((p, i) => (
<span key={`${p}-${i}`}>

Check failure on line 134 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
{i > 0 && (
<EuiIcon

Check failure on line 136 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
type="sortRight"
size="s"
css={css({
marginRight: euiTheme.size.xs,
color: euiTheme.colors.subduedText,
})}
/>
)}
{p}
</span>
))}
</span>
);
};

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

Check failure on line 155 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
<em>Empty group</em>

Check failure on line 156 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
</EuiText>
);
}

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

return (
<div css={groupCss} style={{ marginLeft: depth * 16 }}>

Check failure on line 165 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
<div css={operatorCss}>{group.op}</div>

Check failure on line 166 in packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay.tsx

View workflow job for this annotation

GitHub Actions / linting-and-prettier

'React' must be in scope when using JSX
<div css={areChildrenGroups ? columnGroupWrapCss : wrapCss}>
{group.children.map((child, i) => (
<div key={i}>
{isCondition(child) ? (
<div css={chipCss}>
{renderPathCrumbs(child.path)}
<span css={opCss}>
{child.condition.op}
</span>
<span css={valueCss}>
{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>
);
}
Loading
Loading