From 857c88fa4fd14ec15706fc5571c2d978443c9ed6 Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Sat, 16 Sep 2023 12:50:06 +0300 Subject: [PATCH 1/8] (chore) Lint and type improvements --- .eslintrc | 32 +- .husky/pre-commit | 4 +- .prettierrc | 8 + e2e/fixtures/api.ts | 2 +- e2e/specs/publish-form.spec.ts | 5 +- e2e/specs/unpublish-form.spec.ts | 5 +- package.json | 64 +- .../action-buttons.component.tsx | 52 +- .../dashboard/dashboard.component.tsx | 54 +- src/components/dashboard/dashboard.test.tsx | 30 +- .../error-state/error-state.component.tsx | 2 +- .../form-editor/form-editor.component.tsx | 58 +- .../form-renderer/form-renderer.component.tsx | 17 +- .../add-question-modal.component.tsx | 139 +- .../delete-page-modal.component.tsx | 22 +- .../delete-question-modal.component.tsx | 24 +- .../delete-section-modal.component.tsx | 22 +- .../draggable-question.component.tsx | 6 +- .../droppable-container.component.tsx | 4 +- .../edit-question-modal.component.tsx | 219 +- .../editable-value.component.tsx | 4 +- .../interactive-builder.component.tsx | 114 +- .../new-form-modal.component.tsx | 26 +- .../page-modal.component.tsx | 24 +- .../interactive-builder/question-modal.scss | 7 + .../section-modal.component.tsx | 24 +- .../value-editor.component.tsx | 8 +- .../modals/save-form-modal.component.tsx | 129 +- .../pagination/pagination.component.tsx | 2 +- .../pagination/usePaginationInfo.ts | 2 +- .../schema-editor/schema-editor.component.tsx | 4 +- src/constants.ts | 3 - src/declarations.d.ts | 1 + src/forms.resource.ts | 57 +- src/hooks/useClobdata.ts | 7 +- src/hooks/useConceptLookup.ts | 4 +- src/hooks/useConceptName.ts | 4 +- src/hooks/useEncounterTypes.ts | 2 +- src/index.ts | 6 +- src/setup-tests.ts | 10 +- src/test-helpers.tsx | 13 +- src/types.ts | 17 +- translations/am.json | 10 +- translations/en.json | 10 +- translations/es.json | 10 +- translations/fr.json | 10 +- translations/he.json | 10 +- translations/km.json | 10 +- tsconfig.json | 14 +- yarn.lock | 4236 ++++++++++++----- 50 files changed, 3843 insertions(+), 1704 deletions(-) create mode 100644 .prettierrc delete mode 100644 src/constants.ts diff --git a/.eslintrc b/.eslintrc index dfbfb23..c79a831 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,16 +1,36 @@ { - "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint" - ], - "root": true, "extends": [ "eslint:recommended", "plugin:prettier/recommended", "plugin:@typescript-eslint/recommended", - "ts-react-important-stuff" + "plugin:@typescript-eslint/recommended-requiring-type-checking" ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": true, + "tsconfigRootDir": "__dirname" + }, + "plugins": ["@typescript-eslint"], + "root": true, "rules": { + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/consistent-type-imports": [ + "error", + { + "prefer": "type-imports", + "disallowTypeAnnotations": true + } + ], + "@typescript-eslint/array-type": [ + "error", + { + "default": "generic" + } + ], "no-restricted-imports": [ "error", { diff --git a/.husky/pre-commit b/.husky/pre-commit index 27e677d..d8fa0e4 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,6 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn pretty-quick --staged \ No newline at end of file +npx lint-staged +yarn turbo extract-translations + diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c4f7fca --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "bracketSpacing": true, + "printWidth": 80, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all" +} \ No newline at end of file diff --git a/e2e/fixtures/api.ts b/e2e/fixtures/api.ts index 5b6055d..d224a01 100644 --- a/e2e/fixtures/api.ts +++ b/e2e/fixtures/api.ts @@ -1,4 +1,4 @@ -import { +import type { APIRequestContext, PlaywrightWorkerArgs, WorkerFixture, diff --git a/e2e/specs/publish-form.spec.ts b/e2e/specs/publish-form.spec.ts index 2bd368b..880115a 100644 --- a/e2e/specs/publish-form.spec.ts +++ b/e2e/specs/publish-form.spec.ts @@ -28,7 +28,10 @@ test("Publish a form", async ({ page }) => { }); await test.step("And I click on a form I need to publish", async () => { - await page.getByRole('row', { name: form.name }).getByLabel('Edit Schema').click(); + await page + .getByRole("row", { name: form.name }) + .getByLabel("Edit Schema") + .click(); }); await test.step("Then I click on the publish form button", async () => { diff --git a/e2e/specs/unpublish-form.spec.ts b/e2e/specs/unpublish-form.spec.ts index f3d9bd7..303651b 100644 --- a/e2e/specs/unpublish-form.spec.ts +++ b/e2e/specs/unpublish-form.spec.ts @@ -28,7 +28,10 @@ test("Unpublish a form", async ({ page }) => { }); await test.step("And I click on a form I need to unpublish", async () => { - await page.getByRole('row', { name: form.name }).getByLabel('Edit Schema').click(); + await page + .getByRole("row", { name: form.name }) + .getByLabel("Edit Schema") + .click(); }); await test.step("Then I click on the unpublish form button and confirms the unpublication", async () => { diff --git a/package.json b/package.json index 50212b7..f85e222 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,15 @@ "browser": "dist/openmrs-esm-form-builder-app.js", "main": "src/index.ts", "source": true, + "lint-staged": { + "*.{js,jsx,ts,tsx}": "eslint --cache --fix" + }, "scripts": { "start": "openmrs develop", "serve": "webpack serve --mode=development", "build": "webpack --mode production", "analyze": "webpack --mode=production --env.analyze=true", - "lint": "TIMING=1 eslint src --ext js,jsx,ts,tsx", + "lint": "TIMING=1 eslint src --ext js,jsx,ts,tsx --max-warnings=0", "prettier": "prettier --write \"src/**/*.{ts,tsx}\"", "typescript": "tsc", "test": "jest --config jest.config.js", @@ -32,7 +35,6 @@ "keywords": [ "openmrs", "microfrontends", - "formbuilder", "form builder" ], "repository": { @@ -47,13 +49,13 @@ "url": "https://github.com/openmrs/openmrs-esm-form-builder/issues" }, "dependencies": { - "@carbon/react": "^1.31.0", + "@carbon/react": "^1.38.0", "@openmrs/openmrs-form-engine-lib": "next", - "dotenv": "^16.1.4", + "dotenv": "^16.3.1", "file-loader": "^6.2.0", "lodash-es": "^4.17.21", "react-ace": "^10.1.0", - "sass": "^1.63.3" + "sass": "^1.67.0" }, "peerDependencies": { "@openmrs/esm-framework": "*", @@ -69,44 +71,44 @@ "@dnd-kit/utilities": "^3.2.1", "@openmrs/esm-framework": "next", "@openmrs/esm-styleguide": "next", - "@playwright/test": "^1.35.0", + "@playwright/test": "^1.38.0", "@swc/cli": "^0.1.62", - "@swc/core": "^1.3.62", - "@swc/jest": "^0.2.26", - "@testing-library/dom": "^8.20.0", - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.5.2", - "@types/react": "^18.2.12", - "@types/react-dom": "^18.2.5", + "@swc/core": "^1.3.85", + "@swc/jest": "^0.2.29", + "@testing-library/dom": "^9.3.1", + "@testing-library/jest-dom": "^6.1.3", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.5.1", + "@types/jest": "^29.5.5", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", "@types/webpack-env": "^1.18.1", - "@typescript-eslint/eslint-plugin": "^5.59.11", - "@typescript-eslint/parser": "^5.59.11", + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", "css-loader": "^6.8.1", - "eslint": "^8.42.0", - "eslint-config-prettier": "^8.8.0", + "eslint": "^8.49.0", + "eslint-config-prettier": "^9.0.0", "eslint-config-ts-react-important-stuff": "^3.0.0", - "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-prettier": "^5.0.0", "husky": "^8.0.3", - "i18next": "^19.9.2", - "i18next-parser": "^5.4.0", + "i18next": "^23.5.1", + "i18next-parser": "^8.7.0", "identity-obj-proxy": "^3.0.0", - "jest": "^29.5.0", - "jest-cli": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", + "jest": "^29.7.0", + "jest-cli": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "lint-staged": "^14.0.1", "openmrs": "next", - "prettier": "^2.8.8", - "pretty-quick": "^3.1.3", + "prettier": "^3.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-error-boundary": "^4.0.10", - "react-i18next": "^11.18.6", + "react-error-boundary": "^4.0.11", + "react-i18next": "^13.2.2", "rxjs": "^7.8.1", "swc-loader": "^0.2.3", - "turbo": "^1.10.3", + "turbo": "^1.10.14", "typescript": "^4.9.5", - "webpack": "^5.86.0", + "webpack": "^5.88.2", "webpack-cli": "^5.1.4" }, "packageManager": "yarn@3.6.0" diff --git a/src/components/action-buttons/action-buttons.component.tsx b/src/components/action-buttons/action-buttons.component.tsx index 57ac4f9..9ed4371 100644 --- a/src/components/action-buttons/action-buttons.component.tsx +++ b/src/components/action-buttons/action-buttons.component.tsx @@ -7,20 +7,20 @@ import { ModalFooter, ModalHeader, } from "@carbon/react"; +import type { TFunction } from "i18next"; import { useParams } from "react-router-dom"; import { showToast, showNotification } from "@openmrs/esm-framework"; -import type { TFunction } from "react-i18next"; -import type { RouteParams, Schema } from "../../types"; +import type { Schema } from "../../types"; import { publishForm, unpublishForm } from "../../forms.resource"; import { useForm } from "../../hooks/useForm"; import SaveFormModal from "../modals/save-form-modal.component"; import styles from "./action-buttons.scss"; -type ActionButtonsProps = { +interface ActionButtonsProps { schema: Schema; t: TFunction; -}; +} type Status = | "idle" @@ -31,7 +31,7 @@ type Status = | "error"; function ActionButtons({ schema, t }: ActionButtonsProps) { - const { formUuid } = useParams(); + const { formUuid } = useParams<{ formUuid?: string }>(); const { form, mutate } = useForm(formUuid); const [status, setStatus] = useState("idle"); const [showUnpublishModal, setShowUnpublishModal] = useState(false); @@ -44,7 +44,6 @@ function ActionButtons({ schema, t }: ActionButtonsProps) { setStatus("publishing"); try { await publishForm(form.uuid); - showToast({ title: t("formPublished", "Form published"), kind: "success", @@ -55,20 +54,23 @@ function ActionButtons({ schema, t }: ActionButtonsProps) { }); setStatus("published"); - mutate(); + await mutate(); } catch (error) { - showNotification({ - title: t("errorPublishingForm", "Error publishing form"), - kind: "error", - critical: true, - description: error?.message, - }); - setStatus("error"); + if (error instanceof Error) { + showNotification({ + title: t("errorPublishingForm", "Error publishing form"), + kind: "error", + critical: true, + description: error?.message, + }); + setStatus("error"); + } } } async function handleUnpublish() { setStatus("unpublishing"); + try { await unpublishForm(form.uuid); @@ -82,15 +84,17 @@ function ActionButtons({ schema, t }: ActionButtonsProps) { }); setStatus("unpublished"); - mutate(); + await mutate(); } catch (error) { - showNotification({ - title: t("errorUnpublishingForm", "Error unpublishing form"), - kind: "error", - critical: true, - description: error?.message, - }); - setStatus("error"); + if (error instanceof Error) { + showNotification({ + title: t("errorUnpublishingForm", "Error unpublishing form"), + kind: "error", + critical: true, + description: error?.message, + }); + setStatus("error"); + } } setShowUnpublishModal(false); } @@ -136,14 +140,14 @@ function ActionButtons({ schema, t }: ActionButtonsProps) {

{t( "unpublishExplainerText", - "Unpublishing a form means you can no longer access it from your frontend. Unpublishing forms does not delete their associated schemas, it only affects whether or not you can access them in your frontend." + "Unpublishing a form means you can no longer access it from your frontend. Unpublishing forms does not delete their associated schemas, it only affects whether or not you can access them in your frontend.", )}

diff --git a/src/components/dashboard/dashboard.component.tsx b/src/components/dashboard/dashboard.component.tsx index d57e738..816984b 100644 --- a/src/components/dashboard/dashboard.component.tsx +++ b/src/components/dashboard/dashboard.component.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useMemo, useState } from "react"; -import { TFunction, useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; +import { useTranslation } from "react-i18next"; import { Button, ComposedModal, @@ -33,7 +34,7 @@ import { TrashCan, } from "@carbon/react/icons"; import { - FetchResponse, + type FetchResponse, navigate, showNotification, showToast, @@ -58,21 +59,21 @@ type Mutator = KeyedMutator<{ }; }>; -type ActionButtonsProps = { +interface ActionButtonsProps { form: FormType; mutate: Mutator; responsiveSize: string; t: TFunction; -}; +} -type FormsListProps = { +interface FormsListProps { forms: Array; isValidating: boolean; mutate: Mutator; t: TFunction; -}; +} -function CustomTag({ condition }: { condition: boolean }) { +function CustomTag({ condition }: { condition?: boolean }) { const { t } = useTranslation(); if (condition) { @@ -106,13 +107,13 @@ function ActionButtons({ new Blob([JSON.stringify(clobdata, null, 2)], { type: "application/json", }), - [clobdata] + [clobdata], ); const handleDeleteForm = useCallback( (formUuid: string) => { deleteForm(formUuid) - .then((res: FetchResponse) => { + .then(async (res: FetchResponse) => { if (res.status === 204) { showToast({ title: t("formDeleted", "Form deleted"), @@ -123,7 +124,7 @@ function ActionButtons({ t("formDeletedSuccessfully", "deleted successfully"), }); - mutate(); + await mutate(); setShowDeleteFormModal(false); } }) @@ -133,11 +134,11 @@ function ActionButtons({ kind: "error", critical: true, description: e?.message, - }) + }), ) .finally(() => setIsDeleting(false)); }, - [form.name, mutate, t] + [form.name, mutate, t], ); const ImportButton = () => { @@ -217,12 +218,14 @@ function ActionButtons({ preventCloseOnClickOutside > -
event.preventDefault()}> + event.preventDefault()} + >

{t( "deleteFormConfirmation", - "Are you sure you want to delete this form?" + "Are you sure you want to delete this form?", )}

@@ -267,7 +270,7 @@ function ActionButtons({ )} - {setShowDeleteFormModal && } + {showDeleteFormModal && } ); } @@ -322,7 +325,7 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { const searchResults = useMemo(() => { if (searchString && searchString.trim() !== "") { return filteredRows.filter((form) => - form.name.toLowerCase().includes(searchString.toLowerCase()) + form.name.toLowerCase().includes(searchString.toLowerCase()), ); } @@ -331,14 +334,14 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { const { paginated, goTo, results, currentPage } = usePagination( searchResults, - pageSize + pageSize, ); const tableRows = results?.map((form: FormType) => ({ ...form, id: form?.uuid, - published: , - retired: , + published: , + retired: , actions: ( setFilter(selectedItem); const handleSearch = useCallback( - (e) => { + (e: React.ChangeEvent) => { goTo(1); setSearchString(e.target.value); }, - [goTo, setSearchString] + [goTo, setSearchString], ); return ( @@ -372,7 +375,7 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { lowContrast title={t( "schemaSaveWarningMessage", - "The dev3 server is ephemeral at best and can't be relied upon to save your schemas permanently. To avoid losing your work, please save your schemas to your local machine. Alternatively, upload your schema to the distro repo to have it persisted across server resets." + "The dev3 server is ephemeral at best and can't be relied upon to save your schemas permanently. To avoid losing your work, please save your schemas to your local machine. Alternatively, upload your schema to the distro repo to have it persisted across server resets.", )} /> )} @@ -421,7 +424,7 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) {