Skip to content
Closed
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
13 changes: 10 additions & 3 deletions TESTING_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,21 @@ installation's agent). `browser/lib/src/plugin-route-grant.test.ts` checks
`installRelease` produces exactly that, signed by the installer; that a
`config:` target that doesn't resolve refuses before anything is committed;
that revoking takes the agent off the parent and leaves other writers; and
that an upgrade replaces the grant and moves the rights.
that an upgrade replaces the grant and moves the rights. A config edit on the
Installation page that moves a `config:` target (#1758): the same file checks
`routeWriteConfigChange` finds the moved targets and refuses an unresolved one,
and that `saveInstallationConfig` moves `write` to the new parent when
approved, drops the grant and the rights when declined, commits nothing for an
unresolved target and only saves when nothing moves.
`RouteWriteMoveDialog.test.tsx` checks the dialog marks the new parent New,
starts unchecked and saves with the choice.
`route_writes::http_tests::a_page_shaped_install_stores_a_post` installs that
fixture and POSTs (`2xx`). `RouteWriteApproval.test.tsx` covers the review:
unchecked by default, the targets passed only once approved, install disabled
while a target doesn't resolve, not offered below `read-write`, and an update
kept approved or asked again. Not covered: a Playwright run of install, POST
and revoke (done by hand for #1717's follow-up), and the rights following a
config edit on the Installation page.
and revoke (done by hand for #1717's follow-up), and a Playwright run of the
config edit (done by hand for #1758).

The data-browser no longer connects or syncs LocalThought platforms: that code
was removed, and plugins will run in their own iframe and make proxy calls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ import activitypub from '../../../../../testdata/plugin-manifest/v3-activitypub.

// The dialog's chrome needs a real <dialog> and the app's providers; what is
// under test is its content and its Install button.
const dialog = vi.hoisted(() => [
{},
() => undefined,
() => undefined,
true,
]);
const dialog = vi.hoisted(() => [{}, () => undefined, () => undefined, true]);

vi.mock('@components/Dialog', async original => {
const Pass = ({ children }: { children?: React.ReactNode }) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// @vitest-environment jsdom
// @wc-ignore-file
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { ThemeProvider } from 'styled-components';
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
grantsWithRouteWrites,
routeWriteConfigChange,
type DeclaredWriteTarget,
} from '@tomic/react';
import { buildTheme } from '../../styling';
import { RouteWriteMoveDialog } from './RouteWriteMoveDialog';
import inbox from '../../../../../testdata/plugin-routes/inbox/manifest.json';

const dialog = vi.hoisted(() => [{}, () => undefined, () => undefined, true]);

vi.mock('@components/Dialog', async original => {
const Pass = ({ children }: { children?: React.ReactNode }) => (
<div>{children}</div>
);

return {
...(await original<typeof import('@components/Dialog')>()),
Dialog: Object.assign(Pass, {
Title: Pass,
Content: Pass,
Actions: Pass,
}),
useDialog: () => dialog,
};
});
vi.mock('@views/ResourceInline', () => ({
ResourceInline: ({ subject }: { subject: string }) => <span>{subject}</span>,
}));

afterEach(cleanup);

// #1754's fixture: `inbox-items` under `config:inbox`.
const TARGETS = inbox.http.writeTargets as DeclaredWriteTarget[];
const OLD = 'https://atomic.test/inbox';
const NEW = 'https://atomic.test/elsewhere';

function renderMove() {
const change = routeWriteConfigChange(
grantsWithRouteWrites(['storage'], TARGETS),
{ inbox: OLD },
{ inbox: NEW },
)!;
const onSave = vi.fn(async (_approve: boolean) => undefined);
render(
<ThemeProvider theme={buildTheme(false, '#1a4fff')}>
<RouteWriteMoveDialog
plugin='fixtures/inbox'
change={change}
config={{ inbox: NEW }}
onSave={onSave}
onClose={() => undefined}
/>
</ThemeProvider>,
);

return onSave;
}

describe('moving a write target in the config', () => {
it('shows the new parent as New and asks for approval, unchecked', () => {
renderMove();

const target = screen.getByTestId('route-write-target-inbox-items');
expect(target.textContent).toContain(NEW);
expect(screen.getByTestId('route-write-new')).toBeTruthy();
expect(
(screen.getByTestId('route-write-approval') as HTMLInputElement).checked,
).toBe(false);
});

it('saves with the approval when it is given', async () => {
const onSave = renderMove();

fireEvent.click(screen.getByTestId('route-write-approval'));
fireEvent.click(screen.getByRole('button', { name: 'Save config' }));

await vi.waitFor(() => expect(onSave).toHaveBeenCalledWith(true));
});

it('saves declined when it is left unchecked', async () => {
const onSave = renderMove();

fireEvent.click(screen.getByRole('button', { name: 'Save config' }));

await vi.waitFor(() => expect(onSave).toHaveBeenCalledWith(false));
});
});
91 changes: 91 additions & 0 deletions browser/data-browser/src/chunks/Plugins/RouteWriteMoveDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Button } from '@components/Button';
import { Dialog, useDialog } from '@components/Dialog';
import { Column } from '@components/Row';
import type { JSONValue, RouteWriteConfigChange } from '@tomic/react';
import { useEffect, useState } from 'react';
import { RouteWriteApproval } from './RouteWriteApproval';

interface RouteWriteMoveDialogProps {
plugin: string;
/** The move to approve; the dialog opens while it is set. */
change: RouteWriteConfigChange | undefined;
config: JSONValue | undefined;
/** Saves the config; `approve` says whether the moved targets were approved. */
onSave: (approve: boolean) => Promise<void>;
/** Called after the dialog closed, whether or not anything was saved. */
onClose: () => void;
}

/**
* Asks again for the route grant when a config change points a write target
* somewhere else (#1758), the way an upgrade review asks for new targets:
* the moved targets are marked New and nothing is approved by default.
* Approving moves the plugin's write rights to the new parent; saving
* unchecked drops the route grant and the rights it came with.
*/
export function RouteWriteMoveDialog({
plugin,
change,
config,
onSave,
onClose,
}: RouteWriteMoveDialogProps) {
const [approve, setApprove] = useState(false);
const [busy, setBusy] = useState(false);
const [dialogProps, show, hide] = useDialog({
onCancel: onClose,
onSuccess: onClose,
});

useEffect(() => {
if (!change) return;
setApprove(false);
show();
}, [change, show]);

if (!change) return null;

const save = async () => {
setBusy(true);

try {
await onSave(approve);
hide(true);
} finally {
setBusy(false);
}
};

return (
<Dialog {...dialogProps} width='700px'>
<Dialog.Title>
<h1>Move incoming items</h1>
</Dialog.Title>
<Dialog.Content>
<Column>
<p>
This config stores what other servers send somewhere else. Approve
the new place to move the plugin’s write rights there, or save
without it to stop accepting these items.
</p>
<RouteWriteApproval
plugin={plugin}
targets={change.targets}
newTargets={change.moved}
config={config}
checked={approve}
onChange={setApprove}
/>
</Column>
</Dialog.Content>
<Dialog.Actions>
<Button onClick={() => hide(false)} subtle disabled={busy}>
Cancel
</Button>
<Button onClick={save} disabled={busy}>
{busy ? 'Saving…' : 'Save config'}
</Button>
</Dialog.Actions>
</Dialog>
);
}
26 changes: 26 additions & 0 deletions browser/data-browser/src/locales/de.po
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ msgstr ""
#: src/chunks/DashboardPage/BlockConfigDialog.tsx
#: src/chunks/PluginRuns/RunPluginDialog.tsx
#: src/chunks/Plugins/InstallationReviewDialog.tsx
#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
#: src/chunks/TablePage/BreakdownDialog.tsx
#: src/chunks/TablePage/DerivedColumnDialog.tsx
#: src/chunks/TablePage/Issues/IssuesView.tsx
Expand Down Expand Up @@ -1165,6 +1166,7 @@ msgstr ""

#: src/chunks/PluginRuns/AutomationWorkspace.tsx
#: src/chunks/PluginRuns/IntegrationConnection.tsx
#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
#: src/components/TeamProfileStep.tsx
#: src/views/Canvas/CanvasPage.tsx
msgid "Saving…"
Expand Down Expand Up @@ -9590,3 +9592,27 @@ msgstr ""
#: src/chunks/Plugins/RouteWriteApproval.tsx
msgid "Route writes"
msgstr ""

#: src/views/Installation/InstallationPage.tsx
msgid "Config saved, rights moved"
msgstr ""

#: src/views/Installation/InstallationPage.tsx
msgid "Config saved"
msgstr ""

#: src/views/Installation/InstallationPage.tsx
msgid "Set <0/> to the resource that should receive incoming items. The plugin's route grant stores them there, so this config can't be saved without it."
msgstr ""

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "Move incoming items"
msgstr ""

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "This config stores what other servers send somewhere else. Approve the new place to move the plugin’s write rights there, or save without it to stop accepting these items."
msgstr ""

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "Save config"
msgstr ""
26 changes: 26 additions & 0 deletions browser/data-browser/src/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ msgstr "AI Agents"
#: src/chunks/DashboardPage/BlockConfigDialog.tsx
#: src/chunks/PluginRuns/RunPluginDialog.tsx
#: src/chunks/Plugins/InstallationReviewDialog.tsx
#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
#: src/chunks/TablePage/BreakdownDialog.tsx
#: src/chunks/TablePage/DerivedColumnDialog.tsx
#: src/chunks/TablePage/Issues/IssuesView.tsx
Expand Down Expand Up @@ -1165,6 +1166,7 @@ msgstr "Save failed: {0}"

#: src/chunks/PluginRuns/AutomationWorkspace.tsx
#: src/chunks/PluginRuns/IntegrationConnection.tsx
#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
#: src/components/TeamProfileStep.tsx
#: src/views/Canvas/CanvasPage.tsx
msgid "Saving…"
Expand Down Expand Up @@ -9608,3 +9610,27 @@ msgstr "The plugin gets write rights on these resources, and may only change or
#: src/chunks/Plugins/RouteWriteApproval.tsx
msgid "Route writes"
msgstr "Route writes"

#: src/views/Installation/InstallationPage.tsx
msgid "Config saved, rights moved"
msgstr "Config saved, rights moved"

#: src/views/Installation/InstallationPage.tsx
msgid "Config saved"
msgstr "Config saved"

#: src/views/Installation/InstallationPage.tsx
msgid "Set <0/> to the resource that should receive incoming items. The plugin's route grant stores them there, so this config can't be saved without it."
msgstr "Set <0/> to the resource that should receive incoming items. The plugin's route grant stores them there, so this config can't be saved without it."

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "Move incoming items"
msgstr "Move incoming items"

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "This config stores what other servers send somewhere else. Approve the new place to move the plugin’s write rights there, or save without it to stop accepting these items."
msgstr "This config stores what other servers send somewhere else. Approve the new place to move the plugin’s write rights there, or save without it to stop accepting these items."

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "Save config"
msgstr "Save config"
26 changes: 26 additions & 0 deletions browser/data-browser/src/locales/es.po
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ msgstr "Agentes de IA"
#: src/chunks/DashboardPage/BlockConfigDialog.tsx
#: src/chunks/PluginRuns/RunPluginDialog.tsx
#: src/chunks/Plugins/InstallationReviewDialog.tsx
#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
#: src/chunks/TablePage/BreakdownDialog.tsx
#: src/chunks/TablePage/DerivedColumnDialog.tsx
#: src/chunks/TablePage/Issues/IssuesView.tsx
Expand Down Expand Up @@ -1165,6 +1166,7 @@ msgstr "Error al guardar: {0}"

#: src/chunks/PluginRuns/AutomationWorkspace.tsx
#: src/chunks/PluginRuns/IntegrationConnection.tsx
#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
#: src/components/TeamProfileStep.tsx
#: src/views/Canvas/CanvasPage.tsx
msgid "Saving…"
Expand Down Expand Up @@ -9590,3 +9592,27 @@ msgstr ""
#: src/chunks/Plugins/RouteWriteApproval.tsx
msgid "Route writes"
msgstr ""

#: src/views/Installation/InstallationPage.tsx
msgid "Config saved, rights moved"
msgstr ""

#: src/views/Installation/InstallationPage.tsx
msgid "Config saved"
msgstr ""

#: src/views/Installation/InstallationPage.tsx
msgid "Set <0/> to the resource that should receive incoming items. The plugin's route grant stores them there, so this config can't be saved without it."
msgstr ""

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "Move incoming items"
msgstr ""

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "This config stores what other servers send somewhere else. Approve the new place to move the plugin’s write rights there, or save without it to stop accepting these items."
msgstr ""

#: src/chunks/Plugins/RouteWriteMoveDialog.tsx
msgid "Save config"
msgstr ""
Loading
Loading