Skip to content

Commit f1144ad

Browse files
committed
feat: added single page for audit rows
1 parent 3eae3b1 commit f1144ad

52 files changed

Lines changed: 4456 additions & 2510 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

PRODUCT.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Product
2+
3+
## Register
4+
5+
product
6+
7+
## Users
8+
9+
Superposition Embeddable UI is used by developers, platform operators, product engineers, and workspace administrators who need to inspect and manage configuration, dimensions, overrides, and audit activity from within a host application.
10+
11+
## Product Purpose
12+
13+
The library provides embeddable admin surfaces for Superposition configuration management. Success means a host app can mount focused, trustworthy workflows that feel native to Juspay product tooling while remaining easy to scope, theme, and integrate.
14+
15+
## Brand Personality
16+
17+
Precise, composed, operational. The UI should feel like a dependable control surface for high-stakes configuration work, not a decorative dashboard.
18+
19+
## Anti-references
20+
21+
Avoid bespoke controls that diverge from Blend, marketing-style layouts, oversized empty states, heavy gradients, nested cards, and dense inline styling that makes the interface feel detached from the design system.
22+
23+
## Design Principles
24+
25+
- Blend first: use Blend components, tokens, states, and spacing as the primary interface vocabulary.
26+
- Keep operators in flow: filters, tables, forms, and actions should be predictable, compact, and quick to scan.
27+
- Make scope visible: workspace, boundary context, feature gates, and read-only limits should be clear without adding visual noise.
28+
- Favor structured detail: use consistent key-value rows, tags, and tables for configuration data instead of ad hoc text blocks.
29+
- Preserve host control: redesigns must keep the embeddable API, custom modal hooks, theming hooks, and scoped behavior intact.
30+
31+
## Accessibility & Inclusion
32+
33+
Target accessible defaults through Blend primitives, keyboard-friendly controls, visible focus states, readable contrast, and layouts that remain usable in constrained embedded containers.

