Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .changeset/30436-studio-saved-to-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@object-ui/app-shell': patch
---

fix(app-shell): the Studio item header's "Saved to X's customizations" note says where the customization lives, in the publish sheet's own sentence (#30436)

Clause-②: no
Metadata-protocol: none

The note on an item header (Interfaces item, navigation editor, Data object, Automations flow) now goes on with the sentence the pending-changes sheet draws under it (`preview.changes.customizationEnvironment`, from #30119): the changes are kept in this environment's database as items of the real `<base>.custom` pair, and reach another environment only through `os package export`. No new i18n key: the note reuses `customizationEnvironmentText`. Before the pair's first save the note names the `<base>.custom` id that save creates.
29 changes: 26 additions & 3 deletions ui/packages/app-shell/src/preview/savedToCustomization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,31 @@ export function customizationEnvironmentText(t: Translate, pairId: string): stri
});
}

/** {@link savedToCustomizationText} as an element, for the Studio item header's note line. */
export function SavedToCustomizationNote({ baseLabel, testId }: { baseLabel: string; testId?: string }) {
/**
* {@link savedToCustomizationText} as an element, for the Studio item header's
* note line. Given the pair's id, it goes on with the pending-changes sheet's
* second sentence, {@link customizationEnvironmentText}, so the header and
* the sheet say the same words from the same keys (objectstack#30436).
*/
export function SavedToCustomizationNote({
baseLabel,
pairId,
testId,
}: {
baseLabel: string;
/** The base's `<base>.custom` pair; nothing more is said without one. */
pairId?: string | null;
testId?: string;
}) {
const { t } = useObjectTranslation();
return <span data-testid={testId}>{savedToCustomizationText(t, baseLabel)}</span>;
return (
<>
<span data-testid={testId}>{savedToCustomizationText(t, baseLabel)}</span>
{pairId ? (
<span data-testid={testId ? `${testId}-environment` : undefined} className="block">
{customizationEnvironmentText(t, pairId)}
</span>
) : null}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,18 @@ describe('Customize a read-only base’s flow into its pair (objectstack#28570)'
describe('the flow header says where a customized flow is saved (objectstack#29691)', () => {
const SAVED_TO = 'Saved to HotCRM’s customizations';
const savedTo = () => within(header()).queryByTestId('auto-flow-saved-to');
// objectstack#30436 — and the sheet's second sentence, naming the real pair.
const ENVIRONMENT =
'These changes are kept in this environment’s database as items of com.example.hotcrm.custom, not in your project’s source. They reach another environment only through os package export.';
const environment = () => within(header()).queryByTestId('auto-flow-saved-to-environment');

it('Customize unlocks the flow, and the header says it is saved to the base’s customizations', async () => {
mount();
await openFlow();
expect(savedTo()).toBeNull();
await customize();
await waitFor(() => expect(savedTo()).toHaveTextContent(SAVED_TO), { timeout: 8000 });
expect(environment()).toHaveTextContent(ENVIRONMENT);
expect(savedTo()!.closest('[data-slot="item-note"]')).not.toBeNull();
});

Expand All @@ -520,6 +525,7 @@ describe('the flow header says where a customized flow is saved (objectstack#296
mount({ listedPair: PAIR });
await openFlow(/^Notify owner and manager$/);
await waitFor(() => expect(savedTo()).toHaveTextContent(SAVED_TO), { timeout: 8000 });
expect(environment()).toHaveTextContent(ENVIRONMENT);
});

it('CONTROL: the base’s flow before Customize says nothing of the kind', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,11 @@ describe('A Chinese label names a base object\'s extension field by pinyin, suff
describe('the object header says where a base object’s extension is saved (objectstack#29691)', () => {
const SAVED_TO = 'Saved to HotCRM’s customizations';
const savedTo = () => within(screen.getByTestId('data-object-header')).queryByTestId('data-object-saved-to');
// objectstack#30436 — and the sheet's second sentence, naming the real pair.
const ENVIRONMENT =
'These changes are kept in this environment’s database as items of com.example.hotcrm.custom, not in your project’s source. They reach another environment only through os package export.';
const environment = () =>
within(screen.getByTestId('data-object-header')).queryByTestId('data-object-saved-to-environment');

it('the first add makes the extension, and the header says it is saved to the base’s customizations', async () => {
await renderBase();
Expand All @@ -458,6 +463,7 @@ describe('the object header says where a base object’s extension is saved (obj
await addFromFormToolbar('Region');
await waitFor(() => expect(extensionSaves()).toHaveLength(1), { timeout: 8000 });
await waitFor(() => expect(savedTo()).toHaveTextContent(SAVED_TO), { timeout: 8000 });
expect(environment()).toHaveTextContent(ENVIRONMENT);
expect(savedTo()!.closest('[data-slot="item-note"]')).not.toBeNull();
});

Expand All @@ -467,6 +473,7 @@ describe('the object header says where a base object’s extension is saved (obj
srv.publishedExt = { extend: OBJECT, fields: { region__c: region }, _packageId: PAIR };
await renderBase();
await waitFor(() => expect(savedTo()).toHaveTextContent(SAVED_TO), { timeout: 8000 });
expect(environment()).toHaveTextContent(ENVIRONMENT);
});

it('CONTROL: a base object with no extension says nothing of the kind', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,10 @@ describe('Customize a read-only base’s navigation into its pair (objectstack#3
await openPipeline();
fireEvent.click(await within(rail()).findByTestId('if-edit-nav', undefined, { timeout: 8000 }));
expect(await within(rail()).findByTestId('if-nav-saved-to')).toBeInTheDocument();
// objectstack#30436 — and the sheet's second sentence, naming the pair.
expect(within(rail()).getByTestId('if-nav-saved-to-environment')).toHaveTextContent(
'These changes are kept in this environment’s database as items of com.example.hotcrm.custom, not in your project’s source. They reach another environment only through os package export.',
);
await sleep(1500);
expect(srv.ensure).toEqual([]);
expect(srv.saves).toEqual([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,18 @@ describe('the wall is read from what the server publishes (objectstack#28562)',
describe('the item header says where the customization is saved (objectstack#29691)', () => {
const SAVED_TO = 'Saved to HotCRM’s customizations';
const savedTo = () => within(header()).queryByTestId('if-item-saved-to');
// objectstack#30436 — and the sheet's second sentence, naming the real pair.
const ENVIRONMENT =
'These changes are kept in this environment’s database as items of com.example.hotcrm.custom, not in your project’s source. They reach another environment only through os package export.';
const environment = () => within(header()).queryByTestId('if-item-saved-to-environment');

it('after Customize the header says it, and after the first edit it still does, in the sheet’s own words', async () => {
renderWorkbench();
await openPipeline();
fireEvent.click(await within(header()).findByTestId('studio-customize', undefined, { timeout: 8000 }));
await waitFor(() => expect(savedTo()).toHaveTextContent(SAVED_TO), { timeout: 8000 });
// Before the first save the pair is the `<base>.custom` id that save creates.
expect(environment()).toHaveTextContent(ENVIRONMENT);
// On its own line under the title row, never in it (the fold measures the row).
expect(savedTo()!.closest('[data-slot="item-note"]')).not.toBeNull();
expect(within(header()).getByRole('heading', { level: 1 }).parentElement).not.toContainElement(savedTo());
Expand All @@ -369,6 +375,7 @@ describe('the item header says where the customization is saved (objectstack#296
await waitFor(() => expect(srv.saves).toHaveLength(1), { timeout: 8000 });
await waitFor(() => expect(panelPackage()).toBe(PAIR), { timeout: 8000 });
expect(savedTo()).toHaveTextContent(SAVED_TO);
expect(environment()).toHaveTextContent(ENVIRONMENT);
// The header line and the sheet line read the same.
expect(screen.getByTestId('sheet-saved-to').textContent).toBe(savedTo()!.textContent);
});
Expand All @@ -378,6 +385,7 @@ describe('the item header says where the customization is saved (objectstack#296
await openPipeline();
await within(header()).findByTestId('studio-customize', undefined, { timeout: 8000 });
expect(savedTo()).toBeNull();
expect(environment()).toBeNull();
expect(header().querySelector('[data-slot="item-note"]')).toBeNull();
expect(screen.queryByTestId('sheet-saved-to')).toBeNull();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2862,6 +2862,9 @@ export function InterfacesPillar({
const customizeBase = customizing && customization ? customization.base : null;
const customizePair = customizing && customization ? customization.pairId : null;
const customizeBaseLabel = customization?.baseLabel ?? packageId;
// objectstack#30436 — the pair the "saved to" note names: the served one, or
// the `<base>.custom` id the first save creates.
const customizeNotePair = customization ? (customization.pairId ?? pairIdOf(customization.base)) : null;
const customizeWalled = customizing && customization?.walled === true;
const customizeBlock: CustomizeBlock = customizeWalled
? 'walled'
Expand Down Expand Up @@ -4235,7 +4238,7 @@ export function InterfacesPillar({
// objectstack#29693 — on a narrow row the chips and the item tools fold
// into the ⋯ menu before the title drops below its readable width.
foldMenu={itemMoreEl ? 'pillar' : { label: t('engine.studio.if.more', locale), testId: 'if-item-header-more' }}
note={ifSavedTo ? <SavedToCustomizationNote baseLabel={customizeBaseLabel} testId="if-item-saved-to" /> : undefined}
note={ifSavedTo ? <SavedToCustomizationNote baseLabel={customizeBaseLabel} pairId={customizeNotePair} testId="if-item-saved-to" /> : undefined}
leading={
<button
type="button"
Expand Down Expand Up @@ -5139,7 +5142,7 @@ export function InterfacesPillar({
{/* objectstack#30059 — where a read-only base's navigation edit is saved. */}
{editNav && readOnly && (
<p className="mt-1 text-[10px] text-muted-foreground">
<SavedToCustomizationNote baseLabel={customizeBaseLabel} testId="if-nav-saved-to" />
<SavedToCustomizationNote baseLabel={customizeBaseLabel} pairId={customizeNotePair} testId="if-nav-saved-to" />
</p>
)}
{listOnlyCustomized && !editNav && !navMarked ? null : appStatus === 'missing' && !error ? (
Expand Down Expand Up @@ -7003,6 +7006,7 @@ export function DataPillar({
(extOpen !== null && (extOpen.published.size > 0 || extHasDraft || extDirty))) ? (
<SavedToCustomizationNote
baseLabel={customization.baseLabel}
pairId={customization.pairId ?? pairIdOf(customization.base)}
testId="data-object-saved-to"
/>
) : undefined
Expand Down Expand Up @@ -7807,6 +7811,8 @@ export function AutomationsPillar({
const customizeBase = flowCustomizing && customization ? customization.base : null;
const customizePair = flowCustomizing && customization ? customization.pairId : null;
const customizeBaseLabel = customization?.baseLabel ?? railPackageId ?? '';
// objectstack#30436 — the pair the "saved to" note names.
const customizeNotePair = customization ? (customization.pairId ?? pairIdOf(customization.base)) : null;
const customizeWalled = flowCustomizing && customization?.walled === true;
const customizeBlock: CustomizeBlock = customizeWalled
? 'walled'
Expand Down Expand Up @@ -9053,7 +9059,7 @@ export function AutomationsPillar({
// or is unlocked to customize saves to the base's pair.
note={
customization !== null && (!!current?.addedIn || flowInPair || flowUnlocked) ? (
<SavedToCustomizationNote baseLabel={customizeBaseLabel} testId="auto-flow-saved-to" />
<SavedToCustomizationNote baseLabel={customizeBaseLabel} pairId={customizeNotePair} testId="auto-flow-saved-to" />
) : undefined
}
leading={
Expand Down
Loading