From 50fb671b666e817c472a4034c44d1d659fc816d5 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Wed, 19 Oct 2022 14:32:37 +0200 Subject: [PATCH] Docs/dev docs (#2134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add ADRs * docs/adrs * fix: update developer guide * fix: add space * Update website/docs/contributing/backend/overview.md Co-authored-by: Ivar Conradi Østhus * docs: remove auto-generated sidebar This should've been in .gitignore, but has only been ignored to the ignore file for the website subdirectory. (This has been fixed on main.) * docs: delete empty file * Revert "docs: delete empty file" This reverts commit 2435f173ff0d73f31301a8c4b5b1dfe07a0b4737. * docs: add frontmatter to new dev docs * Docs(fix): add quotes around page titles In yaml, the colon is a special character, so we need to use quotes. * docs: fix remaining titles * Update website/docs/contributing/backend/overview.md Co-authored-by: Ivar Conradi Østhus * fix: update empty ADR * fix: update text to reflect postgres 12 * fix: update backend overview * fix: remove link * fix: add form ADR Co-authored-by: Ivar Conradi Østhus Co-authored-by: Thomas Heartman --- .../docs/contributing/backend/ADR/naming.md | 21 +++ .../backend/ADR/preferred-export.md | 11 ++ website/docs/contributing/backend/overview.md | 134 ++++++++++++++++++ website/docs/contributing/developer-guide.md | 127 +++-------------- .../frontend/ADR/component-naming.md | 29 ++++ .../frontend/ADR/interface-naming.md | 23 +++ .../ADR/preferred-component-props-usage.md | 37 +++++ .../ADR/preferred-data-fetching-method.md | 83 +++++++++++ .../ADR/preferred-data-mutation-method.md | 92 ++++++++++++ .../frontend/ADR/preferred-export.md | 39 +++++ .../ADR/preferred-folder-structure.md | 32 +++++ .../ADR/preferred-form-architecture.md | 17 +++ .../frontend/ADR/preferred-function-type.md | 23 +++ .../ADR/preferred-styles-import-placement.md | 52 +++++++ .../frontend/ADR/preferred-styling-method.md | 16 +++ .../docs/contributing/frontend/overview.md | 23 +++ 16 files changed, 651 insertions(+), 108 deletions(-) create mode 100644 website/docs/contributing/backend/ADR/naming.md create mode 100644 website/docs/contributing/backend/ADR/preferred-export.md create mode 100644 website/docs/contributing/backend/overview.md create mode 100644 website/docs/contributing/frontend/ADR/component-naming.md create mode 100644 website/docs/contributing/frontend/ADR/interface-naming.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-component-props-usage.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-data-fetching-method.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-data-mutation-method.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-export.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-folder-structure.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-form-architecture.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-function-type.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-styles-import-placement.md create mode 100644 website/docs/contributing/frontend/ADR/preferred-styling-method.md create mode 100644 website/docs/contributing/frontend/overview.md diff --git a/website/docs/contributing/backend/ADR/naming.md b/website/docs/contributing/backend/ADR/naming.md new file mode 100644 index 0000000000..e3ca8d43e9 --- /dev/null +++ b/website/docs/contributing/backend/ADR/naming.md @@ -0,0 +1,21 @@ +--- +title: "ADR: Naming" +--- + +## Background + +In the codebase, we have found a need to have a common way of naming things in order to ensure consistency. It's important that files are named after the contents of the file to ensure that it's easy to search for files. You should be able to find the file you need in the command line without the help of advanced IDEs. This can easily be solved by proper naming. It's also crucial that the naming is consistent across the project, if we are using different naming conventions in different places, it will be hard to navigate the codebase. + +## Decision + +We have decided to use a naming convention where the files are named after the main class that it contains. Example: + +```js +feature-toggle-service.ts + +class FeatureToggleService { + ... +} +``` + +The reason for this decision is to remove mental clutter and free up capacity to easily navigate the codebase. Knowing that a file is named after the class that it contains allows you to quickly scan a file without watching for a context where the class is used in order to understand what it is. diff --git a/website/docs/contributing/backend/ADR/preferred-export.md b/website/docs/contributing/backend/ADR/preferred-export.md new file mode 100644 index 0000000000..5408c21dee --- /dev/null +++ b/website/docs/contributing/backend/ADR/preferred-export.md @@ -0,0 +1,11 @@ +--- +title: "ADR: Preferred export" +--- + +## Background + +In the codebase, we have discovered that default exports create multiple problems. One is that you can rename the component when importing it, which can cause confusion. Another is that it is harder to find the component when you are looking for it, as you have to look for the file name instead of the component name (solved by ADR for naming, but still relevant). + +## Decision + +We have decided to use named exports. This will allow us to eliminate the possiblity of exporting a component and renaming it in another file. It also allows us easy access to advanced refactors across the project, because renaming in one place will propagate to all the other places where that import is referenced. This resolves the issues described in the background without any significant downsides. diff --git a/website/docs/contributing/backend/overview.md b/website/docs/contributing/backend/overview.md new file mode 100644 index 0000000000..d971456e5e --- /dev/null +++ b/website/docs/contributing/backend/overview.md @@ -0,0 +1,134 @@ +--- +title: Back end +--- + +The frontend is written in nodejs/typescript. It's written as a REST API following a CSR (controller, service, repository/store) pattern. The following ADRs are defined for the backend: + +## ADRs + +We have created a set of ADRs to help guide the development of the backend: + +* [Naming](./ADR/naming.md) +* [Preferred export](./ADR/preferred-export.md) + +## Requirements + +Before developing on this project you will need two things: + +- PostgreSQL 12.x or newer +- Node.js 14.x or newer + +```sh +yarn install +yarn run start:dev +``` + +## PostgreSQL {#postgresql} + +To run and develop unleash, you need to have PostgreSQL database (PostgreSQL v12.x or newer) locally. + +Unleash currently also work with PostgreSQL v12+, but this might change in a future feature release, and we have stopped running automatic integration tests below PostgreSQL v10. + +### Create a local unleash databases in postgres {#create-a-local-unleash-databases-in-postgres} + +```bash +$ psql postgres < Password is intentionally set to 'passord', which is the Norwegian word for password. + +Then set env vars: + +(Optional as unleash will assume these as default values). + +``` +export DATABASE_URL=postgres://unleash_user:passord@localhost:5432/unleash +export TEST_DATABASE_URL=postgres://unleash_user:passord@localhost:5432/unleash_test +``` + +## PostgreSQL with docker {#postgresql-with-docker} + +If you don't want to install PostgreSQL locally, you can spin up an Docker instance. We have created a script to ease this process: `scripts/docker-postgres.sh` + +## Start the application {#start-the-application} + +In order to start the application you will need Node.js v14.x or newer installed locally. + +``` +// Install dependencies +yarn install + +// Start server in development +yarn start:dev + +// Unleash UI +http://localhost:4242 + +// API: +http://localhost:4242/api/ + +// Execute tests in all packages: +yarn test +``` + +## Database changes {#database-changes} + +We use database migrations to track database changes. Never change a migration that has been merged to main. If you need to change a migration, create a new migration that reverts the old one and then creates the new one. + +### Making a schema change {#making-a-schema-change} + +To run migrations, you will set the environment variable for DATABASE_URL + +`export DATABASE_URL=postgres://unleash_user:passord@localhost:5432/unleash` + +Use db-migrate to create new migrations file. + +```bash +> yarn run db-migrate create YOUR-MIGRATION-NAME +``` + +All migrations require one `up` and one `down` method. There are some migrations that will maintain the database integrity, but not the data integrity and may not be safe to run on a production database. + +Example of a typical migration: + +```js +/* eslint camelcase: "off" */ +'use strict'; + +exports.up = function(db, cb) { + db.createTable( + 'examples', + { + id: { type: 'int', primaryKey: true, notNull: true }, + created_at: { type: 'timestamp', defaultValue: 'now()' }, + }, + cb, + ); +}; + +exports.down = function(db, cb) { + return db.dropTable('examples', cb); +}; +``` + +Test your migrations: + +```bash +> yarn run db-migrate up +> yarn run db-migrate down +``` + +## Publishing / Releasing new packages {#publishing--releasing-new-packages} + +Please run `yarn test` checks before publishing. + +Run `npm run publish` to start the publishing process. + +`npm run publish:dry` diff --git a/website/docs/contributing/developer-guide.md b/website/docs/contributing/developer-guide.md index de6d1a62cc..9c924f3356 100644 --- a/website/docs/contributing/developer-guide.md +++ b/website/docs/contributing/developer-guide.md @@ -1,122 +1,33 @@ +--- +title: Developer guide +--- + ## Introduction {#introduction} -Before developing on this project you will need two things: +This repository contains two main parts. The backend and the frontend of unleash. The backend is a Node.js application that is built using TypeScript. The frontend is a React application that is built using TypeScript. The backend specific code can be found in the `src` lib folder. The frontend specific code can be found in the `frontend` folder. -- PostgreSQL 10.x or newer -- Node.js 14.x or newer +## Development philosophy -```sh -yarn install -yarn run start:dev -``` +The development philosophy at unleash is centered at delivering high quality software. We do this by following a set of principles that we believe will help us achieve this goal. We believe that these principles will also help us deliver software that is easy to maintain and extend, serving as our north star. -## PostgreSQL {#postgresql} +We believe that the following principles will help us achieve our goal of delivering high quality software: -To run and develop unleash, you need to have PostgreSQL database (PostgreSQL v10.x or newer) locally. +* We test our code always -> Unleash currently also work with PostgreSQL v9.5+, but this might change in a future feature release, and we have stopped running automatic integration tests below PostgreSQL v10. +Software is difficult. Being a software engineer is about acknowledging our limits, and taking every precaution necessary to avoid introducing bugs. We believe that testing is the best way to achieve this. We test our code always, and prefer automation over manual testing. -### Create a local unleash databases in postgres {#create-a-local-unleash-databases-in-postgres} +* We strive to write code that is easy to understand and maintain -```bash -$ psql postgres < Password is intentionally set to 'passord', which is the Norwegian word for password. +We don't jump to implementation immediately. We think about the problem at hand, and try to examine the impact that this solution may have in a multitude of scenarios. As our product core is open source, we need to balance the solutions and avoid implementations that may be cumbersome for our community. The need to improve our paid offering must never come at the cost of our open source offering. -Then set env vars: +### Required reading -(Optional as unleash will assume these as default values). +The following resources should be read before contributing to the project: -``` -export DATABASE_URL=postgres://unleash_user:passord@localhost:5432/unleash -export TEST_DATABASE_URL=postgres://unleash_user:passord@localhost:5432/unleash_test -``` - -## PostgreSQL with docker {#postgresql-with-docker} - -If you don't want to install PostgreSQL locally, you can spin up an Docker instance. We have created a script to ease this process: `scripts/docker-postgres.sh` - -## Start the application {#start-the-application} - -In order to start the application you will need Node.js v14.x or newer installed locally. - -``` -// Install dependencies -yarn install - -// Start server in development -yarn start:dev - -// Unleash UI -http://localhost:4242 - -// API: -http://localhost:4242/api/ - -// Execute tests in all packages: -yarn test -``` - -## Database changes {#database-changes} - -We use database migrations to track database changes. - -### Making a schema change {#making-a-schema-change} - -To run migrations, you will set the environment variable for DATABASE_URL - -`export DATABASE_URL=postgres://unleash_user:passord@localhost:5432/unleash` - -Use db-migrate to create new migrations file. - -```bash -> yarn run db-migrate create YOUR-MIGRATION-NAME -``` - -All migrations require one `up` and one `down` method. - -Example of a typical migration: - -```js -/* eslint camelcase: "off" */ -'use strict'; - -exports.up = function(db, cb) { - db.createTable( - 'examples', - { - id: { type: 'int', primaryKey: true, notNull: true }, - created_at: { type: 'timestamp', defaultValue: 'now()' }, - }, - cb, - ); -}; - -exports.down = function(db, cb) { - return db.dropTable('examples', cb); -}; -``` - -Test your migrations: - -```bash -> yarn run db-migrate up -> yarn run db-migrate down -``` - -## Publishing / Releasing new packages {#publishing--releasing-new-packages} - -Please run `yarn test` checks before publishing. - -Run `npm run publish` to start the publishing process. - -`npm run publish:dry` +* [Clean code javascript](https://github.com/ryanmcdermott/clean-code-javascript) +* [frontend overview](./frontend/overview.md) +* [backend overview](./backend/overview.md) diff --git a/website/docs/contributing/frontend/ADR/component-naming.md b/website/docs/contributing/frontend/ADR/component-naming.md new file mode 100644 index 0000000000..f683d8308b --- /dev/null +++ b/website/docs/contributing/frontend/ADR/component-naming.md @@ -0,0 +1,29 @@ +--- +title: "ADR: Component naming and file naming" +--- + +## Background + +In the codebase, we have found a need to have a common way of naming components so that components can be (a) easily searched for, (b) easily identified as react components and (c) be descriptive in what they do in the codebase. + +## Decision + +We have decided to use a naming convention for components that uppercases the first letter of the component. This also extends to the filename of the component. The two should always be the same: + +```jsx +// Do: +// MyComponent.ts + +const MyComponent = () => {}; + +// Don't: +// someRandomName.ts + +const MyComponent = () => {}; +``` + +The reason for this decision is to remove mental clutter and free up capacity to easily navigate the codebase. Knowing that a component name has the same name as the filename will remove any doubts about the file contents quickly and in the same way follow the React standard of uppercase component names. + +### Deviations + +In some instances, for simplicity we might want to create internal components or child components for a larger component. If these child components are small enough in size and it makes sense to keep them in the same file as the parent (AND they are used in no other external components) it's fine to keep in the same file as the parent component. diff --git a/website/docs/contributing/frontend/ADR/interface-naming.md b/website/docs/contributing/frontend/ADR/interface-naming.md new file mode 100644 index 0000000000..c13ed8d706 --- /dev/null +++ b/website/docs/contributing/frontend/ADR/interface-naming.md @@ -0,0 +1,23 @@ +--- +title: "ADR: Interface naming" +--- + +## Background + +In the codebase, we have found a need to have a common way of naming interfaces in order to ensure consistency. + +## Decision + +We have decided to use a naming convention of appending the letter `I` in front of interfaces to signify that we are in fact using an interface. For props, we use `IComponentNameProps`. + +```jsx +// Do: +interface IMyInterface {} +interface IMyComponentNameProps {} + +// Don't: +interface MyInterface {} +interface MyComponentName {} +``` + +The reason for this decision is to remove mental clutter and free up capacity to easily navigate the codebase. Knowing that an interface is prefixed with `I` allows you to quickly scan a file without watching for a context where the interface is used in order to understand what it is. diff --git a/website/docs/contributing/frontend/ADR/preferred-component-props-usage.md b/website/docs/contributing/frontend/ADR/preferred-component-props-usage.md new file mode 100644 index 0000000000..049131178b --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-component-props-usage.md @@ -0,0 +1,37 @@ +--- +title: "ADR: Preferred component props usage" +--- + +## Background + +In the codebase, we have found a need to standardise how to use props, in order to easily be able to figure out what a component is doing and what properties it is given without having to look up the interface. + +## Decision + +We have decided to use props destructuring inline in components in order to quickly display what properties a component is using. + +```tsx +// Do: +const MyComponent = ({ name, age, occupation }: IComponentProps) => { + return ( +
+

{age}

+

{name}

+

{occupation}

+ + ) +}; + +// Don't: +function MyComponent(props) { + return ( +
+

