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
10 changes: 10 additions & 0 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"]
},
"chrome-devtools": {
"type": "stdio",
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest"]
},
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
},
"inputs": []
Expand Down
48 changes: 29 additions & 19 deletions migration/bold-workflow/insert-bold-workflowsteps.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ SELECT
'bold',
'Record',
1,
'{"tool": "record"}',
'{}',
'{"tool": "record"}'::jsonb,
'{}'::jsonb,
(now() AT TIME ZONE 'utc'),
(now() AT TIME ZONE 'utc'),
(SELECT u.id FROM users u ORDER BY u.id ASC LIMIT 1)
Expand All @@ -35,10 +35,12 @@ SELECT
'bold',
'Careful speech',
2,
'{"tool": "phraseBackTranslate", "settings": {"artifactTypeId": "'
(
'{"tool": "phraseBackTranslate", "settings": "{\"artifactTypeId\": \"'
|| (SELECT CAST(id AS TEXT) FROM artifacttypes WHERE typename = 'carefulspeech' ORDER BY id LIMIT 1)
|| '", "namedRegion": "CarefulSpeech"}}',
'{}',
|| '\", \"namedRegion\": \"CarefulSpeech\"}"}'
)::jsonb,
'{}'::jsonb,
(now() AT TIME ZONE 'utc'),
(now() AT TIME ZONE 'utc'),
(SELECT u.id FROM users u ORDER BY u.id ASC LIMIT 1)
Expand All @@ -52,10 +54,12 @@ SELECT
'bold',
'Lwc translation',
3,
'{"tool": "phraseBackTranslate", "settings": {"artifactTypeId": "'
(
'{"tool": "phraseBackTranslate", "settings": "{\"artifactTypeId\": \"'
|| (SELECT CAST(id AS TEXT) FROM artifacttypes WHERE typename = 'backtranslation' ORDER BY id LIMIT 1)
|| '", "namedRegion": "BT"}}',
'{}',
|| '\", \"namedRegion\": \"BT\"}"}'
)::jsonb,
'{}'::jsonb,
(now() AT TIME ZONE 'utc'),
(now() AT TIME ZONE 'utc'),
(SELECT u.id FROM users u ORDER BY u.id ASC LIMIT 1)
Expand All @@ -69,10 +73,12 @@ SELECT
'bold',
'Careful transcription',
4,
'{"tool": "transcribe", "settings": {"artifactTypeId": "'
(
'{"tool": "transcribe", "settings": "{\"artifactTypeId\": \"'
|| (SELECT CAST(id AS TEXT) FROM artifacttypes WHERE typename = 'carefulspeech' ORDER BY id LIMIT 1)
|| '"}}',
'{}',
|| '\"}"}'
)::jsonb,
'{}'::jsonb,
(now() AT TIME ZONE 'utc'),
(now() AT TIME ZONE 'utc'),
(SELECT u.id FROM users u ORDER BY u.id ASC LIMIT 1)
Expand All @@ -86,10 +92,12 @@ SELECT
'bold',
'Lwc transcription',
5,
'{"tool": "transcribe", "settings": {"artifactTypeId": "'
(
'{"tool": "transcribe", "settings": "{\"artifactTypeId\": \"'
|| (SELECT CAST(id AS TEXT) FROM artifacttypes WHERE typename = 'backtranslation' ORDER BY id LIMIT 1)
|| '"}}',
'{}',
|| '\"}"}'
)::jsonb,
'{}'::jsonb,
(now() AT TIME ZONE 'utc'),
(now() AT TIME ZONE 'utc'),
(SELECT u.id FROM users u ORDER BY u.id ASC LIMIT 1)
Expand All @@ -103,8 +111,8 @@ SELECT
'bold',
'Free translation',
6,
'{"tool": "wholeBackTranslate"}',
'{}',
'{"tool": "wholeBackTranslate"}'::jsonb,
'{}'::jsonb,
(now() AT TIME ZONE 'utc'),
(now() AT TIME ZONE 'utc'),
(SELECT u.id FROM users u ORDER BY u.id ASC LIMIT 1)
Expand All @@ -117,10 +125,12 @@ SELECT
'bold',
'Free transcription',
7,
'{"tool": "transcribe", "settings": {"artifactTypeId": "'
(
'{"tool": "transcribe", "settings": "{\"artifactTypeId\": \"'
|| (SELECT CAST(id AS TEXT) FROM artifacttypes WHERE typename = 'wholebacktranslation' ORDER BY id LIMIT 1)
|| '"}}',
'{}',
|| '\"}"}'
)::jsonb,
'{}'::jsonb,
(now() AT TIME ZONE 'utc'),
(now() AT TIME ZONE 'utc'),
(SELECT u.id FROM users u ORDER BY u.id ASC LIMIT 1)
Expand Down
7 changes: 5 additions & 2 deletions src/renderer/src/components/App/OrgHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import BigDialog from '../../hoc/BigDialog';
import { BigDialogBp } from '../../hoc/BigDialogBp';
import GroupTabs from '../GroupTabs';
import { StepEditor } from '../StepEditor';
import { defaultWorkflow } from '../../crud';
import { defaultWorkflow, useTeamWorkflowProcess } from '../../crud';
import { useRole } from '../../crud/useRole';
import { useOrbitData } from '../../hoc/useOrbitData';
import { ProjectSort } from '../Team/ProjectDialog/ProjectSort';
Expand Down Expand Up @@ -89,6 +89,9 @@ export const OrgHead = () => {
return organizations.find((o) => o.id === orgId);
}, [orgId, organizations]);

