From 22c19cb618b23fbfbf114cd9c29031f48b99ebb0 Mon Sep 17 00:00:00 2001
From: Anton Kulyk <kuliks.anton@gmail.com>
Date: Thu, 5 Jan 2023 18:52:03 +0000
Subject: [PATCH] Improve sample state fixture (#27399)

* Remove not used `StaticEntitiesProvider`

* Fix typos

* Convert sample database fixture to TypeScript

* Fix `EnhancedState` typing

* Remove global eslint comment

* Fix invalid import

* Fix incorrect type
---
 .../src/metabase-lib/metadata/Database.ts     |   8 +-
 frontend/src/metabase-lib/metadata/Field.ts   |  12 ++-
 .../parameters/utils/targets.unit.spec.ts     |   3 -
 .../utils/structured-query-table.unit.spec.ts |   4 +-
 .../queries/utils/virtual-table.unit.spec.ts  |   7 +-
 frontend/src/metabase-types/api/field.ts      |   8 +-
 frontend/src/metabase-types/store/entities.ts |   3 +
 .../TableInfo/TableInfo.unit.spec.tsx         |   4 +-
 .../actions/core/initializeQB.unit.spec.ts    |   6 +-
 .../actions/core/updateQuestion.unit.spec.ts  |   3 +-
 .../DataSelectorFieldPicker.tsx               |   9 +-
 .../DataSelectorFieldPicker.unit.spec.tsx     |   4 +-
 .../ModelCacheManagementSection.unit.spec.tsx |   2 +-
 ..._fixture.js => sample_database_fixture.ts} | 102 +++++++++++++-----
 .../ChartSettingOrderedColumns.unit.spec.js   |   3 +-
 tsconfig.json                                 |   3 +-
 16 files changed, 123 insertions(+), 58 deletions(-)
 rename frontend/test/__support__/{sample_database_fixture.js => sample_database_fixture.ts} (55%)

diff --git a/frontend/src/metabase-lib/metadata/Database.ts b/frontend/src/metabase-lib/metadata/Database.ts
index d5eed628b78..678ada5795d 100644
--- a/frontend/src/metabase-lib/metadata/Database.ts
+++ b/frontend/src/metabase-lib/metadata/Database.ts
@@ -1,6 +1,10 @@
 // eslint-disable-next-line @typescript-eslint/ban-ts-comment
 // @ts-nocheck
-import { Database as IDatabase, NativePermissions } from "metabase-types/api";
+import {
+  Database as IDatabase,
+  NativePermissions,
+  StructuredQuery,
+} from "metabase-types/api";
 import { generateSchemaId } from "metabase-lib/metadata/utils/schema";
 import { createLookupByProperty, memoizeClass } from "metabase-lib/utils";
 import Question from "../Question";
