diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b32fbdc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# EditorConfig https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 120 +trim_trailing_whitespace = true diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a582342..378a872 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -17,5 +17,7 @@ jobs: node-version: "18" - name: Install dependencies run: yarn --immutable + - name: Run checks + run: yarn lint - name: Run tests run: yarn test diff --git a/.prettierignore b/.prettierignore index 61c3bc7..301cb0c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ +.vscode .yarn diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 21c6d0f..0000000 --- a/.prettierrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "printWidth": 80, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": false, - "trailingComma": "es5", - "bracketSpacing": true, - "bracketSameLine": false, - "arrowParens": "avoid" -} diff --git a/README.md b/README.md index 122a313..b5509fe 100644 --- a/README.md +++ b/README.md @@ -104,10 +104,7 @@ or to a list of homeservers: ```json { - "restrictBaseUrl": [ - "https://your-first-matrix-server.example.com", - "https://your-second-matrix-server.example.com" - ] + "restrictBaseUrl": ["https://your-first-matrix-server.example.com", "https://your-second-matrix-server.example.com"] } ``` @@ -166,5 +163,6 @@ services: ## Development - See https://yarnpkg.com/getting-started/editor-sdks how to setup your IDE -- Use `yarn test` to run all style, lint and unit tests +- Use `yarn lint` to run all style and linter checks +- Use `yarn test` to run all unit tests - Use `yarn fix` to fix the coding style diff --git a/package.json b/package.json index ace2a2a..ad54db4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "packageManager": "yarn@4.1.1", "devDependencies": { + "@eslint/js": "^9.1.1", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.2", @@ -21,11 +22,16 @@ "@types/node": "^20.12.7", "@types/papaparse": "^5.3.14", "@types/react": "^18.2.79", + "@typescript-eslint/eslint-plugin": "^7.7.1", + "@typescript-eslint/parser": "^7.7.1", "@vitejs/plugin-react": "^4.0.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "eslint-config-react-app": "^7.0.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-unused-imports": "^3.1.0", + "eslint-plugin-yaml": "^0.5.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.3", @@ -34,6 +40,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.4.5", + "typescript-eslint": "^7.7.1", "vite": "^5.0.0", "vite-plugin-version-mark": "^0.0.13" }, @@ -66,17 +73,79 @@ "scripts": { "start": "vite serve", "build": "vite build", - "fix:other": "yarn prettier --write", - "fix:code": "yarn test:lint --fix", - "fix": "yarn fix:code && yarn fix:other", - "prettier": "prettier \"**/*.{js,jsx,ts,tsx,json,md,scss,yaml,yml}\"", - "test:code": "jest", - "test:lint": "eslint --ignore-path .gitignore --ext .js,.jsx,.ts,.jsx .", - "test:style": "yarn prettier --check", - "test": "yarn test:style && yarn test:lint && yarn test:code" + "lint": "eslint --ignore-path .gitignore --ext .ts,.tsx,.yml,.yaml .", + "fix": "yarn lint --fix", + "test": "yarn jest", + "test:watch": "yarn jest --watch" }, "eslintConfig": { - "extends": "react-app" + "env": { + "browser": true + }, + "plugins": [ + "import", + "prettier", + "unused-imports", + "@typescript-eslint", + "yaml" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/stylistic", + "plugin:import/typescript", + "plugin:yaml/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.eslint.json" + }, + "root": true, + "rules": { + "prettier/prettier": "error", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": [ + "**/vite.config.ts", + "**/jest.setup.ts", + "**/*.test.ts", + "**/*.test.tsx" + ] + } + ], + "import/order": [ + "error", + { + "alphabetize": { + "order": "asc", + "caseInsensitive": false + }, + "newlines-between": "always", + "groups": [ + "external", + "builtin", + "internal", + [ + "parent", + "sibling", + "index" + ] + ] + } + ], + "unused-imports/no-unused-imports-ts": 2 + } + }, + "prettier": { + "printWidth": 120, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "avoid" }, "browserslist": { "production": [ diff --git a/src/App.test.tsx b/src/App.test.tsx index ce2f3e0..3a82f7a 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,4 +1,5 @@ import { render, screen } from "@testing-library/react"; + import App from "./App"; describe("App", () => { diff --git a/src/App.tsx b/src/App.tsx index b58f453..b39ea3d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,28 +1,25 @@ -import { - Admin, - CustomRoutes, - Resource, - resolveBrowserLocale, -} from "react-admin"; -import polyglotI18nProvider from "ra-i18n-polyglot"; import { merge } from "lodash"; -import authProvider from "./synapse/authProvider"; -import dataProvider from "./synapse/dataProvider"; -import users from "./components/users"; -import rooms from "./components/rooms"; -import userMediaStats from "./components/statistics"; +import polyglotI18nProvider from "ra-i18n-polyglot"; + +import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin"; +import { Route } from "react-router-dom"; + import reports from "./components/EventReports"; +import { ImportFeature } from "./components/ImportFeature"; +import LoginPage from "./components/LoginPage"; +import registrationToken from "./components/RegistrationTokens"; import roomDirectory from "./components/RoomDirectory"; import destinations from "./components/destinations"; -import registrationToken from "./components/RegistrationTokens"; -import LoginPage from "./components/LoginPage"; -import { ImportFeature } from "./components/ImportFeature"; -import { Route } from "react-router-dom"; +import rooms from "./components/rooms"; +import userMediaStats from "./components/statistics"; +import users from "./components/users"; import germanMessages from "./i18n/de"; import englishMessages from "./i18n/en"; import frenchMessages from "./i18n/fr"; -import chineseMessages from "./i18n/zh"; import italianMessages from "./i18n/it"; +import chineseMessages from "./i18n/zh"; +import authProvider from "./synapse/authProvider"; +import dataProvider from "./synapse/dataProvider"; // TODO: Can we use lazy loading together with browser locale? const messages = { @@ -33,8 +30,7 @@ const messages = { zh: chineseMessages, }; const i18nProvider = polyglotI18nProvider( - locale => - messages[locale] ? merge({}, messages.en, messages[locale]) : messages.en, + locale => (messages[locale] ? merge({}, messages.en, messages[locale]) : messages.en), resolveBrowserLocale(), [ { locale: "en", name: "English" }, diff --git a/src/components/AvatarField.test.tsx b/src/components/AvatarField.test.tsx index 84409bf..2ac9234 100644 --- a/src/components/AvatarField.test.tsx +++ b/src/components/AvatarField.test.tsx @@ -1,5 +1,6 @@ -import { RecordContextProvider } from "react-admin"; import { render, screen } from "@testing-library/react"; +import { RecordContextProvider } from "react-admin"; + import AvatarField from "./AvatarField"; describe("AvatarField", () => { diff --git a/src/components/AvatarField.tsx b/src/components/AvatarField.tsx index fc99853..0a8e232 100644 --- a/src/components/AvatarField.tsx +++ b/src/components/AvatarField.tsx @@ -1,4 +1,5 @@ import { get } from "lodash"; + import { Avatar } from "@mui/material"; import { useRecordContext } from "react-admin"; @@ -6,16 +7,7 @@ const AvatarField = ({ source, ...rest }) => { const record = useRecordContext(rest); const src = get(record, source)?.toString(); const { alt, classes, sizes, sx, variant } = rest; - return ( - - ); + return ; }; export default AvatarField; diff --git a/src/components/EventReports.tsx b/src/components/EventReports.tsx index 810cce8..1cab058 100644 --- a/src/components/EventReports.tsx +++ b/src/components/EventReports.tsx @@ -1,3 +1,6 @@ +import PageviewIcon from "@mui/icons-material/Pageview"; +import ViewListIcon from "@mui/icons-material/ViewList"; +import ReportIcon from "@mui/icons-material/Warning"; import { Datagrid, DateField, @@ -17,15 +20,11 @@ import { useRecordContext, useTranslate, } from "react-admin"; -import { MXCField } from "./media"; -import PageviewIcon from "@mui/icons-material/Pageview"; -import ReportIcon from "@mui/icons-material/Warning"; -import ViewListIcon from "@mui/icons-material/ViewList"; -import { date_format } from "./date"; -const ReportPagination = () => ( - -); +import { DATE_FORMAT } from "./date"; +import { MXCField } from "./media"; + +const ReportPagination = () => ; export const ReportShow = (props: ShowProps) => { const translate = useTranslate(); @@ -38,43 +37,21 @@ export const ReportShow = (props: ShowProps) => { })} icon={} > - + - - + + - } - path="detail" - > - + } path="detail"> + @@ -89,10 +66,7 @@ export const ReportShow = (props: ShowProps) => { - + @@ -115,19 +89,10 @@ const ReportShowActions = () => { }; export const ReportList = (props: ListProps) => ( - } - sort={{ field: "received_ts", order: "DESC" }} - > + } sort={{ field: "received_ts", order: "DESC" }}> - + diff --git a/src/components/ImportFeature.tsx b/src/components/ImportFeature.tsx index 3f9f2da..062af3d 100644 --- a/src/components/ImportFeature.tsx +++ b/src/components/ImportFeature.tsx @@ -1,10 +1,6 @@ +import { parse as parseCsv, unparse as unparseCsv, ParseResult } from "papaparse"; import { ChangeEvent, useState } from "react"; -import { useDataProvider, useNotify, RaRecord, Title } from "react-admin"; -import { - parse as parseCsv, - unparse as unparseCsv, - ParseResult, -} from "papaparse"; + import { Button, Card, @@ -17,6 +13,8 @@ import { NativeSelect, } from "@mui/material"; import { DataProvider, useTranslate } from "ra-core"; +import { useDataProvider, useNotify, RaRecord, Title } from "react-admin"; + import { generateRandomMxId, generateRandomPassword } from "../synapse/synapse"; const LOGGING = true; @@ -121,21 +119,12 @@ const FilePicker = () => { } }; - const verifyCsv = ( - { data, meta, errors }: ParseResult, - { setValues, setStats, setError } - ) => { + const verifyCsv = ({ data, meta, errors }: ParseResult, { setValues, setStats, setError }) => { /* First, verify the presence of required fields */ - const missingFields = expectedFields.filter(eF => - meta.fields?.find(mF => eF === mF) - ); + const missingFields = expectedFields.filter(eF => meta.fields?.find(mF => eF === mF)); if (missingFields.length > 0) { - setError( - translate("import_users.error.required_field", { - field: missingFields[0], - }) - ); + setError(translate("import_users.error.required_field", { field: missingFields[0] })); return false; } @@ -157,7 +146,7 @@ const FilePicker = () => { total: data.length, }; - var errorMessages = errors.map(e => e.message); + const errorMessages = errors.map(e => e.message); data.forEach((line, idx) => { if (line.user_type === undefined || line.user_type === "") { stats.user_types.default++; @@ -305,10 +294,7 @@ const FilePicker = () => { * We do a simple retry loop so that an accidental hit on an existing ID * doesn't trip us up. */ - if (LOGGING) - console.log( - "will check for existence of record " + JSON.stringify(userRecord) - ); + if (LOGGING) console.log("will check for existence of record " + JSON.stringify(userRecord)); let retries = 0; const submitRecord = (recordData: ImportLine) => { return dataProvider.getOne("users", { id: recordData.id }).then( @@ -337,14 +323,7 @@ const FilePicker = () => { } }, async () => { - if (LOGGING) - console.log( - "OK to create record " + - recordData.id + - " (" + - recordData.displayname + - ")." - ); + if (LOGGING) console.log("OK to create record " + recordData.id + " (" + recordData.displayname + ")."); if (!dryRun) { await dataProvider.create("users", { data: recordData }); @@ -429,28 +408,11 @@ const FilePicker = () => { const statsCards = stats && !importResults && [ - + -
- {translate( - "import_users.cards.importstats.users_total", - stats.total - )} -
-
- {translate( - "import_users.cards.importstats.guest_count", - stats.is_guest - )} -
-
- {translate( - "import_users.cards.importstats.admin_count", - stats.admin - )} -
+
{translate("import_users.cards.importstats.users_total", stats.total)}
+
{translate("import_users.cards.importstats.guest_count", stats.is_guest)}
+
{translate("import_users.cards.importstats.admin_count", stats.admin)}
, @@ -463,19 +425,9 @@ const FilePicker = () => { {stats.id > 0 ? (
- - - + + +
) : ( @@ -489,20 +441,13 @@ const FilePicker = () => {
{stats.password === stats.total ? translate("import_users.cards.passwords.all_passwords_present") - : translate( - "import_users.cards.passwords.count_passwords_present", - stats.password - )} + : translate("import_users.cards.passwords.count_passwords_present", stats.password)}
{stats.password > 0 ? (
+ } label={translate("import_users.cards.passwords.use_passwords")} /> @@ -519,19 +464,9 @@ const FilePicker = () => {
- - - + + +
@@ -557,11 +492,7 @@ const FilePicker = () => { example.csv

- + ); @@ -570,22 +501,13 @@ const FilePicker = () => {
- {translate( - "import_users.cards.results.total", - importResults.totalRecordCount - )} + {translate("import_users.cards.results.total", importResults.totalRecordCount)}
- {translate( - "import_users.cards.results.successful", - importResults.succeededRecords.length - )} + {translate("import_users.cards.results.successful", importResults.succeededRecords.length)}
{importResults.skippedRecords.length ? [ - translate( - "import_users.cards.results.skipped", - importResults.skippedRecords.length - ), + translate("import_users.cards.results.skipped", importResults.skippedRecords.length),
); @@ -616,13 +529,7 @@ const FilePicker = () => { !values || values.length === 0 || importResults ? undefined : ( - } + control={} label={translate("import_users.cards.startImport.simulate_only")} />