diff --git a/web-common/src/features/entity-management/entity-mappers.spec.ts b/web-common/src/features/entity-management/entity-mappers.spec.ts index 4b26370c865..c557979b8db 100644 --- a/web-common/src/features/entity-management/entity-mappers.spec.ts +++ b/web-common/src/features/entity-management/entity-mappers.spec.ts @@ -19,6 +19,24 @@ describe("entity-mappers", () => { expect(getNameFromFile("/path/to/data/adbids.csv.tgz")).toBe("adbids"); }); + it("keeps dots in YAML resource names", () => { + expect(getNameFromFile("/dashboards/dashboard.canvas.yaml")).toBe( + "dashboard.canvas", + ); + }); + + it("keeps dots in YML resource names", () => { + expect(getNameFromFile("/dashboards/dashboard.canvas.yml")).toBe( + "dashboard.canvas", + ); + }); + + it("keeps dots in SQL resource names", () => { + expect(getNameFromFile("/models/orders.latest.sql")).toBe( + "orders.latest", + ); + }); + it("no folder", () => { expect(getNameFromFile("adbids.csv")).toBe("adbids"); }); diff --git a/web-common/src/features/entity-management/entity-mappers.ts b/web-common/src/features/entity-management/entity-mappers.ts index 57bfcd968df..a71a4eab0b2 100644 --- a/web-common/src/features/entity-management/entity-mappers.ts +++ b/web-common/src/features/entity-management/entity-mappers.ts @@ -1,5 +1,6 @@ import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; import { EntityType } from "@rilldata/web-common/features/entity-management/types"; +import { RESOURCE_FILE_EXTENSIONS } from "./file-path-utils"; export function getFilePathFromPagePath(path: string): string { const pathSplits = path.split("/"); @@ -64,9 +65,20 @@ export function getFileAPIPathFromNameAndType( } export function getNameFromFile(fileName: string): string { - // TODO: do we need a library here? const splits = fileName.split("/"); - const extensionSplits = splits[splits.length - 1]?.split("."); + const basename = splits[splits.length - 1] ?? ""; + + // Rill resource names are inferred by removing only the final resource file + // extension, so dotted names like `dashboard.canvas.yaml` stay intact. + for (const extension of RESOURCE_FILE_EXTENSIONS) { + if (basename.endsWith(extension)) { + return basename.slice(0, -extension.length); + } + } + + // Non-resource data files keep the legacy behavior of removing compound + // extensions, e.g. `adbids.csv.tgz` -> `adbids`. + const extensionSplits = basename.split("."); return extensionSplits[0]; } diff --git a/web-common/src/features/entity-management/file-path-utils.ts b/web-common/src/features/entity-management/file-path-utils.ts index 9f8924e66ab..ac566322ced 100644 --- a/web-common/src/features/entity-management/file-path-utils.ts +++ b/web-common/src/features/entity-management/file-path-utils.ts @@ -1,5 +1,7 @@ const FILE_PATH_SPLIT_REGEX = /\//; +export const RESOURCE_FILE_EXTENSIONS = [".yaml", ".yml", ".sql"] as const; + export function extractFileName(filePath: string): string { let fileName = filePath.split(FILE_PATH_SPLIT_REGEX).slice(-1)[0]; const lastIndexOfDot = fileName.lastIndexOf("."); @@ -17,8 +19,14 @@ export function extractFileName(filePath: string): string { export function extractFileExtension(filePath: string): string { const fileName = filePath.split(FILE_PATH_SPLIT_REGEX).slice(-1)[0]; - const lastIndexOfDot = fileName.indexOf("."); - return lastIndexOfDot >= 0 ? fileName.substring(lastIndexOfDot) : ""; + + const resourceExtension = RESOURCE_FILE_EXTENSIONS.find((extension) => + fileName.endsWith(extension), + ); + if (resourceExtension) return resourceExtension; + + const firstIndexOfDot = fileName.indexOf("."); + return firstIndexOfDot >= 0 ? fileName.substring(firstIndexOfDot) : ""; } export function splitFolderAndFileName( diff --git a/web-common/src/features/entity-management/infer-resource-kind.spec.ts b/web-common/src/features/entity-management/infer-resource-kind.spec.ts index 11c23526d3f..d7b23f11e66 100644 --- a/web-common/src/features/entity-management/infer-resource-kind.spec.ts +++ b/web-common/src/features/entity-management/infer-resource-kind.spec.ts @@ -48,6 +48,18 @@ describe("inferResourceName", () => { `rows:\n type: invalid\ntype: canvas`, ResourceKind.Canvas, ], + [ + "implicit kind for dotted yaml", + "dashboards/dashboard.canvas.yaml", + `type: canvas\nrows: []`, + ResourceKind.Canvas, + ], + [ + "implicit kind for dotted sql", + "models/orders.latest.sql", + `select * from orders`, + ResourceKind.Model, + ], ]; testCases.forEach(([title, path, contents, expected]) => { diff --git a/web-common/src/features/sources/extract-table-name.spec.ts b/web-common/src/features/sources/extract-table-name.spec.ts index dffbceee866..beda8a9ff27 100644 --- a/web-common/src/features/sources/extract-table-name.spec.ts +++ b/web-common/src/features/sources/extract-table-name.spec.ts @@ -36,6 +36,9 @@ const TestCases = [ "table_v1_parquet", ".v1.parquet.gz", ), + ...generateTestCases("dashboard.canvas.yaml", "dashboard_canvas", ".yaml"), + ...generateTestCases("dashboard.canvas.yml", "dashboard_canvas", ".yml"), + ...generateTestCases("orders.latest.sql", "orders_latest", ".sql"), ]; describe("extract-table-name", () => {