mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-12 01:20:29 +01:00
chore: sync up with group mappers
This commit is contained in:
@@ -41,8 +41,6 @@ async function expandGroup(
|
||||
await user.click(screen.getByTestId(`group-expand-${groupId}`));
|
||||
}
|
||||
|
||||
// DropdownMenuSimple drops the trigger's testId, so the kebab is reached via
|
||||
// its accessible name instead.
|
||||
async function openGroupActionsMenu(
|
||||
user: ReturnType<typeof userEvent.setup>,
|
||||
groupId: string,
|
||||
@@ -53,18 +51,18 @@ async function openGroupActionsMenu(
|
||||
await user.click(within(row).getByRole('button', { name: 'Group actions' }));
|
||||
}
|
||||
|
||||
interface HarnessProps {
|
||||
interface AttributeMappingsTabWithStoreProps {
|
||||
onEditGroup?: (group: DraftGroup) => void;
|
||||
onAddGroup?: () => void;
|
||||
}
|
||||
|
||||
// The tab is a presentational view over a store owned by the container, so the
|
||||
// harness creates the store (via the hook, backed by the mocked API) and wires
|
||||
// The tab is a presentational view over a store owned by the container, so this
|
||||
// wrapper creates the store (via the hook, backed by the mocked API) and wires
|
||||
// the edit/add callbacks the tab needs.
|
||||
function AttributeMappingHarness({
|
||||
function AttributeMappingsTabWithStore({
|
||||
onEditGroup,
|
||||
onAddGroup,
|
||||
}: HarnessProps): JSX.Element {
|
||||
}: AttributeMappingsTabWithStoreProps): JSX.Element {
|
||||
const store = useAttributeMappingStore();
|
||||
return (
|
||||
<AttributeMappingsTab
|
||||
@@ -87,7 +85,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
|
||||
it('renders no error banner on a successful load', async () => {
|
||||
setupGroups();
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('group-name-group-1')).toBeInTheDocument(),
|
||||
@@ -99,7 +97,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
server.use(
|
||||
rest.get(GROUPS_ENDPOINT, (_req, res, ctx) => res(ctx.status(500))),
|
||||
);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await expect(screen.findByRole('alert')).resolves.toHaveTextContent(
|
||||
'Failed to load mapping groups. Please try again.',
|
||||
@@ -108,7 +106,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
|
||||
it('shows the empty state when there are no groups', async () => {
|
||||
setupGroups([]);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await expect(
|
||||
screen.findByTestId('mapper-groups-empty'),
|
||||
@@ -117,7 +115,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
|
||||
it('renders each group header row with its name, condition count and status', async () => {
|
||||
setupGroups();
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
// Condition filters are no longer shown inline as clauses — the header
|
||||
// carries a count instead (the keys surface in the group drawer).
|
||||
@@ -150,7 +148,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
it("stages a toggle of the group's enabled state via the header switch", async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
setupGroups();
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
const toggle = await screen.findByTestId('group-enabled-group-1');
|
||||
expect(toggle).toBeChecked();
|
||||
@@ -164,7 +162,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onAddGroup = jest.fn();
|
||||
setupGroups();
|
||||
render(<AttributeMappingHarness onAddGroup={onAddGroup} />);
|
||||
render(<AttributeMappingsTabWithStore onAddGroup={onAddGroup} />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await user.click(screen.getByTestId('add-group-row'));
|
||||
@@ -176,7 +174,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onEditGroup = jest.fn();
|
||||
setupGroups();
|
||||
render(<AttributeMappingHarness onEditGroup={onEditGroup} />);
|
||||
render(<AttributeMappingsTabWithStore onEditGroup={onEditGroup} />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await openGroupActionsMenu(user, 'group-1');
|
||||
@@ -192,7 +190,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
it('stages a group removal when Delete is chosen from the actions menu', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
setupGroups();
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await openGroupActionsMenu(user, 'group-1');
|
||||
@@ -210,7 +208,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
setupGroups();
|
||||
setupMappers([makeMapper({ id: 'mapper-1' })]);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
// The toggle is the antd Collapse header, which owns the expanded state.
|
||||
@@ -239,7 +237,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
setupMappers([
|
||||
makeMapper({ id: 'mapper-1', name: 'gen_ai.request.model', enabled: true }),
|
||||
]);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
// Mappers are not fetched until the row is expanded.
|
||||
@@ -267,7 +265,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
setupGroups();
|
||||
setupMappers([makeMapper({ id: 'mapper-1', enabled: true })]);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await expandGroup(user);
|
||||
@@ -287,7 +285,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
res(ctx.status(500)),
|
||||
),
|
||||
);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await expandGroup(user);
|
||||
@@ -301,7 +299,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
setupGroups();
|
||||
setupMappers([]);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await expandGroup(user);
|
||||
@@ -327,7 +325,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
},
|
||||
}),
|
||||
]);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await expandGroup(user);
|
||||
@@ -339,7 +337,7 @@ describe('AttributeMappingsTab (integration)', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
setupGroups();
|
||||
setupMappers([makeMapper({ id: 'mapper-1', config: { sources: [] } })]);
|
||||
render(<AttributeMappingHarness />);
|
||||
render(<AttributeMappingsTabWithStore />);
|
||||
|
||||
await screen.findByTestId('group-name-group-1');
|
||||
await expandGroup(user);
|
||||
|
||||
@@ -11,10 +11,6 @@ interface GroupHeaderActionsProps {
|
||||
onRemove: (localId: string) => void;
|
||||
}
|
||||
|
||||
// The enable/disable toggle stays inline in the header (it's the primary,
|
||||
// high-frequency action); the lower-frequency Edit and Delete actions live
|
||||
// behind the kebab to keep the row compact. The wrapper swallows clicks so
|
||||
// they don't also toggle the Collapse panel.
|
||||
function GroupHeaderActions({
|
||||
group,
|
||||
onToggle,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { SpantypesSpanMapperDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { useListSpanMappers } from 'api/generated/services/spanmapper';
|
||||
import { motion, useReducedMotion } from 'motion/react';
|
||||
import { motion } from 'motion/react';
|
||||
|
||||
import {
|
||||
DraftGroup,
|
||||
@@ -15,25 +15,19 @@ import styles from './GroupMappers.module.scss';
|
||||
|
||||
const MAPPER_SKELETON_ROWS = 2;
|
||||
|
||||
// Fade shared by the non-row states (error / empty) so they reveal in step
|
||||
// with the mapper rows while the antd Collapse runs its height animation on
|
||||
// expand.
|
||||
const STATE_ROW_MOTION = {
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
transition: { duration: 0.18, ease: 'easeOut' },
|
||||
} as const;
|
||||
|
||||
type StateRowMotion = typeof STATE_ROW_MOTION | { initial: false };
|
||||
|
||||
interface StateRowProps {
|
||||
groupId: string;
|
||||
rowMotion: StateRowMotion;
|
||||
}
|
||||
|
||||
function ErrorRow({ groupId, rowMotion }: StateRowProps): JSX.Element {
|
||||
function ErrorRow({ groupId }: StateRowProps): JSX.Element {
|
||||
return (
|
||||
<motion.tr className={styles.mapperStateRow} {...rowMotion}>
|
||||
<motion.tr className={styles.mapperStateRow} {...STATE_ROW_MOTION}>
|
||||
<td
|
||||
colSpan={COLUMN_COUNT}
|
||||
className={styles.stateCell}
|
||||
@@ -45,9 +39,9 @@ function ErrorRow({ groupId, rowMotion }: StateRowProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function EmptyRow({ groupId, rowMotion }: StateRowProps): JSX.Element {
|
||||
function EmptyRow({ groupId }: StateRowProps): JSX.Element {
|
||||
return (
|
||||
<motion.tr className={styles.mapperStateRow} {...rowMotion}>
|
||||
<motion.tr className={styles.mapperStateRow} {...STATE_ROW_MOTION}>
|
||||
<td
|
||||
colSpan={COLUMN_COUNT}
|
||||
className={styles.stateCell}
|
||||
@@ -64,11 +58,6 @@ interface GroupMappersProps {
|
||||
}
|
||||
|
||||
function GroupMappers({ group }: GroupMappersProps): JSX.Element {
|
||||
const prefersReducedMotion = useReducedMotion();
|
||||
const stateRowMotion: StateRowMotion = prefersReducedMotion
|
||||
? { initial: false as const }
|
||||
: STATE_ROW_MOTION;
|
||||
|
||||
// A not-yet-saved group has no serverId, so there is nothing to fetch —
|
||||
// the query stays disabled and the panel falls through to the empty row.
|
||||
const hasServerId = group.serverId !== null;
|
||||
@@ -92,18 +81,14 @@ function GroupMappers({ group }: GroupMappersProps): JSX.Element {
|
||||
|
||||
let rows: JSX.Element[];
|
||||
if (isError) {
|
||||
rows = [
|
||||
<ErrorRow key="error" groupId={group.localId} rowMotion={stateRowMotion} />,
|
||||
];
|
||||
rows = [<ErrorRow key="error" groupId={group.localId} />];
|
||||
} else if (isLoading) {
|
||||
rows = Array.from({ length: MAPPER_SKELETON_ROWS }).map((_, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<MapperRowSkeleton key={`mapper-skeleton-${index}`} />
|
||||
));
|
||||
} else if (mappers.length === 0) {
|
||||
rows = [
|
||||
<EmptyRow key="empty" groupId={group.localId} rowMotion={stateRowMotion} />,
|
||||
];
|
||||
rows = [<EmptyRow key="empty" groupId={group.localId} />];
|
||||
} else {
|
||||
rows = mappers.map((mapper, index) => (
|
||||
<MapperRow key={mapper.id} mapper={mapper} index={index} />
|
||||
|
||||
@@ -84,16 +84,12 @@ export function buildPostableGroup(
|
||||
};
|
||||
}
|
||||
|
||||
// A full group payload is also a valid partial-update payload (all updatable
|
||||
// fields are present), so we reuse the postable builder.
|
||||
export function buildUpdatableGroup(
|
||||
draft: GroupDraft,
|
||||
): SpantypesUpdatableSpanMapperGroupDTO {
|
||||
return buildPostableGroup(draft);
|
||||
}
|
||||
|
||||
// ---- working-copy (draft list) helpers ----
|
||||
|
||||
export function buildDraftGroup(group: MapperGroup): DraftGroup {
|
||||
return {
|
||||
localId: group.id,
|
||||
@@ -105,7 +101,6 @@ export function buildDraftGroup(group: MapperGroup): DraftGroup {
|
||||
};
|
||||
}
|
||||
|
||||
// DraftGroup -> editable form state (id carries the localId).
|
||||
export function groupDraftFromNode(group: DraftGroup): GroupDraft {
|
||||
return {
|
||||
id: group.localId,
|
||||
@@ -116,8 +111,6 @@ export function groupDraftFromNode(group: DraftGroup): GroupDraft {
|
||||
};
|
||||
}
|
||||
|
||||
// Form state -> working-copy node. Reuses cleanKeys so the staged list already
|
||||
// holds normalized values.
|
||||
export function nodeFromGroupDraft(
|
||||
draft: GroupDraft,
|
||||
existing?: DraftGroup,
|
||||
|
||||
Reference in New Issue
Block a user