Skip to content

Commit da2b3db

Browse files
committed
0.1.1
1 parent c44ed92 commit da2b3db

10 files changed

Lines changed: 57 additions & 59 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@metabase/cli",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "Metabase CLI",
55
"license": "AGPL-3.0",
66
"repository": {

src/commands/card/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function parseExportFormat(raw: string): ExportFormat {
109109
}
110110

111111
function applyLimit(result: CardQueryResult, limit: number | null): CardQueryResult {
112-
if (limit === null || result.status !== "completed" || result.data.rows.length <= limit) {
112+
if (limit === null || result.data === undefined || result.data.rows.length <= limit) {
113113
return result;
114114
}
115115
return { ...result, data: { ...result.data, rows: result.data.rows.slice(0, limit) } };

src/commands/collection/items.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {
2-
COLLECTION_ITEM_MODELS,
2+
COLLECTION_ITEM_FILTER_MODELS,
33
COLLECTION_PINNED_STATES,
44
CollectionItem,
55
CollectionItemCompact,
6-
CollectionItemModel,
6+
CollectionItemFilterModel,
77
CollectionPinnedState,
88
collectionItemView,
99
} from "../../domain/collection";
@@ -32,7 +32,7 @@ export default defineMetabaseCommand({
3232
},
3333
models: {
3434
type: "string",
35-
description: `Comma-separated model filter: ${COLLECTION_ITEM_MODELS.join(",")}`,
35+
description: `Comma-separated model filter: ${COLLECTION_ITEM_FILTER_MODELS.join(",")}`,
3636
alias: "m",
3737
},
3838
archived: {
@@ -58,7 +58,7 @@ export default defineMetabaseCommand({
5858
],
5959
async run({ args, ctx, getClient }) {
6060
const ref = parseCollectionRef(args.id);
61-
const models = parseEnumCsv(args.models, CollectionItemModel, "--models");
61+
const models = parseEnumCsv(args.models, CollectionItemFilterModel, "--models");
6262
const pinnedState = parseEnum(args["pinned-state"], CollectionPinnedState, "--pinned-state");
6363
const max = args.limit === undefined ? undefined : parseId(args.limit, "--limit");
6464
const client = await getClient();

src/commands/dashboard/update-dashcard.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default defineMetabaseCommand({
4444
}
4545
const patched = Dashcard.parse({ ...target, ...patch });
4646
const updatedDashcards = dashboard.dashcards.map((dashcard) =>
47-
dashcard.id === dashcardId ? patched : dashcard,
47+
stripEntityId(dashcard.id === dashcardId ? patched : dashcard),
4848
);
4949

5050
const result = await client.requestParsed(DashboardDetail, `/api/dashboard/${dashboardId}`, {
@@ -60,3 +60,8 @@ export default defineMetabaseCommand({
6060
renderItem(refreshed, dashcardView, ctx);
6161
},
6262
});
63+
64+
function stripEntityId(dashcard: Dashcard): Omit<Dashcard, "entity_id"> {
65+
const { entity_id: _entity_id, ...rest } = dashcard;
66+
return rest;
67+
}

src/domain/card.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export const CardCreateInput = z
7474
dataset_query: CardDatasetQuery,
7575
display: z.string().min(1),
7676
visualization_settings: z.record(z.string(), z.unknown()),
77-
description: z.string().nullable().optional(),
77+
description: z.string().min(1).nullable().optional(),
7878
collection_id: z.number().int().positive().nullable().optional(),
7979
collection_position: z.number().int().positive().nullable().optional(),
8080
dashboard_id: z.number().int().positive().nullable().optional(),
@@ -118,33 +118,22 @@ const QueryColumn = z
118118
})
119119
.loose();
120120

121-
const CardQueryDataCompleted = z
121+
const CardQueryData = z
122122
.object({
123123
rows: z.array(z.unknown()),
124124
cols: z.array(QueryColumn),
125125
})
126126
.loose();
127127

128-
const CardQueryCompleted = z
129-
.object({
130-
status: z.literal("completed"),
131-
row_count: z.number().int().nonnegative(),
132-
data: CardQueryDataCompleted,
133-
})
134-
.loose();
135-
136-
const CardQueryFailed = z
128+
export const CardQueryResult = z
137129
.object({
138-
status: z.literal("failed"),
130+
status: z.string(),
131+
row_count: z.number().int().nonnegative().optional(),
132+
data: CardQueryData.optional(),
139133
error: z.string().nullable().optional(),
140134
error_type: z.string().nullable().optional(),
141135
})
142136
.loose();
143-
144-
export const CardQueryResult = z.discriminatedUnion("status", [
145-
CardQueryCompleted,
146-
CardQueryFailed,
147-
]);
148137
export type CardQueryResult = z.infer<typeof CardQueryResult>;
149138

150139
export const cardQueryView: ResourceView<CardQueryResult> = {

src/domain/collection.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,21 @@ const CollectionAuthorityLevel = z.enum(["official"]);
1010
const CollectionType = z.enum([
1111
"instance-analytics",
1212
"trash",
13-
"remote-synced",
1413
"library",
1514
"library-data",
1615
"library-metrics",
17-
"shared-tenant-collection",
1816
"tenant-specific-root-collection",
1917
]);
2018

21-
const CollectionNamespace = z.enum([
22-
"snippets",
23-
"transforms",
24-
"analytics",
25-
"tenant-specific",
26-
"shared-tenant-collection",
27-
]);
19+
const CollectionNamespace = z.string().min(1);
2820

29-
export const COLLECTION_ITEM_MODELS = [
21+
export const COLLECTION_ITEM_FILTER_MODELS = [
3022
"card",
3123
"dataset",
3224
"metric",
3325
"dashboard",
3426
"snippet",
3527
"collection",
36-
"indexed-entity",
3728
"document",
3829
"table",
3930
"transform",
@@ -42,6 +33,11 @@ export const COLLECTION_ITEM_MODELS = [
4233
"timeline",
4334
"no_models",
4435
] as const;
36+
export const CollectionItemFilterModel = z.enum(COLLECTION_ITEM_FILTER_MODELS);
37+
export type CollectionItemFilterModel = z.infer<typeof CollectionItemFilterModel>;
38+
39+
// `indexed-entity` is a valid response model but not accepted as a filter value.
40+
export const COLLECTION_ITEM_MODELS = [...COLLECTION_ITEM_FILTER_MODELS, "indexed-entity"] as const;
4541
export const CollectionItemModel = z.enum(COLLECTION_ITEM_MODELS);
4642
export type CollectionItemModel = z.infer<typeof CollectionItemModel>;
4743

src/domain/field.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,14 @@ export const fieldView: ResourceView<Field> = {
189189
],
190190
};
191191

192+
const NonBlankNullable = z.string().min(1).nullable();
193+
192194
export const FieldUpdateInput = z
193195
.object({
194196
display_name: z.string().min(1).optional(),
195-
description: z.string().nullable().optional(),
196-
caveats: z.string().nullable().optional(),
197-
points_of_interest: z.string().nullable().optional(),
197+
description: NonBlankNullable.optional(),
198+
caveats: NonBlankNullable.optional(),
199+
points_of_interest: NonBlankNullable.optional(),
198200
semantic_type: FieldSemanticType.nullable().optional(),
199201
coercion_strategy: FieldCoercionStrategy.nullable().optional(),
200202
fk_target_field_id: z.number().int().positive().nullable().optional(),

tests/e2e/card-query.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { CardQueryResult } from "../../src/domain/card";
2+
3+
type CompletedCardQueryResult = CardQueryResult & { data: NonNullable<CardQueryResult["data"]> };
4+
5+
export function assertCompletedQuery(
6+
result: CardQueryResult,
7+
): asserts result is CompletedCardQueryResult {
8+
if (result.status !== "completed") {
9+
throw new Error(`expected status "completed", got "${result.status}"`);
10+
}
11+
if (result.data === undefined) {
12+
throw new Error(`expected data to be defined; got: ${JSON.stringify(result)}`);
13+
}
14+
}

tests/e2e/card.e2e.test.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Card, CardCompact, CardQueryResult } from "../../src/domain/card";
66
import { parseJson } from "../../src/runtime/json";
77

88
import { readBootstrap, type E2EBootstrap } from "./bootstrap-data";
9+
import { assertCompletedQuery } from "./card-query";
910
import { cleanupConfigHome, mkTempConfigHome, runCli } from "./run-cli";
1011
import { E2E_CARDS, E2E_COLLECTIONS, E2E_DATABASES, E2E_TABLES } from "./seed/ids";
1112

@@ -196,9 +197,7 @@ describe("card e2e", () => {
196197

197198
expect(result.exitCode, result.stderr).toBe(0);
198199
const parsed = parseJson(result.stdout, CardQueryResult);
199-
if (parsed.status !== "completed") {
200-
throw new Error(`expected status "completed", got "${parsed.status}"`);
201-
}
200+
assertCompletedQuery(parsed);
202201
expect({
203202
status: parsed.status,
204203
row_count: parsed.row_count,
@@ -221,9 +220,7 @@ describe("card e2e", () => {
221220

222221
expect(result.exitCode, result.stderr).toBe(0);
223222
const parsed = parseJson(result.stdout, CardQueryResult);
224-
if (parsed.status !== "completed") {
225-
throw new Error(`expected status "completed", got "${parsed.status}"`);
226-
}
223+
assertCompletedQuery(parsed);
227224
expect({
228225
rowsLength: parsed.data.rows.length,
229226
row_count: parsed.row_count,

tests/e2e/query.e2e.test.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CardQueryResult } from "../../src/domain/card";
99
import { parseJson } from "../../src/runtime/json";
1010

1111
import { readBootstrap, type E2EBootstrap } from "./bootstrap-data";
12+
import { assertCompletedQuery } from "./card-query";
1213
import { cleanupConfigHome, mkTempConfigHome, runCli } from "./run-cli";
1314
import { E2E_DATABASES, E2E_TABLES } from "./seed/ids";
1415

@@ -203,11 +204,9 @@ describe("query e2e", () => {
203204

204205
expect(result.exitCode, result.stderr).toBe(0);
205206
const queryResult = parseJson(result.stdout, CardQueryResult);
206-
expect(queryResult.status).toBe("completed");
207-
if (queryResult.status === "completed") {
208-
expect(queryResult.row_count).toBe(3);
209-
expect(queryResult.data.rows).toHaveLength(3);
210-
}
207+
assertCompletedQuery(queryResult);
208+
expect(queryResult.row_count).toBe(3);
209+
expect(queryResult.data.rows).toHaveLength(3);
211210
});
212211

213212
it("run with a legacy native body skips MBQL 5 pre-flight and executes against /api/dataset", async () => {
@@ -225,11 +224,9 @@ describe("query e2e", () => {
225224

226225
expect(result.exitCode, result.stderr).toBe(0);
227226
const queryResult = parseJson(result.stdout, CardQueryResult);
228-
expect(queryResult.status).toBe("completed");
229-
if (queryResult.status === "completed") {
230-
expect(queryResult.row_count).toBe(1);
231-
expect(queryResult.data.rows).toEqual([[1, 2]]);
232-
}
227+
assertCompletedQuery(queryResult);
228+
expect(queryResult.row_count).toBe(1);
229+
expect(queryResult.data.rows).toEqual([[1, 2]]);
233230
});
234231

235232
it("--dry-run with a legacy native body returns ok and exits 0 (no schema applies)", async () => {
@@ -266,11 +263,9 @@ describe("query e2e", () => {
266263

267264
expect(result.exitCode, result.stderr).toBe(0);
268265
const queryResult = parseJson(result.stdout, CardQueryResult);
269-
expect(queryResult.status).toBe("completed");
270-
if (queryResult.status === "completed") {
271-
expect(queryResult.row_count).toBe(3);
272-
expect(queryResult.data.rows).toHaveLength(3);
273-
}
266+
assertCompletedQuery(queryResult);
267+
expect(queryResult.row_count).toBe(3);
268+
expect(queryResult.data.rows).toHaveLength(3);
274269
});
275270

276271
it("--dry-run with a legacy MBQL 4 body returns ok and exits 0 (server normalizes; no MBQL 5 schema applies)", async () => {

0 commit comments

Comments
 (0)