Skip to content

Commit bc9df25

Browse files
refactor(mastra): clear Sonar S1192 + S1541 from visualization port
The viz/streaming port surfaced six SonarCloud findings on PR #22: - S1192 (×5): the step-id string literals (`check-cache`, `get-tables`, `check-templates`, `get-columns`, `sql-and-validate`, `get-dataset-data`) each appear 3–4 times across `createStep({id})`, `emitToolStatus(...)`, `getStepResult(...)` and branch-output unwrap. Hoist each into a `STEP_*` const declared once per workflow file. - S1541 (×1, critical): `runVisualizationWorkflow` hit cyclomatic 11 after gaining the `requestedType` param. Extract the final Tool + Completed event emission into `emitVisualizationResult`, bringing the method back under the threshold. No behaviour change — same step ids, same emitted events.
1 parent 924d614 commit bc9df25

3 files changed

Lines changed: 52 additions & 27 deletions

File tree

src/components/visualization/tools/generate-visualization.mastra.tool.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,35 @@ It does not return anything, instead it fires an event internally that renders t
110110
// visualizationWorkflow's final step is `.then(renderVisualizationStep)`
111111
// (not a `.branch()`), so the result lands directly on the top level
112112
// — no branch-key unwrap needed.
113-
const rawResult =
114-
(result as {result?: Record<string, unknown>}).result ?? {};
115-
const workflowResult = rawResult as {
113+
const workflowResult = ((result as {result?: Record<string, unknown>})
114+
.result ?? {}) as {
116115
chartConfig?: unknown;
117116
visualization?: string;
118117
datasetId?: string;
119118
sql?: string;
120119
description?: string;
121120
};
122-
// Emit final Tool event so the UI can render the chart inline.
123-
// UI consumers expect `visualization` to hold the chart type and
124-
// `config` to hold chart settings.
121+
this.emitVisualizationResult(writer, toolCallId, workflowResult);
122+
return workflowResult;
123+
}
124+
125+
/**
126+
* Emit the final Tool event the UI renders the chart from, then a
127+
* Completed ToolStatus. The UI's renderVizFromToolEvent reads
128+
* `data.visualization` as the chart TYPE and `data.config` as the
129+
* chart settings (see sandbox app.js renderChart signature).
130+
*/
131+
private emitVisualizationResult(
132+
writer: ((e: LLMStreamEvent) => void) | undefined,
133+
toolCallId: string,
134+
workflowResult: {
135+
chartConfig?: unknown;
136+
visualization?: string;
137+
datasetId?: string;
138+
sql?: string;
139+
description?: string;
140+
},
141+
): void {
125142
writer?.({
126143
type: LLMStreamEventType.Tool,
127144
data: {
@@ -141,6 +158,5 @@ It does not return anything, instead it fires an event internally that renders t
141158
type: LLMStreamEventType.ToolStatus,
142159
data: {id: toolCallId, status: ToolStatus.Completed},
143160
});
144-
return workflowResult;
145161
}
146162
}