{props.age}

+

{props.name}

+

{props.occupation}

+ + ) +} +``` + +The reason for this decision is to remove mental clutter and free up capacity to easily navigate the codebase. In addition, when components grow, the ability to look at the signature and instantly know what dependencies this component uses gives you an advantage when scanning the codebase. diff --git a/website/docs/contributing/frontend/ADR/preferred-data-fetching-method.md b/website/docs/contributing/frontend/ADR/preferred-data-fetching-method.md new file mode 100644 index 0000000000..6d693fa34e --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-data-fetching-method.md @@ -0,0 +1,83 @@ +--- +title: "ADR: Preferred data fetching method" +--- + +## Background + +We have found a need to standardise how we fetch data from APIs, in order to reduce complexity and simplify the data fetching process. + +## Decision + +We have decided to remove redux from our application and fetch all of our data via a third party library called `useSWR` (SWR stands for stale-while-revalidate and is a common cache strategy). + +```tsx +// Do: +// useSegments.ts + +import useSWR from 'swr'; +import { useCallback } from 'react'; +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; +import { ISegment } from 'interfaces/segment'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { IFlags } from 'interfaces/uiConfig'; + +export interface UseSegmentsOutput { + segments?: ISegment[]; + refetchSegments: () => void; + loading: boolean; + error?: Error; +} + +export const useSegments = (strategyId?: string): UseSegmentsOutput => { + const { uiConfig } = useUiConfig(); + + const { data, error, mutate } = useSWR( + [strategyId, uiConfig.flags], + fetchSegments + ); + + const refetchSegments = useCallback(() => { + mutate().catch(console.warn); + }, [mutate]); + + return { + segments: data, + refetchSegments, + loading: !error && !data, + error, + }; +}; + +export const fetchSegments = async ( + strategyId?: string, + flags?: IFlags +): Promise => { + if (!flags?.SE) { + return []; + } + + return fetch(formatSegmentsPath(strategyId)) + .then(handleErrorResponses('Segments')) + .then(res => res.json()) + .then(res => res.segments); +}; + +const formatSegmentsPath = (strategyId?: string): string => { + return strategyId + ? formatApiPath(`api/admin/segments/strategies/${strategyId}`) + : formatApiPath('api/admin/segments'); +}; + +// Don't: +const MyComponent = () => { + useEffect(() => { + const getData = () => { + fetch(API_URL) + .then(res => res.json()) + .then(setData); + }; + getData(); + }, []); +}; +``` diff --git a/website/docs/contributing/frontend/ADR/preferred-data-mutation-method.md b/website/docs/contributing/frontend/ADR/preferred-data-mutation-method.md new file mode 100644 index 0000000000..2ddec1bfec --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-data-mutation-method.md @@ -0,0 +1,92 @@ +--- +title: "ADR: Preferred data mutation method" +--- + +## Background + +Because our product is open-core, we have complexities and needs for our SaaS platform that are not compatible with the needs of our open-source product. We have found a need to standardise how we fetch data from APIs, in order to reduce complexity and simplify the data fetching process. + +## Decision + +We have decided to standardise data-fetching and error handling by implementing a top level `useAPI` hook that will take care of formatting the +request in the correct way adding the basePath if unleash is hosted on a subpath, wrap with error handlers and return the data in a consistent way. + +Example: + +```tsx +import { ITagPayload } from 'interfaces/tags'; +import useAPI from '../useApi/useApi'; + +const useTagTypesApi = () => { + const { makeRequest, createRequest, errors, loading } = useAPI({ + propagateErrors: true, + }); + + const createTag = async (payload: ITagPayload) => { + const path = `api/admin/tag-types`; + const req = createRequest(path, { + method: 'POST', + body: JSON.stringify(payload), + }); + + try { + const res = await makeRequest(req.caller, req.id); + + return res; + } catch (e) { + throw e; + } + }; + + const validateTagName = async (name: string) => { + const path = `api/admin/tag-types/validate`; + const req = createRequest(path, { + method: 'POST', + body: JSON.stringify({ name }), + }); + try { + const res = await makeRequest(req.caller, req.id); + return res; + } catch (e) { + throw e; + } + }; + const updateTagType = async (tagName: string, payload: ITagPayload) => { + const path = `api/admin/tag-types/${tagName}`; + const req = createRequest(path, { + method: 'PUT', + body: JSON.stringify(payload), + }); + + try { + const res = await makeRequest(req.caller, req.id); + return res; + } catch (e) { + throw e; + } + }; + + const deleteTagType = async (tagName: string) => { + const path = `api/admin/tag-types/${tagName}`; + const req = createRequest(path, { method: 'DELETE' }); + + try { + const res = await makeRequest(req.caller, req.id); + return res; + } catch (e) { + throw e; + } + }; + + return { + createTag, + validateTagName, + updateTagType, + deleteTagType, + errors, + loading, + }; +}; + +export default useTagTypesApi; +``` diff --git a/website/docs/contributing/frontend/ADR/preferred-export.md b/website/docs/contributing/frontend/ADR/preferred-export.md new file mode 100644 index 0000000000..adafde39df --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-export.md @@ -0,0 +1,39 @@ +--- +title: "ADR: Preferred export" +--- + +## Background + +We have seen a need to standardize how to export from files in the project, in order to achieve consistency and avoid situations where we can have a component default exported as one name and renamed as something else in a different file. For example: + +``` +// Problem example +// File A + +const MyComponent = () => { + +} + +export default MyComponent; + +// File B +import NewName from '../components/MyComponent/MyComponent.tsx'; +``` + +The above can cause massive confusion and make it hard to navigate the codebase. + +## Decision + +We have decided to standardise exports on named exports. This will allow us to eliminate the possiblity of exporting a component and renaming it in another file. + +```jsx +// Do: +export const MyComponent = () => {}; + +// Don't: +const MyComponent = () => {}; + +export default MyComponent; +``` + +The reason for this decision is to remove mental clutter and free up capacity to easily navigate the codebase. If you can always deduce that the component is named as it is defined, then finding that component becomes a lot easier. This will ensure that we remove unnecessary hurdles to understand and work within the codebase. diff --git a/website/docs/contributing/frontend/ADR/preferred-folder-structure.md b/website/docs/contributing/frontend/ADR/preferred-folder-structure.md new file mode 100644 index 0000000000..ffc776d40c --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-folder-structure.md @@ -0,0 +1,32 @@ +--- +title: "ADR: Preferred folder structure" +--- + +## Background + +Folder structure is important in how easy it is to navigate and reason about the codebase. It's important to have a clear structure that is easy to understand and follow, while grouping related files together in such a way that is easy to find and remove. + +## Decision + +We have decided to create tree-like folder structure that mimics as closely as possible the relationship of the React components in the project. This has a number of benefits: + +* If you are looking for a component, you can easily find it by looking at the folder structure. +* If you need to delete a component, you can be sure that all of the files connected to that component will be deleted if you delete the folder. This is supremely important, because it allows us to get rid of dead code easily and without having to worry about the consequences of deleting a file and worrying about whether it's used somewhere else. + +## Folder structure example: + +``` +ProfilePage + ProfilePage.tsx + ProfilePage.styles.ts + ProfileSettings + ProfileSettings.tsx + ProfileSettings.styles.ts + ProfilePicture + ProfilePicture.tsx + ProfilePicture.styles.ts +``` + +Now you can clearly see that if you need to delete the `ProfilePage` component, you can simply delete the `ProfilePage` folder and all of the files connected to that component will be deleted. + +If you experience that you need to create a component that is used in multiple places, the component should be moved to the closest possible ancestor. If this is not possible, the component should be moved to the `common` folder. diff --git a/website/docs/contributing/frontend/ADR/preferred-form-architecture.md b/website/docs/contributing/frontend/ADR/preferred-form-architecture.md new file mode 100644 index 0000000000..766b42390a --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-form-architecture.md @@ -0,0 +1,17 @@ +--- +title: "ADR: Preferred form architecture" +--- + +## Background + +Forms can be tricky. In software, we often want to write DRY components, repeating as little as possible. Yet we also want a clear separation of concerns. Forms represent a challenge in this way because you have to choose which principle is the most important. You can't both have it DRY and completely separated. + +## Decision + +We have decided to architecture our forms in the following way: + +* Create a hook that contains all the logic for the form. This hook will return a form object that contains all the form state and functions to update the state. +* Create a reusable form component that does not contain any logic +* Create separate Create and Edit components that use the form component and the form hook to create the form and implements it's own logic for submitting the form. + +In this way, we keep as much of the form as possible DRY, but we avoid passing state internally in the form so the form doesn't need to know wheter it is in create or edit mode. This allows us to keep one thing in mind when working, and not have to worry about dual states of the component. \ No newline at end of file diff --git a/website/docs/contributing/frontend/ADR/preferred-function-type.md b/website/docs/contributing/frontend/ADR/preferred-function-type.md new file mode 100644 index 0000000000..9472f12d9f --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-function-type.md @@ -0,0 +1,23 @@ +--- +title: "ADR: Preferred function type" +--- + +## Background + +In the codebase, we have found a need to standardise function types in order to keep the codebase recognizible across different sections, and to encourage / discourage certain patterns. + +## Decision + +We have decided to use arrow functions across the board in the project. Both for helper functions and for react components. + +```jsx +// Do: +const myFunction = () => {}; +const MyComponent = () => {}; + +// Don't: +function myFunction() {} +function MyComponent() {} +``` + +The reason for this decision is to remove mental clutter and free up capacity to easily navigate the codebase. In addition, using arrow functions allows you to avoid the complexity of losing the scope of `this` for nested functions, and keeps `this` stable without any huge drawbacks. Losing hoisting is an acceptable compromise. diff --git a/website/docs/contributing/frontend/ADR/preferred-styles-import-placement.md b/website/docs/contributing/frontend/ADR/preferred-styles-import-placement.md new file mode 100644 index 0000000000..ce8bdea9f7 --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-styles-import-placement.md @@ -0,0 +1,52 @@ +--- +title: "ADR: preferred styles import placement" +--- + +## Background + +SUPERSEDED BY [ADR: Preferred styling method](./preferred-styling-method.md) + +In the codebase, we have found a need to standardise where to locate the styles import. When using CSS modules, the styles import placement matters for the priority of the styles if you are passing through styles to other components. IE: + +``` +// import order matters, because the useStyles in MyComponent now +// is after the useStyles import it will not take precedence if it has +// a styling conflict. +import useStyles from './SecondComponent.styles.ts'; +import MyComponent from '../MyComponent/MyComponent.tsx'; + +const SecondComponent = () => { + const styles = useStyles(); + + return +} +``` + +## Decision + +We have decided to always place style imports as the last import in the file, so that any components that the file may use can safely be overriden with styles from the parent component. + +```tsx +// Do: +import MyComponent from '../MyComponent/MyComponent.tsx'; + +import useStyles from './SecondComponent.styles.ts'; + +const SecondComponent = () => { + const styles = useStyles(); + + return ; +}; + +// Don't: +import useStyles from './SecondComponent.styles.ts'; +import MyComponent from '../MyComponent/MyComponent.tsx'; + +const SecondComponent = () => { + const styles = useStyles(); + + return ; +}; +``` + +The reason for this decision is to remove the posibillity for hard to find bugs, that are not obvious to detect and that might be time consuming to find a solution to. diff --git a/website/docs/contributing/frontend/ADR/preferred-styling-method.md b/website/docs/contributing/frontend/ADR/preferred-styling-method.md new file mode 100644 index 0000000000..9478fb704d --- /dev/null +++ b/website/docs/contributing/frontend/ADR/preferred-styling-method.md @@ -0,0 +1,16 @@ +--- +title: "ADR: preferred styling method" +--- + +This document supersedes [ADR: preferred styles import placement](./preferred-styles-import-placement.md) + + +## Background + +In the codebase, we need to have a uniform way of performing style updates. + +## Decision + +We have decided to move away from using makeStyles as it's currently deprecated from @material/ui, and kept alive with an +external interop package to maintain compatability with the latest version. The preferred path forward is to use styled components which is +supported natively in @material/ui and sparingly use the sx prop available on all mui components. diff --git a/website/docs/contributing/frontend/overview.md b/website/docs/contributing/frontend/overview.md new file mode 100644 index 0000000000..0dc59d58df --- /dev/null +++ b/website/docs/contributing/frontend/overview.md @@ -0,0 +1,23 @@ +--- +title: Front end +--- + +## Frontend overview + +The frontend is written in react/typescript. It's is a single page application that communicates with the backend via a REST API. The frontend is built using vite and served by the backend. + +## ADRs + +We have created a set of ADRs to help guide the development of the frontend: + +* [Component naming](./ADR/component-naming.md) +* [Interface naming](./ADR/interface-naming.md) +* [Preferred component props usage](./ADR/preferred-component-props-usage.md) +* [Preferred export](./ADR/preferred-export.md) +* [Preferred function type](./ADR/preferred-function-type.md) +* [Preferred style import placement](./ADR/preferred-styles-import-placement.md) +* [Preferred styling method](./ADR/preferred-styling-method.md) +* [Preferred data mutation method](./ADR/preferred-data-mutation-method.md) +* [Preferred data fetching method](./ADR/preferred-data-fetching-method.md) +* [Preferred folder structure](./ADR/preferred-folder-structure.md) +* [Preferred form architecture](./ADR/preferred-form-architecture.md)