@@ -127,7 +131,7 @@ class DatabaseInner extends Base {
   }
 
   question(
-    query = {
+    query: StructuredQuery = {
       "source-table": null,
     },
   ) {
diff --git a/frontend/src/metabase-lib/metadata/Field.ts b/frontend/src/metabase-lib/metadata/Field.ts
index 348cecc0c79..df429c93004 100644
--- a/frontend/src/metabase-lib/metadata/Field.ts
+++ b/frontend/src/metabase-lib/metadata/Field.ts
@@ -4,7 +4,11 @@ import _ from "underscore";
 import moment from "moment-timezone";
 
 import { formatField, stripId } from "metabase/lib/formatting";
-import type { FieldFingerprint } from "metabase-types/api/field";
+import type {
+  DatasetColumn,
+  Field as IField,
+  FieldFingerprint,
+} from "metabase-types/api";
 import type { Field as FieldRef } from "metabase-types/types/Query";
 import {
   isDate,
@@ -71,6 +75,10 @@ class FieldInner extends Base {
   // added when creating "virtual fields" that are associated with a given query
   query?: StructuredQuery | NativeQuery;
 
+  getPlainObject(): IField {
+    return this._plainObject;
+  }
+
   getId() {
     if (Array.isArray(this.id)) {
       return this.id[1];
@@ -436,7 +444,7 @@ class FieldInner extends Base {
     return this.isString();
   }
 
-  column(extra = {}) {
+  column(extra = {}): DatasetColumn {
     return this.dimension().column({
       source: "fields",
       ...extra,
diff --git a/frontend/src/metabase-lib/parameters/utils/targets.unit.spec.ts b/frontend/src/metabase-lib/parameters/utils/targets.unit.spec.ts
index f86e9e2ddf2..d8270673e65 100644
--- a/frontend/src/metabase-lib/parameters/utils/targets.unit.spec.ts
+++ b/frontend/src/metabase-lib/parameters/utils/targets.unit.spec.ts
@@ -72,7 +72,6 @@ describe("parameters/utils/targets", () => {
 
   describe("getParameterTargetField", () => {
     it("should return null when the target is not a dimension", () => {
-      // @ts-expect-error - SAMPLE_DATABASE is defined
       const question = SAMPLE_DATABASE.nativeQuestion({
         query: "select * from PRODUCTS where CATEGORY = {{foo}}",
         "template-tags": {
@@ -96,7 +95,6 @@ describe("parameters/utils/targets", () => {
         "dimension",
         ["template-tag", "foo"],
       ];
-      // @ts-expect-error - SAMPLE_DATABASE is defined
       const question = SAMPLE_DATABASE.nativeQuestion({
         query: "select * from PRODUCTS where {{foo}}",
         "template-tags": {
@@ -119,7 +117,6 @@ describe("parameters/utils/targets", () => {
         "dimension",
         ["field", PRODUCTS.CATEGORY.id, null],
       ];
-      // @ts-expect-error - SAMPLE_DATABASE is defined
       const question = SAMPLE_DATABASE.question({
         "source-table": PRODUCTS.id,
       });
diff --git a/frontend/src/metabase-lib/queries/utils/structured-query-table.unit.spec.ts b/frontend/src/metabase-lib/queries/utils/structured-query-table.unit.spec.ts
index 0c80d5aaafc..bf8613e2766 100644
--- a/frontend/src/metabase-lib/queries/utils/structured-query-table.unit.spec.ts
+++ b/frontend/src/metabase-lib/queries/utils/structured-query-table.unit.spec.ts
@@ -137,7 +137,9 @@ describe("metabase-lib/queries/utils/structured-query-table", () => {
 
     metadata.tables[ORDERS_DATASET_TABLE.id] = ORDERS_DATASET_TABLE;
 
-    const table = getStructuredQueryTable(ORDERS_DATASET.query());
+    const table = getStructuredQueryTable(
+      ORDERS_DATASET.query() as StructuredQuery,
+    );
     it("should return a nested card table using the given query's question", () => {
       expect(table?.getPlainObject()).toEqual(
         expect.objectContaining({
diff --git a/frontend/src/metabase-lib/queries/utils/virtual-table.unit.spec.ts b/frontend/src/metabase-lib/queries/utils/virtual-table.unit.spec.ts
index 041a8c151fc..43b88d04458 100644
--- a/frontend/src/metabase-lib/queries/utils/virtual-table.unit.spec.ts
+++ b/frontend/src/metabase-lib/queries/utils/virtual-table.unit.spec.ts
@@ -1,10 +1,13 @@
 import { metadata, PRODUCTS } from "__support__/sample_database_fixture";
+
+import StructuredQuery from "metabase-lib/queries/StructuredQuery";
 import Field from "metabase-lib/metadata/Field";
 import Table from "metabase-lib/metadata/Table";
+
 import { createVirtualField, createVirtualTable } from "./virtual-table";
 
 describe("metabase-lib/queries/utils/virtual-table", () => {
-  const query = PRODUCTS.newQuestion().query();
+  const query = PRODUCTS.newQuestion().query() as StructuredQuery;
   const field = createVirtualField({
     id: 123,
     metadata,
@@ -28,7 +31,7 @@ describe("metabase-lib/queries/utils/virtual-table", () => {
   });
 
   describe("createVirtualTable", () => {
-    const query = PRODUCTS.newQuestion().query();
+    const query = PRODUCTS.newQuestion().query() as StructuredQuery;
     const field1 = createVirtualField({
       id: 1,
       metadata,
diff --git a/frontend/src/metabase-types/api/field.ts b/frontend/src/metabase-types/api/field.ts
index 851383d7de0..caeba662f68 100644
--- a/frontend/src/metabase-types/api/field.ts
+++ b/frontend/src/metabase-types/api/field.ts
@@ -50,8 +50,8 @@ export type FieldDimension = {
   name: string;
 };
 
-export interface Field {
-  id?: FieldId;
+export interface ConcreteField {
+  id: FieldId;
   table_id: TableId;
 
   name: string;
@@ -85,3 +85,7 @@ export interface Field {
   created_at: string;
   updated_at: string;
 }
+
+export type Field = Omit<ConcreteField, "id"> & {
+  id?: FieldId;
+};
diff --git a/frontend/src/metabase-types/store/entities.ts b/frontend/src/metabase-types/store/entities.ts
index 28afe08c047..5726a5f5b8f 100644
--- a/frontend/src/metabase-types/store/entities.ts
+++ b/frontend/src/metabase-types/store/entities.ts
@@ -2,6 +2,8 @@ import {
   Collection,
   CollectionId,
   Database,
+  Field,
+  FieldId,
   NativeQuerySnippet,
   NativeQuerySnippetId,
   Table,
@@ -12,6 +14,7 @@ import {
 export interface EntitiesState {
   collections?: Record<CollectionId, Collection>;
   databases?: Record<number, Database>;
+  fields?: Record<FieldId, Field>;
   tables?: Record<number | string, Table>;
   snippets?: Record<NativeQuerySnippetId, NativeQuerySnippet>;
   users?: Record<UserId, User>;
diff --git a/frontend/src/metabase/components/MetadataInfo/TableInfo/TableInfo.unit.spec.tsx b/frontend/src/metabase/components/MetadataInfo/TableInfo/TableInfo.unit.spec.tsx
index cdab9a3e361..5fb7e4e8321 100644
--- a/frontend/src/metabase/components/MetadataInfo/TableInfo/TableInfo.unit.spec.tsx
+++ b/frontend/src/metabase/components/MetadataInfo/TableInfo/TableInfo.unit.spec.tsx
@@ -107,7 +107,9 @@ describe("TableInfo", () => {
     });
 
     it("should display the given table's description", () => {
-      expect(screen.getByText(PRODUCTS.description)).toBeInTheDocument();
+      expect(
+        screen.getByText(PRODUCTS.description as string),
+      ).toBeInTheDocument();
     });
 
     it("should show a count of columns on the table", () => {
diff --git a/frontend/src/metabase/query_builder/actions/core/initializeQB.unit.spec.ts b/frontend/src/metabase/query_builder/actions/core/initializeQB.unit.spec.ts
index b3d83c067a8..bc28c8ab22f 100644
--- a/frontend/src/metabase/query_builder/actions/core/initializeQB.unit.spec.ts
+++ b/frontend/src/metabase/query_builder/actions/core/initializeQB.unit.spec.ts
@@ -9,7 +9,7 @@ import Databases from "metabase/entities/databases";
 import Snippets from "metabase/entities/snippets";
 import { setErrorPage } from "metabase/redux/app";
 
-import { User } from "metabase-types/api";
+import { DatabaseId, TableId, User } from "metabase-types/api";
 import { createMockUser } from "metabase-types/api/mocks";
 import { Card, NativeDatasetQuery } from "metabase-types/types/Card";
 import { TemplateTag } from "metabase-types/types/Query";
@@ -648,8 +648,8 @@ describe("QB Actions > initializeQB", () => {
 
   describe("blank question", () => {
     type BlankSetupOpts = Omit<BaseSetupOpts, "location" | "params"> & {
-      db?: number;
-      table?: number;
+      db?: DatabaseId;
+      table?: TableId;
       segment?: number;
       metric?: number;
     };
diff --git a/frontend/src/metabase/query_builder/actions/core/updateQuestion.unit.spec.ts b/frontend/src/metabase/query_builder/actions/core/updateQuestion.unit.spec.ts
index ee0c842bf0e..60679a9fb05 100644
--- a/frontend/src/metabase/query_builder/actions/core/updateQuestion.unit.spec.ts
+++ b/frontend/src/metabase/query_builder/actions/core/updateQuestion.unit.spec.ts
@@ -19,7 +19,6 @@ import Question from "metabase-lib/Question";
 import NativeQuery from "metabase-lib/queries/NativeQuery";
 import StructuredQuery from "metabase-lib/queries/StructuredQuery";
 import Join from "metabase-lib/queries/structured/Join";
-import Field from "metabase-lib/metadata/Field";
 import {
   getAdHocQuestion,
   getSavedStructuredQuestion,
@@ -76,7 +75,7 @@ async function setup({
 
   const queryResult = createMockDataset({
     data: {
-      cols: ORDERS.fields.map((field: Field) => field.column()),
+      cols: ORDERS.fields.map(field => field.column()),
     },
   });
 
diff --git a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.tsx b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.tsx
index 86547965968..cece699a26c 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.tsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.tsx
@@ -3,8 +3,8 @@ import { t } from "ttag";
 
 import AccordionList from "metabase/core/components/AccordionList";
 import Icon from "metabase/components/Icon";
-import type { Field } from "metabase-types/api/field";
 import type { Table } from "metabase-types/api/table";
+import type Field from "metabase-lib/metadata/Field";
 import DataSelectorLoading from "../DataSelectorLoading";
 
 import {
@@ -31,10 +31,7 @@ type HeaderProps = {
 
 type FieldWithName = {
   name: string;
-  field: {
-    id: number;
-    dimension: () => any;
-  };
+  field: Field;
 };
 
 const DataSelectorFieldPicker = ({
@@ -57,7 +54,7 @@ const DataSelectorFieldPicker = ({
     {
       name: header,
       items: fields.map(field => ({
-        name: field.display_name,
+        name: field.displayName(),
         field: field,
       })),
     },
diff --git a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.unit.spec.tsx b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.unit.spec.tsx
index c49075731f6..82822d3a119 100644
--- a/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.unit.spec.tsx
+++ b/frontend/src/metabase/query_builder/components/DataSelector/DataSelectorFieldPicker/DataSelectorFieldPicker.unit.spec.tsx
@@ -57,13 +57,11 @@ describe("DataSelectorFieldPicker", () => {
         display_name: tableDisplayName,
       };
 
-      const fields = [ORDERS.PRODUCT_ID];
-
       render(
         <DataSelectorFieldPicker
           {...props}
           selectedTable={selectedTable as Table}
-          fields={fields}
+          fields={[ORDERS.PRODUCT_ID]}
         />,
       );
 
diff --git a/frontend/src/metabase/query_builder/components/view/sidebars/ModelCacheManagementSection/ModelCacheManagementSection.unit.spec.tsx b/frontend/src/metabase/query_builder/components/view/sidebars/ModelCacheManagementSection/ModelCacheManagementSection.unit.spec.tsx
index ebdaabf2571..066a5bfb6cf 100644
--- a/frontend/src/metabase/query_builder/components/view/sidebars/ModelCacheManagementSection/ModelCacheManagementSection.unit.spec.tsx
+++ b/frontend/src/metabase/query_builder/components/view/sidebars/ModelCacheManagementSection/ModelCacheManagementSection.unit.spec.tsx
@@ -35,7 +35,7 @@ async function setup({
   const modelCacheInfo = getMockModelCacheInfo({
     ...cacheInfo,
     card_id: model.id(),
-    card_name: model.displayName(),
+    card_name: model.displayName() as string,
   });
 
   const onRefreshMock = jest
diff --git a/frontend/test/__support__/sample_database_fixture.js b/frontend/test/__support__/sample_database_fixture.ts
similarity index 55%
rename from frontend/test/__support__/sample_database_fixture.js
rename to frontend/test/__support__/sample_database_fixture.ts
index 442dc28fbdd..10df7712d7a 100644
--- a/frontend/test/__support__/sample_database_fixture.js
+++ b/frontend/test/__support__/sample_database_fixture.ts
@@ -1,15 +1,22 @@
-/* eslint-disable react/prop-types */
-import React from "react";
-import { Provider } from "react-redux";
 import { normalize } from "normalizr";
 import { chain } from "icepick";
-import { getStore } from "metabase/store";
 
 import { getMetadata } from "metabase/selectors/metadata";
 import { FieldSchema } from "metabase/schema";
 
-import state from "./sample_database_fixture.json";
-export { default as state } from "./sample_database_fixture.json";
+import type { Field as IField, FieldId } from "metabase-types/api";
+import type { State } from "metabase-types/store";
+
+import type Database from "metabase-lib/metadata/Database";
+import type Field from "metabase-lib/metadata/Field";
+import type Metadata from "metabase-lib/metadata/Metadata";
+import type Table from "metabase-lib/metadata/Table";
+
+import stateFixture from "./sample_database_fixture.json";
+
+export const state = stateFixture as unknown as State;
+
+export default state;
 
 export const SAMPLE_DATABASE_ID = 1;
 export const ANOTHER_DATABASE_ID = 2;
@@ -19,31 +26,45 @@ export const OTHER_MULTI_SCHEMA_DATABASE_ID = 5;
 
 export const MAIN_METRIC_ID = 1;
 
-function aliasTablesAndFields(metadata) {
-  // alias DATABASE.TABLE.FIELD for convienence in tests
+function aliasTablesAndFields(metadata: Metadata) {
+  // alias DATABASE.TABLE.FIELD for convenience in tests
   // NOTE: this assume names don't conflict with other properties in Database/Table which I think is safe for Sample Database
+  /* eslint-disable @typescript-eslint/ban-ts-comment */
   for (const database of Object.values(metadata.databases)) {
     for (const table of database.tables) {
       if (!(table.name in database)) {
+        // @ts-ignore
         database[table.name] = table;
       }
       for (const field of table.fields) {
         if (!(field.name in table)) {
+          // @ts-ignore
           table[field.name] = field;
         }
       }
     }
   }
+  /* eslint-enable @typescript-eslint/ban-ts-comment */
 }
 
-function normalizeFields(fields) {
+function normalizeFields(fields: Record<string, IField>) {
   return normalize(fields, [FieldSchema]).entities.fields || {};
 }
 
-export function createMetadata(updateState = state => state) {
+// Icepick doesn't expose it's IcepickWrapper type,
+// so this trick pulls it out of the return type of chain()
+// `icepickChainWrapper` is needed because typeof chain<State> doesn't work
+// See: https://stackoverflow.com/questions/50321419/typescript-returntype-of-generic-function
+const icepickChainWrapper = (state: State) => chain(state);
+type EnhancedState = ReturnType<typeof icepickChainWrapper>;
+
+export function createMetadata(updateState = (state: EnhancedState) => state) {
+  // This allows to use icepick helpers inside custom `updateState` functions
+  // Example: const metadata = createMetadata(state => state.assocIn(...))
   const stateModified = updateState(chain(state)).thaw().value();
+
   stateModified.entities.fields = normalizeFields(
-    stateModified.entities.fields,
+    stateModified.entities.fields || {},
   );
 
   const metadata = getMetadata(stateModified);
@@ -53,22 +74,55 @@ export function createMetadata(updateState = state => state) {
 
 export const metadata = createMetadata();
 
-export const SAMPLE_DATABASE = metadata.database(SAMPLE_DATABASE_ID);
-export const ANOTHER_DATABASE = metadata.database(ANOTHER_DATABASE_ID);
-export const MONGO_DATABASE = metadata.database(MONGO_DATABASE_ID);
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+
+/**
+ * In the wild, fields might not have a concrete ID
+ * (e.g. when coming from a native query)
+ * But for our sample data we can be sure that they're always concrete.
+ */
+type SimpleField = Omit<Field, "id"> & {
+  id: FieldId;
+};
+
+type AliasedTable = Table & {
+  [fieldName: string]: SimpleField;
+};
+
+/**
+ * Databases below are extended with table aliases.
+ * So it's possible to do SAMPLE_DATABASE.ORDERS or SAMPLE_DATABASE.ORDERS.TOTAL
+ * to retrieve tables and field instances.
+ */
+type AliasedSampleDatabase = Database & {
+  ORDERS: AliasedTable;
+  PRODUCTS: AliasedTable;
+  PEOPLE: AliasedTable;
+  REVIEWS: AliasedTable;
+};
+
+export const SAMPLE_DATABASE = metadata.database(
+  SAMPLE_DATABASE_ID,
+) as AliasedSampleDatabase;
+
+export const ANOTHER_DATABASE = metadata.database(ANOTHER_DATABASE_ID)!;
+export const MONGO_DATABASE = metadata.database(MONGO_DATABASE_ID)!;
 export const MULTI_SCHEMA_DATABASE = metadata.database(
   MULTI_SCHEMA_DATABASE_ID,
-);
+)!;
 export const OTHER_MULTI_SCHEMA_DATABASE = metadata.database(
   OTHER_MULTI_SCHEMA_DATABASE_ID,
-);
+)!;
+/* eslint-enable @typescript-eslint/no-non-null-assertion */
 
 export const ORDERS = SAMPLE_DATABASE.ORDERS;
 export const PRODUCTS = SAMPLE_DATABASE.PRODUCTS;
 export const PEOPLE = SAMPLE_DATABASE.PEOPLE;
 export const REVIEWS = SAMPLE_DATABASE.REVIEWS;
 
-export function makeMetadata(metadata) {
+export function makeMetadata(
+  metadata: Record<string, Record<string, any>>,
+): Metadata {
   metadata = {
     databases: {
       1: { name: "database", tables: [] },
@@ -90,7 +144,8 @@ export function makeMetadata(metadata) {
     questions: {},
     ...metadata,
   };
-  // convienence for filling in missing bits
+
+  // convenience for filling in missing bits
   for (const objects of Object.values(metadata)) {
     for (const [id, object] of Object.entries(objects)) {
       object.id = /^\d+$/.test(id) ? parseInt(id) : id;
@@ -99,6 +154,7 @@ export function makeMetadata(metadata) {
       }
     }
   }
+
   // linking to default db
   for (const table of Object.values(metadata.tables)) {
     if (table.db == null) {
@@ -107,6 +163,7 @@ export function makeMetadata(metadata) {
       (db0.tables = db0.tables || []).push(table.id);
     }
   }
+
   // linking to default table
   for (const childType of ["fields", "segments", "metrics"]) {
     for (const child of Object.values(metadata[childType])) {
@@ -122,12 +179,3 @@ export function makeMetadata(metadata) {
 
   return getMetadata({ entities: metadata });
 }
-
-const nopEntitiesReducer = (s = state.entities, a) => s;
-
-// simple provider which only supports static metadata defined above, no actions will take effect
-export const StaticEntitiesProvider = ({ children }) => (
-  <Provider store={getStore({ entities: nopEntitiesReducer }, null, state)}>
-    {children}
-  </Provider>
-);
diff --git a/frontend/test/metabase/visualizations/components/settings/ChartSettingOrderedColumns.unit.spec.js b/frontend/test/metabase/visualizations/components/settings/ChartSettingOrderedColumns.unit.spec.js
index 9d0a9851186..62b97a34a87 100644
--- a/frontend/test/metabase/visualizations/components/settings/ChartSettingOrderedColumns.unit.spec.js
+++ b/frontend/test/metabase/visualizations/components/settings/ChartSettingOrderedColumns.unit.spec.js
@@ -1,8 +1,7 @@
 import React from "react";
 import { render, screen, fireEvent } from "@testing-library/react";
-
+import { ORDERS } from "__support__/sample_database_fixture";
 import ChartSettingOrderedColumns from "metabase/visualizations/components/settings/ChartSettingOrderedColumns";
-import { ORDERS } from "__support__/sample_database_fixture.js";
 
 function renderChartSettingOrderedColumns(props) {
   render(
diff --git a/tsconfig.json b/tsconfig.json
index 660542786b1..1b838e9bf0f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -20,7 +20,8 @@
     "allowJs": true,
     "esModuleInterop": true,
     "experimentalDecorators": true,
-    "forceConsistentCasingInFileNames": true
+    "forceConsistentCasingInFileNames": true,
+    "resolveJsonModule": true
   },
   "include": [
     "frontend/src/**/*.ts",
-- 
GitLab