const headerWorkflowProcess = useTeamWorkflowProcess(orgId ?? undefined);
const stepEditorProcess = headerWorkflowProcess ?? defaultWorkflow;

const isAdmin = useMemo(
() => userIsOrgAdmin(orgId ?? ''),
[orgId, userIsOrgAdmin]
Expand Down Expand Up @@ -267,7 +270,7 @@ export const OrgHead = () => {
bp={isMobile ? BigDialogBp.mobile : BigDialogBp.md}
>
<StepEditor
process={defaultWorkflow}
process={stepEditorProcess}
org={isPersonal ? personalTeam || '' : orgId || ''}
/>
</BigDialog>
Expand Down
106 changes: 106 additions & 0 deletions src/renderer/src/components/StepEditor/StepEditor.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,112 @@ const mountStepEditor = (memory: Memory) => {
);
};

const BOLD_STEPS: MockOrgWfAttrs[] = [
{
id: 'bold-1',
name: 'Record',
sequencenum: 1,
process: 'bold',
tool: '{"tool": "record"}',
},
{
id: 'bold-2',
name: 'Careful speech',
sequencenum: 2,
process: 'bold',
tool: '{"tool": "phraseBackTranslate", "settings": "{\\"artifactTypeId\\": \\"art-cs\\", \\"namedRegion\\": \\"CarefulSpeech\\"}"}',
},
{
id: 'bold-3',
name: 'Lwc translation',
sequencenum: 3,
process: 'bold',
tool: '{"tool": "phraseBackTranslate", "settings": "{\\"artifactTypeId\\": \\"art-pbt\\", \\"namedRegion\\": \\"BT\\"}"}',
},
{
id: 'bold-4',
name: 'Careful transcription',
sequencenum: 4,
process: 'bold',
tool: '{"tool": "transcribe", "settings": "{\\"artifactTypeId\\": \\"art-cs\\"}"}',
},
{
id: 'bold-5',
name: 'Lwc transcription',
sequencenum: 5,
process: 'bold',
tool: '{"tool": "transcribe", "settings": "{\\"artifactTypeId\\": \\"art-pbt\\"}"}',
},
{
id: 'bold-6',
name: 'Free translation',
sequencenum: 6,
process: 'bold',
tool: '{"tool": "wholeBackTranslate"}',
},
{
id: 'bold-7',
name: 'Free transcription',
sequencenum: 7,
process: 'bold',
tool: '{"tool": "transcribe", "settings": "{\\"artifactTypeId\\": \\"art-wbt\\"}"}',
},
];

describe('StepEditor — Bold workflow', () => {
it('loads all 7 Bold workflow steps with correct names', () => {
const memory = createWorkflowStepMemory(TEST_ORG_ID, BOLD_STEPS);
mountStepEditor(memory);
cy.get('.MuiDialogContent-root input#stepName').should('have.length', 7);
const names = [
'Record',
'Careful speech',
'LWC translation',
'Careful transcription',
'LWC transcription',
'Free translation',
'Free transcription',
];
names.forEach((name, i) => {
cy.get('.MuiDialogContent-root input#stepName')
.eq(i)
.should('have.value', name);
});
});

it('handles settings stored as an embedded object (pre-fix format) without crashing', () => {
// Regression guard: getToolSettings must normalize object settings to a JSON string
// so that prettySettings (JSON.parse) does not receive "[object Object]" and throw.
const stepsWithEmbeddedObj: MockOrgWfAttrs[] = [
{
id: 'bold-obj-1',
name: 'Record',
sequencenum: 1,
process: 'bold',
tool: '{"tool": "record"}',
},
{
id: 'bold-obj-2',
name: 'Careful speech',
sequencenum: 2,
process: 'bold',
// settings as a nested object (old DB format) instead of a JSON string
tool: JSON.stringify({
tool: 'phraseBackTranslate',
settings: { artifactTypeId: 'art-cs', namedRegion: 'CarefulSpeech' },
}),
},
];
const memory = createWorkflowStepMemory(TEST_ORG_ID, stepsWithEmbeddedObj);
mountStepEditor(memory);
// Both steps must render — if getToolSettings crashes, only step 1 would appear
cy.get('.MuiDialogContent-root input#stepName').should('have.length', 2);
cy.get('.MuiDialogContent-root input#stepName')
.eq(1)
.should('have.value', 'Careful speech');
});
});

describe('StepEditor (Edit Workflow)', () => {
it('loads org workflow steps and shows the top Add control', () => {
const memory = createWorkflowStepMemory(TEST_ORG_ID, [
Expand Down
82 changes: 61 additions & 21 deletions src/renderer/src/components/StepEditor/StepEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ export const StepEditor = ({ process, org }: IProps) => {
clearRequested,
clearCompleted,
} = useContext(UnsavedContext).state;
const { GetOrgWorkflowSteps, localizedWorkStep } = useOrgWorkflowSteps();
const {
GetOrgWorkflowSteps,
getProcessTemplateSteps,
resolveOrgWorkflowStepPresentation,
} = useOrgWorkflowSteps();
const { showMessage } = useSnackBar();
const saving = useRef(false);
const toolId = 'stepEditor';
Expand Down Expand Up @@ -397,28 +401,64 @@ export const StepEditor = ({ process, org }: IProps) => {
}, [toolsChanged]);

useEffect(() => {
GetOrgWorkflowSteps({ process: 'ANY', org, showAll: true }).then(
(orgSteps) => {
const newRows = Array<IStepRow>();
orgSteps.forEach((s) => {
const tool = getTool(s.attributes?.tool);
const settings = getToolSettings(s.attributes?.tool);
newRows.push({
id: s.id,
seq: s.attributes?.sequencenum,
name: localizedWorkStep(s.attributes?.name),
pos: 0,
tool: toCamel(tool),
settings: settings,
prettySettings: prettySettings(tool, settings),
rIdx: newRows.length,
});
});
setRows(newRows.sort((i, j) => i.seq - j.seq));
// Scope to the team's workflow when `process` is set (e.g. bold). Using
// 'ANY' merged every process and sorted only by sequencenum, so Edit Workflow
// could disagree with the steps CreateOrgWorkflowSteps just added.
GetOrgWorkflowSteps({
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a team has mixed process workflow steps, I think we want to see them all. Why do your bold teams have mixed workflow steps. I think this is an unnecessary and dangerous change. We send in the defaultworkflow, so there will always be a process, and 'ANY' will never be used. THere are 171 teams on prod with mixed processes. Will only steps from draft show up for them?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "merged every process and sorted only by sequencenum" is exactly what we want to be doing, even if AI doesn't understand it.

process: process ?? 'ANY',
org,
showAll: true,
}).then((orgSteps) => {
const newRows = Array<IStepRow>();
const rawCounts = new Map<string, number>();
for (const s of orgSteps) {
const n = s.attributes?.name ?? '';
rawCounts.set(n, (rawCounts.get(n) ?? 0) + 1);
}
);
const proc = process ?? 'ANY';
const sortedOrg = [...orgSteps].sort((a, b) => {
const d = a.attributes.sequencenum - b.attributes.sequencenum;
if (d !== 0) return d;
return String(a.id).localeCompare(String(b.id));
});
const templates = proc !== 'ANY' ? getProcessTemplateSteps(proc) : [];
const uniqueSeqs = new Set(
sortedOrg.map((s) => s.attributes?.sequencenum ?? 0)
).size;
const useTemplateIndex =
proc !== 'ANY' &&
templates.length === sortedOrg.length &&
sortedOrg.length > 0 &&
(uniqueSeqs < sortedOrg.length || uniqueSeqs <= 1);

sortedOrg.forEach((s, idx) => {
const rawName = s.attributes?.name ?? '';
const dup = (rawCounts.get(rawName) ?? 0) > 1;
const pres = resolveOrgWorkflowStepPresentation(
s,
proc,
dup,
useTemplateIndex
? { index: idx, orgCount: sortedOrg.length }
: undefined
);
const tool = getTool(pres.toolAttr);
const settings = getToolSettings(pres.toolAttr);
newRows.push({
id: s.id,
seq: pres.sequencenum ?? s.attributes?.sequencenum,
name: pres.name,
pos: 0,
tool: toCamel(tool),
settings: settings,
prettySettings: prettySettings(tool, settings),
rIdx: newRows.length,
});
});
setRows(newRows.sort((i, j) => i.seq - j.seq));
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [org]);
}, [org, process]);

useEffect(() => {
setSortKey((sortKey) => sortKey + 1);
Expand Down
Loading
Loading