From 50ae1e5c599793a2e168f093d601782b89ed46f7 Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Fri, 6 Dec 2024 17:19:52 +0100 Subject: [PATCH 1/6] enhance(frontend): agenta-web root Readme updates --- agenta-web/README.md | 173 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/agenta-web/README.md b/agenta-web/README.md index d7b49b27fe..97ae519f3e 100644 --- a/agenta-web/README.md +++ b/agenta-web/README.md @@ -2,4 +2,177 @@ This directory contains the code source for the web app for Agenta AI. +## Installation + Please see the Readme.md in the main dir for installation and usage instructions. + +## Configuration for Better Development Experience + +### Visual Studio Code Users + +To have a better experience while working on the client application, you can configure certain plugins in your workspace `settings.json`. + +#### ESLint + +To ensure ESLint functions properly, add the following configuration: + +```json +{ + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ] +} +``` + +#### Prettier + +To ensure Prettier functions properly, add the following configuration: + +```json +{ + "prettier.prettierPath": "./agenta-web/node_modules/prettier" +} +``` + +## Contribution Guidelines + +### Folder Structure + +Below is the folder structure of the `./agenta-web/src` directory: + +```text +agenta-web/src +├── assets +│ ├── constants.ts +│ ├── utils.ts +│ ├── UIElement1.tsx +├── components +│ ├── Component1 +│ │ ├── assets +│ │ │ ├── constants.ts +│ │ │ ├── utils.ts +│ │ │ ├── Component1UIElement.tsx +│ │ ├── Component.tsx +│ ├── hooks +│ │ ├── useComponent1Hook.ts +│ │ ├── types.d.ts +├── hooks +│ ├── useSharedHook1.ts +│ ├── useSharedHook2.ts +├── pages +│ ├── Home +│ ├── About +│ ├── Contact +├── utils +├── store +│ ├── atoms +│ │ ├── globalAtoms.ts +│ ├── context +│ │ ├── GlobalContext.tsx +├── modules +│ ├── Module1 +│ │ ├── assets +│ │ │ ├── constants.ts +│ │ │ ├── Module1UIElement.tsx +│ │ ├── store +│ │ │ ├── atoms +│ │ │ │ ├── moduleAtoms.ts +│ │ │ ├── context +│ │ │ │ ├── ModuleContext.tsx +│ ├── components +│ │ │ ├── ModuleComponent1 +│ │ │ │ ├── assets +│ │ │ │ │ ├── constants.ts +│ │ │ │ │ ├── utils.ts +│ │ │ │ │ ├── ModuleComponent1UIElement.tsx +│ │ │ │ ├── Component.tsx +│ │ │ │ ├── hooks +│ │ │ │ │ ├── useModuleComponent1Hook.ts +│ │ │ │ │ ├── types.d.ts +│ │ │ ├── ModuleComponent2.tsx +│ │ ├── hooks +│ │ │ ├── useModuleHook1.ts +│ │ │ ├── useModuleHook2.ts +│ │ ├── Module.tsx +│ │ ├── types.d.ts +│ ├── Module2 +│ │ ├── assets +│ │ │ ├── constants.ts +│ │ │ ├── utils.ts +│ │ │ ├── Module2UIElement.tsx +│ │ ├── components +│ │ │ ├── ModuleComponent1.tsx +│ │ ├── hooks +│ │ │ ├── useModuleHook1.ts +│ │ ├── Module.tsx +│ │ ├── types.d.ts +└── global.d.ts +``` + +### Architecture Overview + +Our folder structure follows a module-based architecture that prioritizes maintainability, reusability, and clear separation of concerns. + +#### Core Principles + +1. **Modular Organization** + - Modules represent distinct feature areas (similar to pages) + - Each module is self-contained with its own components, hooks, and assets + - Shared functionality is elevated to appropriate hierarchy levels + +2. **Component Structure** + - Components are organized by their scope of use + - Each component may contain: + - Presentational logic (`Component.tsx`) + - UI-only subcomponents (`assets/*.tsx`) + - Component-specific hooks (`hooks/*.ts`) + - Local constants and utilities (`assets/*.ts`) + - Type definitions (`types.d.ts`) + +3. **Code Movement Guidelines** + The following rules determine where code should live: + - Module-specific code stays within the module + - Components used across multiple modules move to root `/components` + - Hooks used across multiple modules move to root `/hooks` + - UI elements, constants, or utilities used across modules move to root `/assets` + - Types used across modules move to root `types.d.ts` + +#### State Management + +1. **Store Organization** + - Each module can have its own `store` folder containing: + - Jotai atoms for reactive state + - Context providers for complex state/dependency injection + - Global store at root level for cross-module state + +2. **State Movement Guidelines** + - State used only within a component stays as local state + - State shared between components in a module uses module-level store + - State shared across modules moves to root `/store` + - Consider these factors when choosing state location: + - Scope of state usage + - Frequency of updates + - Performance implications + - Data persistence requirements + +3. **State Management Tools** + - Prefer Jotai atoms for simple reactive state + - Use Context for complex state with multiple consumers + - Local component state for UI-only concerns + +#### Implementation Strategy + +- **Current Approach**: Gradual adoption during regular development +- **Migration**: Update components to follow this structure as they are modified +- **No Big Bang**: Avoid large-scale refactoring +- **Progressive Enhancement**: Easy to implement incrementally + +This structure supports: + +- Clear ownership and responsibility +- Easy code review and modification +- Identification of reusable patterns +- Natural code organization based on usage +- Scalable architecture that grows with the application From 8b7a019080d64e906e26ff2fa82c2bb624247246 Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Sun, 8 Dec 2024 22:49:36 +0100 Subject: [PATCH 2/6] enhance(frontend): documentation on data fetching --- agenta-web/README.md | 83 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/agenta-web/README.md b/agenta-web/README.md index 97ae519f3e..56a3ab457e 100644 --- a/agenta-web/README.md +++ b/agenta-web/README.md @@ -104,10 +104,10 @@ agenta-web/src │ │ │ ├── Module2UIElement.tsx │ │ ├── components │ │ │ ├── ModuleComponent1.tsx -│ │ ├── hooks +│ ├── hooks │ │ │ ├── useModuleHook1.ts -│ │ ├── Module.tsx -│ │ ├── types.d.ts +│ ├── Module.tsx +│ ├── types.d.ts └── global.d.ts ``` @@ -176,3 +176,80 @@ This structure supports: - Identification of reusable patterns - Natural code organization based on usage - Scalable architecture that grows with the application + +### Data Fetching Best Practices + +We recommend using SWR with Axios for data fetching instead of useEffect patterns. This helps achieve cleaner code while simplifying management of fetch states. + +#### Example: Converting useEffect Data Fetching to SWR with Axios + +❌ **Avoid this pattern:** + +```javascript +useEffect(() => { + fetchData1().then(data1 => { + setData1(data1); + }).catch(error => { + setError1(error); + }); + + fetchData2().then(data2 => { + setData2(data2); + }).catch(error => { + setError2(error); + }); +}, []); +``` + +✅ **Use this pattern:** + +We configure SWR globally with our pre-configured Axios instance: + +```javascript +// src/utils/swrConfig.js +import axios from '@/lib/helpers/axios'; +import useSWR from 'swr'; + +const fetcher = url => axios.get(url).then(res => res.data); + +export const swrConfig = { + fetcher, +}; +``` + +To ensure SWR configuration is applied globally, wrap your application with SWRConfig in `_app.tsx`: + +```javascript +// src/pages/_app.tsx +import { SWRConfig } from 'swr'; +import { swrConfig } from '../utils/swrConfig'; + +function MyApp({ Component, pageProps }) { + return ( + + + + ); +} + +export default MyApp; +``` + +```javascript +import useSWR from 'swr'; + +function Component() { + const { data: data1, error: error1, loading: loadingData1 } = useSWR('/api/data1'); + const { data: data2, error: error2, loading: loadingData2 } = useSWR('/api/data2'); + + if (error1 || error2) return
Error loading data
; + if (!data1 || !data2) return
Loading...
; + + return ( +
+
Data 1: {data1}
+
Data 2: {data2}
+
+ ); +} +``` From 9180a1e565eab7fed402febb8bc7decbf5121098 Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Mon, 9 Dec 2024 13:33:19 +0100 Subject: [PATCH 3/6] enhance(frontend): approach to use mutations with swr --- agenta-web/README.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/agenta-web/README.md b/agenta-web/README.md index 56a3ab457e..7d10199c6a 100644 --- a/agenta-web/README.md +++ b/agenta-web/README.md @@ -179,7 +179,12 @@ This structure supports: ### Data Fetching Best Practices -We recommend using SWR with Axios for data fetching instead of useEffect patterns. This helps achieve cleaner code while simplifying management of fetch states. +We recommend using SWR with Axios for data fetching instead of useEffect patterns. This helps achieve cleaner code while, + +- simplifying management of fetch states. +- handling cache better +- having a more interactive UI by revalidating in background +- utilizing optimistic mutations. #### Example: Converting useEffect Data Fetching to SWR with Axios @@ -235,6 +240,8 @@ function MyApp({ Component, pageProps }) { export default MyApp; ``` +and data can be then be fetched in a way that fits react mental model inside the component: + ```javascript import useSWR from 'swr'; @@ -253,3 +260,35 @@ function Component() { ); } ``` + +Mutations can be triggered via Swr in the following way + +```javascript +import useSWRMutation from 'swr/mutation' + +async function sendRequest(url, { arg }: { arg: { username: string }}) { + return fetch(url, { + method: 'POST', + body: JSON.stringify(arg) + }).then(res => res.json()) +} + +function App() { + const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest, /* options */) + + return ( + + ) +} +``` From 441006959d39e0f4d6e99951c040a8efafc99067 Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Mon, 9 Dec 2024 13:58:31 +0100 Subject: [PATCH 4/6] enhance(frontend): guideline to include best practices for memoization in react components --- agenta-web/README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/agenta-web/README.md b/agenta-web/README.md index 7d10199c6a..719b29ad02 100644 --- a/agenta-web/README.md +++ b/agenta-web/README.md @@ -292,3 +292,47 @@ function App() { ) } ``` + +### React Best Practices + +#### Avoiding Inline Array Props + +Passing inline arrays of objects with heavy content such as JSX is considered a bad practice in React. This is because it can lead to unnecessary re-renders and performance issues. When you pass an inline array, a new array is created every time the component renders, causing React to think that the prop has changed even if the content is the same. + +For example, in the `AccordionTreePanel` component, the `items` prop is passed an inline array of objects with JSX content: + +❌ **Avoid this pattern:** + +```javascript +Content 1, + }, + { + title: 'Item 2', + content:
Content 2
, + }, + ]} +/> +``` + +✅ **Use this pattern:** + +```javascript +import { useMemo } from 'react'; + +const items = useMemo(() => [ + { + title: 'Item 1', + content:
Content 1
, + }, + { + title: 'Item 2', + content:
Content 2
, + }, +], []); + + +``` From 4398434caa922bbeb5f925c02e5f1ccf8ed267e8 Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Mon, 9 Dec 2024 15:34:44 +0100 Subject: [PATCH 5/6] chore(frontend): run prettier --- agenta-web/README.md | 231 ++++++++++++++++++++++--------------------- 1 file changed, 121 insertions(+), 110 deletions(-) diff --git a/agenta-web/README.md b/agenta-web/README.md index 719b29ad02..2993075dc7 100644 --- a/agenta-web/README.md +++ b/agenta-web/README.md @@ -118,73 +118,77 @@ Our folder structure follows a module-based architecture that prioritizes mainta #### Core Principles 1. **Modular Organization** - - Modules represent distinct feature areas (similar to pages) - - Each module is self-contained with its own components, hooks, and assets - - Shared functionality is elevated to appropriate hierarchy levels + + - Modules represent distinct feature areas (similar to pages) + - Each module is self-contained with its own components, hooks, and assets + - Shared functionality is elevated to appropriate hierarchy levels 2. **Component Structure** - - Components are organized by their scope of use - - Each component may contain: - - Presentational logic (`Component.tsx`) - - UI-only subcomponents (`assets/*.tsx`) - - Component-specific hooks (`hooks/*.ts`) - - Local constants and utilities (`assets/*.ts`) - - Type definitions (`types.d.ts`) + + - Components are organized by their scope of use + - Each component may contain: + - Presentational logic (`Component.tsx`) + - UI-only subcomponents (`assets/*.tsx`) + - Component-specific hooks (`hooks/*.ts`) + - Local constants and utilities (`assets/*.ts`) + - Type definitions (`types.d.ts`) 3. **Code Movement Guidelines** The following rules determine where code should live: - - Module-specific code stays within the module - - Components used across multiple modules move to root `/components` - - Hooks used across multiple modules move to root `/hooks` - - UI elements, constants, or utilities used across modules move to root `/assets` - - Types used across modules move to root `types.d.ts` + - Module-specific code stays within the module + - Components used across multiple modules move to root `/components` + - Hooks used across multiple modules move to root `/hooks` + - UI elements, constants, or utilities used across modules move to root `/assets` + - Types used across modules move to root `types.d.ts` #### State Management 1. **Store Organization** - - Each module can have its own `store` folder containing: - - Jotai atoms for reactive state - - Context providers for complex state/dependency injection - - Global store at root level for cross-module state + + - Each module can have its own `store` folder containing: + - Jotai atoms for reactive state + - Context providers for complex state/dependency injection + - Global store at root level for cross-module state 2. **State Movement Guidelines** - - State used only within a component stays as local state - - State shared between components in a module uses module-level store - - State shared across modules moves to root `/store` - - Consider these factors when choosing state location: - - Scope of state usage - - Frequency of updates - - Performance implications - - Data persistence requirements + + - State used only within a component stays as local state + - State shared between components in a module uses module-level store + - State shared across modules moves to root `/store` + - Consider these factors when choosing state location: + - Scope of state usage + - Frequency of updates + - Performance implications + - Data persistence requirements 3. **State Management Tools** - - Prefer Jotai atoms for simple reactive state - - Use Context for complex state with multiple consumers - - Local component state for UI-only concerns + - Prefer Jotai atoms for simple reactive state + - Use Context for complex state with multiple consumers + - Local component state for UI-only concerns #### Implementation Strategy -- **Current Approach**: Gradual adoption during regular development -- **Migration**: Update components to follow this structure as they are modified -- **No Big Bang**: Avoid large-scale refactoring -- **Progressive Enhancement**: Easy to implement incrementally +- **Current Approach**: Gradual adoption during regular development +- **Migration**: Update components to follow this structure as they are modified +- **No Big Bang**: Avoid large-scale refactoring +- **Progressive Enhancement**: Easy to implement incrementally This structure supports: -- Clear ownership and responsibility -- Easy code review and modification -- Identification of reusable patterns -- Natural code organization based on usage -- Scalable architecture that grows with the application +- Clear ownership and responsibility +- Easy code review and modification +- Identification of reusable patterns +- Natural code organization based on usage +- Scalable architecture that grows with the application ### Data Fetching Best Practices We recommend using SWR with Axios for data fetching instead of useEffect patterns. This helps achieve cleaner code while, -- simplifying management of fetch states. -- handling cache better -- having a more interactive UI by revalidating in background -- utilizing optimistic mutations. +- simplifying management of fetch states. +- handling cache better +- having a more interactive UI by revalidating in background +- utilizing optimistic mutations. #### Example: Converting useEffect Data Fetching to SWR with Axios @@ -192,18 +196,22 @@ We recommend using SWR with Axios for data fetching instead of useEffect pattern ```javascript useEffect(() => { - fetchData1().then(data1 => { - setData1(data1); - }).catch(error => { - setError1(error); - }); - - fetchData2().then(data2 => { - setData2(data2); - }).catch(error => { - setError2(error); - }); -}, []); + fetchData1() + .then((data1) => { + setData1(data1) + }) + .catch((error) => { + setError1(error) + }) + + fetchData2() + .then((data2) => { + setData2(data2) + }) + .catch((error) => { + setError2(error) + }) +}, []) ``` ✅ **Use this pattern:** @@ -212,52 +220,52 @@ We configure SWR globally with our pre-configured Axios instance: ```javascript // src/utils/swrConfig.js -import axios from '@/lib/helpers/axios'; -import useSWR from 'swr'; +import axios from "@/lib/helpers/axios" +import useSWR from "swr" -const fetcher = url => axios.get(url).then(res => res.data); +const fetcher = (url) => axios.get(url).then((res) => res.data) export const swrConfig = { - fetcher, -}; + fetcher, +} ``` To ensure SWR configuration is applied globally, wrap your application with SWRConfig in `_app.tsx`: ```javascript // src/pages/_app.tsx -import { SWRConfig } from 'swr'; -import { swrConfig } from '../utils/swrConfig'; - -function MyApp({ Component, pageProps }) { - return ( - - - - ); +import {SWRConfig} from "swr" +import {swrConfig} from "../utils/swrConfig" + +function MyApp({Component, pageProps}) { + return ( + + + + ) } -export default MyApp; +export default MyApp ``` and data can be then be fetched in a way that fits react mental model inside the component: ```javascript -import useSWR from 'swr'; +import useSWR from "swr" function Component() { - const { data: data1, error: error1, loading: loadingData1 } = useSWR('/api/data1'); - const { data: data2, error: error2, loading: loadingData2 } = useSWR('/api/data2'); - - if (error1 || error2) return
Error loading data
; - if (!data1 || !data2) return
Loading...
; - - return ( -
-
Data 1: {data1}
-
Data 2: {data2}
-
- ); + const {data: data1, error: error1, loading: loadingData1} = useSWR("/api/data1") + const {data: data2, error: error2, loading: loadingData2} = useSWR("/api/data2") + + if (error1 || error2) return
Error loading data
+ if (!data1 || !data2) return
Loading...
+ + return ( +
+
Data 1: {data1}
+
Data 2: {data2}
+
+ ) } ``` @@ -265,17 +273,17 @@ Mutations can be triggered via Swr in the following way ```javascript import useSWRMutation from 'swr/mutation' - + async function sendRequest(url, { arg }: { arg: { username: string }}) { return fetch(url, { method: 'POST', body: JSON.stringify(arg) }).then(res => res.json()) } - + function App() { const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest, /* options */) - + return (