Skip to content

refactor(cli): add shared Metrics HTTP command foundation - #122

Draft
rtiman-spotify wants to merge 2 commits into
spotify:mainfrom
rtiman-spotify:cli-data-foundation
Draft

rtiman-spotify wants to merge 2 commits into
spotify:mainfrom
rtiman-spotify:cli-data-foundation

Conversation

@rtiman-spotify

Copy link
Copy Markdown

Summary

Shared foundation for separate CLI resource features replacing the proposed Data MCP adapter. Adds direct regional Metrics HTTP helpers, validated resource paths, consistent output/errors, bounded cancellation, and an extensible data command group. Existing commands remain unchanged; no onboarding, discovery, run storage, or MCP registration.

Feature PRs carry this exact foundation commit until it is merged by a human. Please review this PR first. Do not merge dependent feature PRs before their prerequisites.

Verification

Node 24; pnpm build and pnpm qa passed. Existing E2E suite passed: 46 tests across 15 files. Built root/data help passed. HTTP tests use MSW; no live API compatibility claim.

Checklist

  • All local tests passing
  • Relevant help and documentation supplied for this infrastructure scope
  • Linter/style checks passed
  • Tests added for new behavior
  • Regression tests added
  • TUI screenshots (not applicable)

Related: https://spotify.atlassian.net/browse/SLMD-1921. No Jira edits or merge performed.

rtiman-spotify and others added 2 commits October 19, 2026 15:30
Co-Authored-By: Xirp Agent (gpt-6.1-sol) <xirp-agent@users.noreply.ghe.spotify.net>
Co-Authored-By: Xirp Agent (gpt-6.1-sol) <xirp-agent@users.noreply.ghe.spotify.net>

@bespoyasov bespoyasov left a comment •

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.

Hi! I looked through the code and left a few comments from the technical side but, unfortunately, I don't have access to the board you linked to this PR so I can't judge the functionality itself since I lack the context about the feature.

Would be nice to get a reference with the info, purpose, and the reasons behind the feature. Thanks!

P.S. I think we should avoid linking internal resources in public repos 🙃

import { prepareAuthTokens } from '@spotify-confidence/testing/auth';
import { dataCommand } from '@commands/index.js';

function run(args: string[]) {

@bespoyasov bespoyasov Oct 20, 2026 •

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.

We have a createRunner util in testing for this, please use that function instead

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.

Better to test a command as a whole (integration-style) than a handler in isolation

Comment on lines +267 to +276
server.use(
http.post('https://onboarding.confidence.dev/v1/agentTelemetryKey:acquire', () =>
HttpResponse.json({ clientSecret: 'test' }),
),
http.post('https://events.eu.confidence.dev/v1/events:publish', async ({ request }) => {
const body = (await request.json()) as { events: { payload: { step: string } }[] };
steps.push(body.events[0].payload.step);
return HttpResponse.json({});
}),
);

@bespoyasov bespoyasov Oct 20, 2026 •

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.

Should be extracted in testing package for better collocation with other MSW handlers

import { extractFlags, formatJson, message, print, resolveFormat } from '@output/index.js';

/** Preserve the full response in JSON; render generic resources as key/value rows otherwise. */
export function printData(data: unknown, argv: Record<string, unknown>): void {

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.

For displaying the results, we have print in @output. Please, re-use that util; if it lacks functionality, extend it instead of creating a duplicate

Comment on lines +2 to +4
export function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

@bespoyasov bespoyasov Oct 20, 2026 •

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.

Looks more like a part of core?

Comment on lines +19 to +28
export function resourcePath(name: string, collection: string): string {
if (!/^[A-Za-z][A-Za-z0-9]*$/u.test(collection)) {
throw new Error('Invalid resource collection.');
}
const prefix = `${collection}/`;
if (!name.startsWith(prefix) || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(name.slice(prefix.length))) {
throw new Error(`Resource name must be ${collection}/<id> with a single valid ID.`);
}
return `/v1/${name}`;
}

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.

Doesn't seem to be related to @network; please, extract into features/data

const [group, action] = argv._;
if (
argv['dry-run'] === true &&
(group === 'data' || (group === 'warehouse' && (action === 'list' || action === 'get')))

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.

Why this exception in particular? If necessary, please, extract into a util and name appropriately to better explain the purpose and context


// Read the body under the same deadline as fetch. Transport/body failures
// belong to the outer catch; only JSON decoding failures are handled here.
const text = await response.text();

@bespoyasov bespoyasov Oct 20, 2026 •

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.

Consuming the response as text and then parsing it via JSON.parse feels awkward. Instead, please, add an option in the config for how to parse the response

import { metricsRequest } from '@network/metrics.js';
import { isRecord, requireRecord, requireString, resourcePath } from '@network/metrics-types.js';

afterEach(() => vi.unstubAllEnvs());

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.

Please, prefer explicit resource management over afterEach/beforeEach utils

import type { Argv } from 'yargs';
import { noop } from '@spotify-confidence/shared-kernel';

/** Feature PRs register their subcommands in this group. */

@bespoyasov bespoyasov Oct 20, 2026 •

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.

This comment doesn't add any missing context or explain quirks related to the code; please, clean up such comments throughout the PR to make the code less noisy

@rtiman-spotify
rtiman-spotify marked this pull request as draft October 20, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants