From 69095dad5ec3809d00e4eff8af8e9a8f324e043e Mon Sep 17 00:00:00 2001 From: Edon Gashi Date: Tue, 29 Nov 2022 11:12:47 +0100 Subject: [PATCH] Interface and types for example queries (cherry picked from commit b7b87bc23d5a1c94b286a97eace70d553582aa63) --- src/main/metabase/examples.ts | 66 +++++++++++++++++++++++ src/main/metabase/types.ts | 98 +++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/main/metabase/examples.ts create mode 100644 src/main/metabase/types.ts diff --git a/src/main/metabase/examples.ts b/src/main/metabase/examples.ts new file mode 100644 index 0000000..90658b5 --- /dev/null +++ b/src/main/metabase/examples.ts @@ -0,0 +1,66 @@ +import { Table } from './types'; + +/** An example query card. */ +type ExampleQuery = { + name: string; // Title of card. + sql: string; // SQL query. + sizeX: number; // Grid of 18 units wide. + sizeY: number; // Height of card in units. + display: 'table' | 'bar' | 'row' | 'scalar' | 'map'; // Other types TBD. + visualizationSettings: Record; // To be typed later. + + // There's also row/col properties, but we'll make some rectangle + // packing algorithm to arrange cards automatically in a section. +}; + +/** A section for a group of examples. */ +type ExamplesSection = { + title: string | null; // Markdown text as the section heading. + queries: ExampleQuery[]; // Cards in section. +}; + +function lines(...lines: string[]) { + return lines.join('\n'); +} + +export function exampleQueries(table: Table, aidColumns: string[]): ExamplesSection[] { + const { fields, display_name } = table; // TODO: iterate and inspect fields + + let name = table.name; + // if (requiresQuoting(name)) { + // name = `"${name}"` + // } + + // const t = getT('example-queries'); // Let's worry about i18n later... + + return [ + { + title: 'Overview', + queries: [ + { + name: `Count of ${display_name}`, + sql: lines('SELECT count(*)', `FROM ${name}`), + sizeX: 6, // 6 is a good default (3 cards per row). + sizeY: 4, // 4 is a good default. + display: 'scalar', + visualizationSettings: {}, // No visualizations for now. To be done after we finish SQL. + }, + ], + }, + { + // GROUP BY examples + title: `Distribution of ${display_name}`, + queries: [ + { + name: `${display_name} by `, + sql: lines('SELECT , count(*)', `FROM ${name}`, 'GROUP BY '), + sizeX: 6, + sizeY: 4, // For a table we might need something taller. + display: 'table', // For now we show results only as 'table'. + visualizationSettings: {}, + }, + ], + }, + // ... + ]; +} diff --git a/src/main/metabase/types.ts b/src/main/metabase/types.ts new file mode 100644 index 0000000..de0f258 --- /dev/null +++ b/src/main/metabase/types.ts @@ -0,0 +1,98 @@ +/* + * API response types extracted from sample responses and metabase source. + * Fields that we don't care about are commented out or marked as unknown. + */ + +type ISO8601Time = string; + +export type Table = { + description: string; + entity_type: string; + schema: string; + // db: unknown; + show_in_getting_started: boolean; + name: string; + fields: Field[]; + // caveats: unknown; + // segments: unknown[]; + dimension_options: Record; + updated_at: ISO8601Time; + active: boolean; + id: number; + db_id: number; + // visibility_type: unknown; + // field_order: unknown; + initial_sync_status: string; + display_name: string; + // metrics: unknown[]; + created_at: ISO8601Time; + // points_of_interest: unknown; +}; + +export type TextFieldFingerprint = { + 'percent-json': number; + 'percent-url': number; + 'percent-email': number; + 'percent-state': number; + 'average-length': number; +}; + +export type NumberFieldFingerprint = { + avg: number; + max: number; + min: number; + q1: number; + q3: number; + sd: number; +}; + +export type DateTimeFieldFingerprint = { + earliest: ISO8601Time; + latest: ISO8601Time; +}; + +export interface FieldFingerprint { + global: { + 'distinct-count'?: number; + 'nil%': number; + }; + type?: { + 'type/Text'?: TextFieldFingerprint; + 'type/Number'?: NumberFieldFingerprint; + 'type/DateTime'?: DateTimeFieldFingerprint; + }; +} + +export type Field = { + description: string | null; + database_type: string; // See https://github.com/metabase/metabase/blob/master/src/metabase/driver/postgres.clj#L504-L566 + semantic_type: string | null; // See https://github.com/metabase/metabase/blob/master/shared/src/metabase/types.cljc + // coercion_strategy: unknown; + name: string; + fingerprint_version: number; + // has_field_values: string; + // settings: unknown; + // caveats: unknown; + // fk_target_field_id: unknown; + // dimensions: unknown[]; + dimension_options: string[]; + updated_at: ISO8601Time; + // custom_position: number; + effective_type: string; + active: boolean; + // nfc_path: unknown; + // parent_id: unknown; + id: number; + last_analyzed: ISO8601Time; + position: number; + visibility_type: 'details-only' | 'hidden' | 'normal' | 'retired'; + // default_dimension_option: unknown; + // target: unknown; + preview_display: boolean; + display_name: string; + database_position: number; + fingerprint: FieldFingerprint; + created_at: ISO8601Time; + base_type: string; + // points_of_interest: unknown; +};