__tests__/api/audit-logs.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ describe("auditLogsApi", () => {
4141
tables: ["contexts", "default_configs"],
4242
action: ["UPDATE"],
4343
username: "alice",
44+
dimension_params: {
45+
"dimension[cid]": "swiggy",
46+
"dimension[merchant_id]": "merchant_123",
47+
},
4448
sort_by: "asc",
4549
},
4650
);
@@ -52,6 +56,8 @@ describe("auditLogsApi", () => {
5256
expect(url).toContain("table=contexts,default_configs");
5357
expect(url).toContain("action=UPDATE");
5458
expect(url).toContain("username=alice");
59+
expect(url).toContain("dimension[cid]=swiggy");
60+
expect(url).toContain("dimension[merchant_id]=merchant_123");
5561
expect(url).toContain("sort_by=asc");
5662
expect(url).toContain("from_date=2024-05-01T00%3A00%3A00.000Z");
5763
expect(url).toContain("to_date=2024-05-02T23%3A59%3A59.999Z");

__tests__/api/resolve.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,19 @@ describe("resolveApi", () => {
2424
vi.restoreAllMocks();
2525
});
2626

27-
it("calls GET /config/resolve with dimension query params", async () => {
27+
it("calls POST /config/resolve with context in the request body", async () => {
2828
const api = resolveApi(client);
2929

30-
await api.resolve({ region: "ap-south-1", city: "blr" }, "DEEP");
30+
await api.resolve({ region: "ap-south-1", city: "blr" }, "MERGE");
3131

3232
const [url, init] = mockFetch.mock.calls[0];
3333
expect(url).toBe(
34-
"https://superposition.test/config/resolve?dimension[region]=ap-south-1&dimension[city]=blr&merge_strategy=DEEP",
34+
"https://superposition.test/config/resolve?merge_strategy=MERGE",
35+
);
36+
expect(init.method).toBe("POST");
37+
expect(init.body).toBe(
38+
JSON.stringify({ context: { region: "ap-south-1", city: "blr" } }),
3539
);
36-
expect(init.method).toBe("GET");
37-
expect(init.body).toBeUndefined();
3840
});
3941

4042
it("calls GET /config for the cached config contract", async () => {

__tests__/components/Table.test.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { describe, it, expect, vi } from "vitest";
22
import { render, screen, fireEvent } from "@testing-library/react";
3+
import userEvent from "@testing-library/user-event";
4+
import type { FormEvent } from "react";
35
import { resolveTableSerialNumberProps, Table } from "../../src/components/Table";
6+
import { SuperpositionUIProvider } from "../../src/providers/SuperpositionUIProvider";
47

58
interface Row {
69
id: string;
@@ -91,6 +94,116 @@ describe("Table", () => {
9194
expect(screen.getByText("11").style.textAlign).toBe("left");
9295
});
9396

97+
it("keeps Blend table controls from submitting host forms", async () => {
98+
const user = userEvent.setup();
99+
const onSubmit = vi.fn((event: FormEvent<HTMLFormElement>) => {
100+
event.preventDefault();
101+
});
102+
const onPageSizeChange = vi.fn();
103+
104+
render(
105+
<form onSubmit={onSubmit}>
106+
<SuperpositionUIProvider
107+
config={{ apiBaseUrl: "/api", orgId: "org", workspace: "ws" }}
108+
>
109+
<Table
110+
columns={columns}
111+
data={data}
112+
keyExtractor={(row) => row.id}
113+
pagination={{
114+
currentPage: 1,
115+
pageSize: 10,
116+
totalRows: 25,
117+
pageSizeOptions: [10, 20],
118+
}}
119+
onPageChange={vi.fn()}
120+
onPageSizeChange={onPageSizeChange}
121+
showSettings
122+
enableColumnManager
123+
columnManagerAlwaysSelected={["name"]}
124+
/>
125+
</SuperpositionUIProvider>
126+
</form>,
127+
);
128+
129+
await user.click(screen.getByRole("button", { name: "Manage columns" }));
130+
await user.click(
131+
screen.getByRole("button", { name: "Select number of rows per page" }),
132+
);
133+
await user.click(await screen.findByText("20"));
134+
135+
expect(onSubmit).not.toHaveBeenCalled();
136+
expect(onPageSizeChange).toHaveBeenCalledWith(20);
137+
});
138+
139+
it("keeps rows-per-page warning-free on narrow screens", async () => {
140+
const user = userEvent.setup();
141+
const onSubmit = vi.fn((event: FormEvent<HTMLFormElement>) => {
142+
event.preventDefault();
143+
});
144+
const onPageSizeChange = vi.fn();
145+
const originalInnerWidth = window.innerWidth;
146+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
147+
148+
Object.defineProperty(window, "innerWidth", {
149+
configurable: true,
150+
writable: true,
151+
value: 500,
152+
});
153+
window.dispatchEvent(new Event("resize"));
154+
155+
try {
156+
render(
157+
<form onSubmit={onSubmit}>
158+
<SuperpositionUIProvider
159+
config={{ apiBaseUrl: "/api", orgId: "org", workspace: "ws" }}
160+
>
161+
<Table
162+
columns={columns}
163+
data={data}
164+
keyExtractor={(row) => row.id}
165+
pagination={{
166+
currentPage: 1,
167+
pageSize: 10,
168+
totalRows: 25,
169+
pageSizeOptions: [10, 20],
170+
}}
171+
onPageChange={vi.fn()}
172+
onPageSizeChange={onPageSizeChange}
173+
/>
174+
</SuperpositionUIProvider>
175+
</form>,
176+
);
177+
178+
await user.click(
179+
screen.getByRole("button", { name: "Select number of rows per page" }),
180+
);
181+
await user.click(await screen.findByText("20 / page"));
182+
183+
const consoleMessages = consoleErrorSpy.mock.calls
184+
.flat()
185+
.map((message) => String(message));
186+
187+
expect(onSubmit).not.toHaveBeenCalled();
188+
expect(onPageSizeChange).toHaveBeenCalledWith(20);
189+
expect(
190+
consoleMessages.some(
191+
(message) =>
192+
message.includes("Function components cannot be given refs") ||
193+
message.includes("enableVirtualization"),
194+
),
195+
).toBe(false);
196+
} finally {
197+
consoleErrorSpy.mockRestore();
198+
Object.defineProperty(window, "innerWidth", {
199+
configurable: true,
200+
writable: true,
201+
value: originalInnerWidth,
202+
});
203+
window.dispatchEvent(new Event("resize"));
204+
}
205+
});
206+
94207
it("resolves serial number options from embeddable config", () => {
95208
expect(resolveTableSerialNumberProps({ serialNumber: true }, 21)).toEqual({
96209
showSerialNumber: true,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { render, screen } from "@testing-library/react";
2+
import { describe, expect, it } from "vitest";
3+
import { Tooltip } from "../../src/components/Tooltip";
4+
5+
describe("Tooltip", () => {
6+
it("applies aria-describedby to the interactive child", () => {
7+
render(
8+
<Tooltip content="Helpful details">
9+
<button type="button" aria-describedby="existing-description">
10+
Hover me
11+
</button>
12+
</Tooltip>,
13+
);
14+
15+
const trigger = screen.getByRole("button", { name: "Hover me" });
16+
const tooltip = screen.getByRole("tooltip", { name: "Helpful details" });
17+
18+
expect(trigger.getAttribute("aria-describedby")).toContain("existing-description");
19+
expect(trigger.getAttribute("aria-describedby")).toContain(tooltip.id);
20+
expect(trigger.parentElement?.getAttribute("aria-describedby")).toBeNull();
21+
});
22+
});

0 commit comments

Comments
 (0)