src/mastra/workflows/db-query/generate.workflow.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ import {
2424

2525
const MAX_VALIDATION_ATTEMPTS = 3;
2626

27+
// Step ids referenced from createStep, emitToolStatus, getStepResult and
28+
// branch-output unwrap — declared once to satisfy SonarQube S1192.
29+
const STEP_CHECK_CACHE = 'check-cache';
30+
const STEP_GET_TABLES = 'get-tables';
31+
const STEP_CHECK_TEMPLATES = 'check-templates';
32+
const STEP_GET_COLUMNS = 'get-columns';
33+
const STEP_SQL_AND_VALIDATE = 'sql-and-validate';
34+
2735
/** Workflow status enum exported so the post-cache step's union type
2836
* stays under SonarQube's S4622 threshold (max 2 unions inline). */
2937
type DbQueryStatus = 'AsIs' | 'FromTemplate' | 'Failed' | 'Continue';
@@ -88,7 +96,7 @@ const outputSchema = z.object({
8896
* CheckCacheNode (`git show 4be9767^:src/components/db-query/nodes/check-cache.node.ts`).
8997
*/
9098
const checkCacheStep = createStep({
91-
id: 'check-cache',
99+
id: STEP_CHECK_CACHE,
92100
inputSchema,
93101
outputSchema: z.object({
94102
cacheHit: z.boolean(),
@@ -125,7 +133,7 @@ Return ONLY the verdict, no other text.`;
125133
if (similar) {
126134
emitToolStatus(
127135
requestContext,
128-
'check-cache',
136+
STEP_CHECK_CACHE,
129137
'Found similar query in cache, using it as example',
130138
);
131139
return {cacheHit: false};
@@ -134,7 +142,7 @@ Return ONLY the verdict, no other text.`;
134142
if (match) {
135143
emitToolStatus(
136144
requestContext,
137-
'check-cache',
145+
STEP_CHECK_CACHE,
138146
'Found relevant query in cache',
139147
);
140148
const idx = parseInt(match[1], 10) - 1;
@@ -164,13 +172,13 @@ Return ONLY the verdict, no other text.`;
164172
* array, so the rest of the workflow can be reasoned about.
165173
*/
166174
const getTablesStep = createStep({
167-
id: 'get-tables',
175+
id: STEP_GET_TABLES,
168176
inputSchema,
169177
outputSchema: z.object({tables: z.array(z.string())}),
170178
execute: async ({requestContext}) => {
171179
emitToolStatus(
172180
requestContext,
173-
'get-tables',
181+
STEP_GET_TABLES,
174182
'Extracting relevant tables from the schema',
175183
);
176184
const schemaStore = getSchemaStore(requestContext);
@@ -197,7 +205,7 @@ const getTablesStep = createStep({
197205
* matched template id is consumed.
198206
*/
199207
const checkTemplatesStep = createStep({
200-
id: 'check-templates',
208+
id: STEP_CHECK_TEMPLATES,
201209
inputSchema,
202210
outputSchema: z.object({
203211
matched: z.boolean(),
@@ -233,7 +241,7 @@ Return 'match <index>' for an exact match or 'no_match'. No other text.`;
233241
if (match) {
234242
emitToolStatus(
235243
requestContext,
236-
'check-templates',
244+
STEP_CHECK_TEMPLATES,
237245
'Matched query template',
238246
);
239247
const idx = parseInt(match[1], 10) - 1;
@@ -262,14 +270,14 @@ const postCacheAndTablesStep = createStep({
262270
prompt: z.string(),
263271
}),
264272
execute: async ({getStepResult, getInitData, inputData}) => {
265-
const cache = (getStepResult('check-cache') ?? {cacheHit: false}) as {
273+
const cache = (getStepResult(STEP_CHECK_CACHE) ?? {cacheHit: false}) as {
266274
cacheHit: boolean;
267275
datasetId?: string;
268276
};
269-
const tables = (getStepResult('get-tables') ?? {tables: []}) as {
277+
const tables = (getStepResult(STEP_GET_TABLES) ?? {tables: []}) as {
270278
tables: string[];
271279
};
272-
const templates = (getStepResult('check-templates') ?? {
280+
const templates = (getStepResult(STEP_CHECK_TEMPLATES) ?? {
273281
matched: false,
274282
}) as {matched: boolean; templateId?: string};
275283
// After `.parallel()`, `inputData` is the parallel-step output map keyed by
@@ -387,7 +395,7 @@ const failedStep = createStep({
387395
* table names.
388396
*/
389397
const getColumnsStep = createStep({
390-
id: 'get-columns',
398+
id: STEP_GET_COLUMNS,
391399
inputSchema: z.any(),
392400
outputSchema: z.object({
393401
prompt: z.string(),
@@ -397,7 +405,7 @@ const getColumnsStep = createStep({
397405
execute: async ({inputData, requestContext}) => {
398406
emitToolStatus(
399407
requestContext,
400-
'get-columns',
408+
STEP_GET_COLUMNS,
401409
'Extracting relevant columns from the schema',
402410
);
403411
const data = inputData as {
@@ -449,7 +457,7 @@ const generateChecklistStep = createStep({
449457
}),
450458
execute: async ({inputData, requestContext}) => {
451459
const wrapped = inputData as Record<string, unknown>;
452-
const fromGetColumns = wrapped['get-columns'] as
460+
const fromGetColumns = wrapped[STEP_GET_COLUMNS] as
453461
| {prompt?: string; tables?: string[]}
454462
| undefined;
455463
const prompt =
@@ -487,7 +495,7 @@ Return ONLY the checklist as plain text bullets, no preamble.`;
487495
* `git show 4be9767^:src/components/db-query/nodes/{syntactic,semantic}-validator.node.ts`.
488496
*/
489497
const sqlAndValidateStep = createStep({
490-
id: 'sql-and-validate',
498+
id: STEP_SQL_AND_VALIDATE,
491499
// Loose input schema: dountil feeds this step its own output on each
492500
// iteration after the first, but on iter 0 the upstream step's payload
493501
// arrives — schema unions across the two are awkward in zod, so the
@@ -506,7 +514,7 @@ const sqlAndValidateStep = createStep({
506514
execute: async ({inputData, requestContext}) => {
507515
emitToolStatus(
508516
requestContext,
509-
'sql-and-validate',
517+
STEP_SQL_AND_VALIDATE,
510518
'Generating SQL query from the prompt',
511519
);
512520
const data = inputData as {
@@ -536,14 +544,14 @@ const sqlAndValidateStep = createStep({
536544
if (stage === 'syntactic') {
537545
emitToolStatus(
538546
requestContext,
539-
'sql-and-validate',
547+
STEP_SQL_AND_VALIDATE,
540548
'Validating generated SQL query',
541549
);
542550
return;
543551
}
544552
emitToolStatus(
545553
requestContext,
546-
'sql-and-validate',
554+
STEP_SQL_AND_VALIDATE,
547555
"Verifying if the query fully satisfies the user's requirement",
548556
);
549557
},

src/mastra/workflows/visualization.workflow.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from './db-query/_helpers';
1212

1313
const DEFAULT_CHART_TYPE = 'bar';
14+
const STEP_GET_DATASET_DATA = 'get-dataset-data';
1415

1516
/**
1617
* Unwrap the branch-wrapped or direct upstream input. Mastra wraps the
@@ -204,7 +205,7 @@ const callQueryGenerationStep = createStep({
204205
* when the consumer hasn't bound the db-query component.
205206
*/
206207
const getDatasetDataStep = createStep({
207-
id: 'get-dataset-data',
208+
id: STEP_GET_DATASET_DATA,
208209
// Accept both shapes: direct selectVisualisation output and the
209210
// branch-wrapped post-callQueryGeneration output. Body unwraps.
210211
inputSchema: z.any(),
@@ -219,7 +220,7 @@ const getDatasetDataStep = createStep({
219220
execute: async ({inputData, requestContext}) => {
220221
emitToolStatus(
221222
requestContext,
222-
'get-dataset-data',
223+
STEP_GET_DATASET_DATA,
223224
'Preparing visualization',
224225
);
225226
const upstream = pickFromBranch<{
@@ -261,7 +262,7 @@ const renderVisualizationStep = createStep({
261262
userQuery?: string;
262263
sql?: string;
263264
description?: string;
264-
}>(inputData, 'get-dataset-data');
265+
}>(inputData, STEP_GET_DATASET_DATA);
265266
const chartType = upstream.chartType ?? DEFAULT_CHART_TYPE;
266267
const userQuery = upstream.userQuery ?? '';
267268
const sql = upstream.sql;

0 commit comments

Comments
 (0)