Improve build performance 2x (#18449)
## Summary Front Before: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/b978f67c-c0a6-49fc-bedd-a443f11c365d" /> Front After: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/a4939dbb-a8b4-4c74-978c-daa7f27d00f3" /> Server Before: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/da53e97f-ec65-4224-a656-ca41040aef6e" /> Server After: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/8cdf3885-f515-4d6c-989f-a421a4e8206c" /> ### CI Server Pipeline Restructuring - Split monolithic `server-setup` job into three parallel jobs: `server-build`, `server-lint-typecheck`, and `server-validation` - `server-build` only handles build + Nx cache save (~1m vs old 3.5m), unblocking downstream jobs faster - `server-lint-typecheck` runs in parallel with no DB dependency - `server-validation` handles DB setup, migration checks, and GraphQL generation checks in parallel with tests - Make `server-test` (unit tests) fully independent — no longer waits for server-setup, builds its own artifacts - Increase integration test shards from 8 to 10 for better parallelism - Expected critical path reduction: ~10m → ~7m (~30% faster) ### CI Front Pipeline Improvements - Use artifact upload/download for storybook build instead of rebuilding in test shards - Serve pre-built storybook via `http-server` in test jobs, with `STORYBOOK_URL` env var - Update `vitest.config.ts` to use `storybookUrl` when `STORYBOOK_URL` is set - Remove redundant `twenty-shared`, `twenty-ui`, `twenty-sdk` builds from storybook test shards ### Vite Build Optimizations - Conditionally enable `rollup-plugin-visualizer` behind `ANALYZE=true` env var (not loaded by default) - Broaden Istanbul coverage exclusions to skip test files, stories, mocks, and decorators - Remove `@tabler/icons-react` alias from twenty-front and storybook configs - Bundle `@tabler/icons-react` into twenty-ui instead of treating it as an external dependency - Add lazy loading with `React.lazy` + `Suspense` for all page-level route components in `useCreateAppRouter`
This commit is contained in:
@@ -62,6 +62,12 @@ jobs:
|
||||
run: npx nx reset:env twenty-front
|
||||
- name: Front / Build storybook
|
||||
run: npx nx storybook:build twenty-front
|
||||
- name: Upload storybook build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: packages/twenty-front/storybook-static
|
||||
retention-days: 1
|
||||
- name: Save storybook build cache
|
||||
uses: ./.github/actions/save-cache
|
||||
with:
|
||||
@@ -78,6 +84,7 @@ jobs:
|
||||
env:
|
||||
SHARD_COUNTER: 4
|
||||
REACT_APP_SERVER_BASE_URL: http://localhost:3000
|
||||
STORYBOOK_URL: http://localhost:6006
|
||||
steps:
|
||||
- name: Fetch local actions
|
||||
uses: actions/checkout@v4
|
||||
@@ -85,19 +92,33 @@ jobs:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore storybook build cache
|
||||
uses: ./.github/actions/restore-cache
|
||||
with:
|
||||
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION }}
|
||||
- name: Clean stale storybook vitest cache
|
||||
run: rm -rf packages/twenty-front/node_modules/.cache/storybook
|
||||
- name: Build dependencies
|
||||
run: |
|
||||
npx nx build twenty-shared
|
||||
npx nx build twenty-ui
|
||||
npx nx build twenty-sdk
|
||||
- name: Download storybook build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: storybook-static
|
||||
path: packages/twenty-front/storybook-static
|
||||
- name: Install Playwright
|
||||
run: |
|
||||
cd packages/twenty-front
|
||||
npx playwright install
|
||||
- name: Front / Write .env
|
||||
run: npx nx reset:env twenty-front
|
||||
- name: Run storybook tests
|
||||
run: npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
|
||||
- name: Serve storybook & run tests
|
||||
run: |
|
||||
npx http-server packages/twenty-front/storybook-static --port 6006 --silent &
|
||||
timeout 30 bash -c 'until curl -sf http://localhost:6006 > /dev/null 2>&1; do sleep 1; done'
|
||||
npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
|
||||
# - name: Rename coverage file
|
||||
# run: |
|
||||
# if [ -f "packages/twenty-front/coverage/storybook/coverage-final.json" ]; then
|
||||
@@ -214,6 +235,7 @@ jobs:
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=10240"
|
||||
ANALYZE: "true"
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
|
||||
@@ -13,7 +13,7 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
SERVER_SETUP_CACHE_KEY: server-setup
|
||||
SERVER_BUILD_CACHE_KEY: server-build
|
||||
|
||||
jobs:
|
||||
changed-files-check:
|
||||
@@ -27,11 +27,59 @@ jobs:
|
||||
packages/twenty-front/src/generated-metadata/**
|
||||
packages/twenty-emails/**
|
||||
packages/twenty-shared/**
|
||||
server-setup:
|
||||
|
||||
server-build:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore server build cache
|
||||
id: restore-server-build-cache
|
||||
uses: ./.github/actions/restore-cache
|
||||
with:
|
||||
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
|
||||
- name: Build twenty-shared
|
||||
run: npx nx build twenty-shared
|
||||
- name: Server / Write .env
|
||||
run: npx nx reset:env twenty-server
|
||||
- name: Server / Build
|
||||
run: npx nx build twenty-server
|
||||
- name: Save server build cache
|
||||
uses: ./.github/actions/save-cache
|
||||
with:
|
||||
key: ${{ steps.restore-server-build-cache.outputs.cache-primary-key }}
|
||||
|
||||
server-lint-typecheck:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build twenty-shared
|
||||
run: npx nx build twenty-shared
|
||||
- name: Server / Run lint & typecheck
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
tag: scope:backend
|
||||
tasks: lint,typecheck
|
||||
|
||||
server-validation:
|
||||
needs: server-build
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
@@ -58,18 +106,12 @@ jobs:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore server setup
|
||||
id: restore-server-setup-cache
|
||||
- name: Restore server build cache
|
||||
uses: ./.github/actions/restore-cache
|
||||
with:
|
||||
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
|
||||
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
|
||||
- name: Build twenty-shared
|
||||
run: npx nx build twenty-shared
|
||||
- name: Server / Run lint & typecheck
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
tag: scope:backend
|
||||
tasks: lint,typecheck
|
||||
- name: Server / Write .env
|
||||
run: npx nx reset:env twenty-server
|
||||
- name: Server / Build
|
||||
@@ -84,10 +126,8 @@ jobs:
|
||||
run: |
|
||||
timeout 30s npx nx run twenty-server:worker || exit_code=$?
|
||||
if [ $exit_code -eq 124 ]; then
|
||||
# If timeout was reached (exit code 124), consider it a success
|
||||
exit 0
|
||||
elif [ $exit_code -ne 0 ]; then
|
||||
# If worker failed for other reasons, fail the build
|
||||
exit $exit_code
|
||||
fi
|
||||
- name: Server / Start
|
||||
@@ -120,11 +160,9 @@ jobs:
|
||||
fi
|
||||
- name: GraphQL / Check for Pending Generation
|
||||
run: |
|
||||
# Run GraphQL generation commands
|
||||
npx nx run twenty-front:graphql:generate
|
||||
npx nx run twenty-front:graphql:generate --configuration=metadata
|
||||
|
||||
# Check if GraphQL generated files were modified
|
||||
if ! git diff --quiet -- packages/twenty-front/src/generated packages/twenty-front/src/generated-metadata; then
|
||||
echo "::error::GraphQL schema changes detected. Please run 'npx nx run twenty-front:graphql:generate' and 'npx nx run twenty-front:graphql:generate --configuration=metadata' and commit the changes."
|
||||
echo ""
|
||||
@@ -137,14 +175,11 @@ jobs:
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
- name: Save server setup
|
||||
uses: ./.github/actions/save-cache
|
||||
with:
|
||||
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
|
||||
|
||||
server-test:
|
||||
needs: server-build
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: server-setup
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
@@ -152,10 +187,12 @@ jobs:
|
||||
fetch-depth: 10
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Restore server setup
|
||||
- name: Restore server build cache
|
||||
uses: ./.github/actions/restore-cache
|
||||
with:
|
||||
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
|
||||
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
|
||||
- name: Build twenty-shared
|
||||
run: npx nx build twenty-shared
|
||||
- name: Server / Run Tests
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
@@ -165,11 +202,11 @@ jobs:
|
||||
server-integration-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: server-setup
|
||||
needs: server-build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
@@ -207,7 +244,7 @@ jobs:
|
||||
ANALYTICS_ENABLED: true
|
||||
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
|
||||
CLICKHOUSE_PASSWORD: clickhousePassword
|
||||
SHARD_COUNTER: 8
|
||||
SHARD_COUNTER: 10
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
@@ -223,10 +260,10 @@ jobs:
|
||||
echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
|
||||
echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
|
||||
echo "BILLING_PLAN_REQUIRED_LINK=http://localhost:3001/stripe-redirection" >> .env.test
|
||||
- name: Restore server setup
|
||||
- name: Restore server build cache
|
||||
uses: ./.github/actions/restore-cache
|
||||
with:
|
||||
key: ${{ env.SERVER_SETUP_CACHE_KEY }}
|
||||
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
|
||||
- name: Server / Build
|
||||
run: npx nx build twenty-server
|
||||
- name: Build dependencies
|
||||
@@ -247,11 +284,20 @@ jobs:
|
||||
tasks: 'test:integration'
|
||||
configuration: 'with-db-reset'
|
||||
args: --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
|
||||
|
||||
ci-server-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, server-setup, server-test, server-integration-test]
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
server-build,
|
||||
server-lint-typecheck,
|
||||
server-validation,
|
||||
server-test,
|
||||
server-integration-test,
|
||||
]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
|
||||
@@ -58,11 +58,6 @@ const config: StorybookConfig = {
|
||||
|
||||
return mergeConfig(viteConfig, {
|
||||
logLevel: 'warn',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ReactNode, Suspense } from 'react';
|
||||
|
||||
type LazyRouteProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const LazyRoute = ({ children }: LazyRouteProps) => (
|
||||
<Suspense fallback={<></>}>{children}</Suspense>
|
||||
);
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AppRouterProviders } from '@/app/components/AppRouterProviders';
|
||||
import { LazyRoute } from '@/app/components/LazyRoute';
|
||||
import { SettingsRoutes } from '@/app/components/SettingsRoutes';
|
||||
import { VerifyLoginTokenEffect } from '@/auth/components/VerifyLoginTokenEffect';
|
||||
|
||||
@@ -8,25 +9,96 @@ import { BlankLayout } from '@/ui/layout/page/components/BlankLayout';
|
||||
import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
import { lazy } from 'react';
|
||||
import {
|
||||
createBrowserRouter,
|
||||
createRoutesFromElements,
|
||||
Route,
|
||||
} from 'react-router-dom';
|
||||
import { Authorize } from '~/pages/auth/Authorize';
|
||||
import { PasswordReset } from '~/pages/auth/PasswordReset';
|
||||
import { SignInUp } from '~/pages/auth/SignInUp';
|
||||
import { NotFound } from '~/pages/not-found/NotFound';
|
||||
import { RecordIndexPage } from '~/pages/object-record/RecordIndexPage';
|
||||
import { RecordShowPage } from '~/pages/object-record/RecordShowPage';
|
||||
import { BookCall } from '~/pages/onboarding/BookCall';
|
||||
import { BookCallDecision } from '~/pages/onboarding/BookCallDecision';
|
||||
import { ChooseYourPlan } from '~/pages/onboarding/ChooseYourPlan';
|
||||
import { CreateProfile } from '~/pages/onboarding/CreateProfile';
|
||||
import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace';
|
||||
import { InviteTeam } from '~/pages/onboarding/InviteTeam';
|
||||
import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess';
|
||||
import { SyncEmails } from '~/pages/onboarding/SyncEmails';
|
||||
|
||||
const RecordIndexPage = lazy(() =>
|
||||
import('~/pages/object-record/RecordIndexPage').then((module) => ({
|
||||
default: module.RecordIndexPage,
|
||||
})),
|
||||
);
|
||||
|
||||
const RecordShowPage = lazy(() =>
|
||||
import('~/pages/object-record/RecordShowPage').then((module) => ({
|
||||
default: module.RecordShowPage,
|
||||
})),
|
||||
);
|
||||
|
||||
const SignInUp = lazy(() =>
|
||||
import('~/pages/auth/SignInUp').then((module) => ({
|
||||
default: module.SignInUp,
|
||||
})),
|
||||
);
|
||||
|
||||
const PasswordReset = lazy(() =>
|
||||
import('~/pages/auth/PasswordReset').then((module) => ({
|
||||
default: module.PasswordReset,
|
||||
})),
|
||||
);
|
||||
|
||||
const Authorize = lazy(() =>
|
||||
import('~/pages/auth/Authorize').then((module) => ({
|
||||
default: module.Authorize,
|
||||
})),
|
||||
);
|
||||
|
||||
const CreateWorkspace = lazy(() =>
|
||||
import('~/pages/onboarding/CreateWorkspace').then((module) => ({
|
||||
default: module.CreateWorkspace,
|
||||
})),
|
||||
);
|
||||
|
||||
const CreateProfile = lazy(() =>
|
||||
import('~/pages/onboarding/CreateProfile').then((module) => ({
|
||||
default: module.CreateProfile,
|
||||
})),
|
||||
);
|
||||
|
||||
const SyncEmails = lazy(() =>
|
||||
import('~/pages/onboarding/SyncEmails').then((module) => ({
|
||||
default: module.SyncEmails,
|
||||
})),
|
||||
);
|
||||
|
||||
const InviteTeam = lazy(() =>
|
||||
import('~/pages/onboarding/InviteTeam').then((module) => ({
|
||||
default: module.InviteTeam,
|
||||
})),
|
||||
);
|
||||
|
||||
const ChooseYourPlan = lazy(() =>
|
||||
import('~/pages/onboarding/ChooseYourPlan').then((module) => ({
|
||||
default: module.ChooseYourPlan,
|
||||
})),
|
||||
);
|
||||
|
||||
const PaymentSuccess = lazy(() =>
|
||||
import('~/pages/onboarding/PaymentSuccess').then((module) => ({
|
||||
default: module.PaymentSuccess,
|
||||
})),
|
||||
);
|
||||
|
||||
const BookCallDecision = lazy(() =>
|
||||
import('~/pages/onboarding/BookCallDecision').then((module) => ({
|
||||
default: module.BookCallDecision,
|
||||
})),
|
||||
);
|
||||
|
||||
const BookCall = lazy(() =>
|
||||
import('~/pages/onboarding/BookCall').then((module) => ({
|
||||
default: module.BookCall,
|
||||
})),
|
||||
);
|
||||
|
||||
const NotFound = lazy(() =>
|
||||
import('~/pages/not-found/NotFound').then((module) => ({
|
||||
default: module.NotFound,
|
||||
})),
|
||||
);
|
||||
|
||||
export const useCreateAppRouter = (
|
||||
isFunctionSettingsEnabled?: boolean,
|
||||
@@ -43,26 +115,59 @@ export const useCreateAppRouter = (
|
||||
<Route element={<DefaultLayout />}>
|
||||
<Route path={AppPath.Verify} element={<VerifyLoginTokenEffect />} />
|
||||
<Route path={AppPath.VerifyEmail} element={<VerifyEmailEffect />} />
|
||||
<Route path={AppPath.SignInUp} element={<SignInUp />} />
|
||||
<Route path={AppPath.Invite} element={<SignInUp />} />
|
||||
<Route path={AppPath.ResetPassword} element={<PasswordReset />} />
|
||||
<Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} />
|
||||
<Route path={AppPath.CreateProfile} element={<CreateProfile />} />
|
||||
<Route path={AppPath.SyncEmails} element={<SyncEmails />} />
|
||||
<Route path={AppPath.InviteTeam} element={<InviteTeam />} />
|
||||
<Route path={AppPath.PlanRequired} element={<ChooseYourPlan />} />
|
||||
<Route
|
||||
path={AppPath.SignInUp}
|
||||
element={<LazyRoute><SignInUp /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.Invite}
|
||||
element={<LazyRoute><SignInUp /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.ResetPassword}
|
||||
element={<LazyRoute><PasswordReset /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.CreateWorkspace}
|
||||
element={<LazyRoute><CreateWorkspace /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.CreateProfile}
|
||||
element={<LazyRoute><CreateProfile /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.SyncEmails}
|
||||
element={<LazyRoute><SyncEmails /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.InviteTeam}
|
||||
element={<LazyRoute><InviteTeam /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.PlanRequired}
|
||||
element={<LazyRoute><ChooseYourPlan /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.PlanRequiredSuccess}
|
||||
element={<PaymentSuccess />}
|
||||
element={<LazyRoute><PaymentSuccess /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.BookCallDecision}
|
||||
element={<BookCallDecision />}
|
||||
element={<LazyRoute><BookCallDecision /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.BookCall}
|
||||
element={<LazyRoute><BookCall /></LazyRoute>}
|
||||
/>
|
||||
<Route path={AppPath.BookCall} element={<BookCall />} />
|
||||
<Route path={indexAppPath.getIndexAppPath()} element={<></>} />
|
||||
<Route path={AppPath.RecordIndexPage} element={<RecordIndexPage />} />
|
||||
<Route path={AppPath.RecordShowPage} element={<RecordShowPage />} />
|
||||
<Route
|
||||
path={AppPath.RecordIndexPage}
|
||||
element={<LazyRoute><RecordIndexPage /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.RecordShowPage}
|
||||
element={<LazyRoute><RecordShowPage /></LazyRoute>}
|
||||
/>
|
||||
<Route
|
||||
path={AppPath.SettingsCatchAll}
|
||||
element={
|
||||
@@ -72,10 +177,16 @@ export const useCreateAppRouter = (
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path={AppPath.NotFoundWildcard} element={<NotFound />} />
|
||||
<Route
|
||||
path={AppPath.NotFoundWildcard}
|
||||
element={<LazyRoute><NotFound /></LazyRoute>}
|
||||
/>
|
||||
</Route>
|
||||
<Route element={<BlankLayout />}>
|
||||
<Route path={AppPath.Authorize} element={<Authorize />} />
|
||||
<Route
|
||||
path={AppPath.Authorize}
|
||||
element={<LazyRoute><Authorize /></LazyRoute>}
|
||||
/>
|
||||
</Route>
|
||||
</Route>,
|
||||
),
|
||||
|
||||
+9
-19
@@ -1,26 +1,19 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { expect, userEvent, within } from 'storybook/test';
|
||||
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { MAIN_COLOR_NAMES } from 'twenty-ui/theme';
|
||||
const StyledContainer = styled.div`
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
width: 300px;
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof ExpandableList> = {
|
||||
title: 'UI/Layout/ExpandableList/ExpandableList',
|
||||
component: ExpandableList,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<StyledContainer>
|
||||
<div style={{ padding: 4, width: 300 }}>
|
||||
<Story />
|
||||
</StyledContainer>
|
||||
</div>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
@@ -50,19 +43,16 @@ export const WithChipCount: Story = {
|
||||
|
||||
export const WithExpandedList: Story = {
|
||||
...WithChipCount,
|
||||
play: async () => {
|
||||
const root = document.getElementsByTagName('body')[0];
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
if (!isDefined(root)) {
|
||||
throw new Error('Root element not found');
|
||||
}
|
||||
|
||||
const rootCanvas = within(root);
|
||||
|
||||
const chipCount = await rootCanvas.findByText(/^\+\d+$/);
|
||||
const chipCount = await canvas.findByText(/^\+\d+$/);
|
||||
|
||||
await userEvent.click(chipCount);
|
||||
|
||||
expect(await rootCanvas.findByText('Option 7')).toBeDefined();
|
||||
const body = canvasElement.ownerDocument.body;
|
||||
const bodyCanvas = within(body);
|
||||
|
||||
expect(await bodyCanvas.findByText('Option 7')).toBeDefined();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -89,8 +89,13 @@ export default defineConfig(({ mode }) => {
|
||||
exclude: [
|
||||
'**/generated-metadata/**',
|
||||
'**/testing/mock-data/generated/**',
|
||||
'**/testing/**',
|
||||
'**/*.test.{ts,tsx}',
|
||||
'**/*.spec.{ts,tsx}',
|
||||
'**/*.stories.{ts,tsx}',
|
||||
'**/__stories__/**',
|
||||
'**/__tests__/**',
|
||||
'**/__mocks__/**',
|
||||
'**/types/**',
|
||||
'**/constants/**',
|
||||
'**/states/**',
|
||||
@@ -105,6 +110,7 @@ export default defineConfig(({ mode }) => {
|
||||
'**/mutations/**',
|
||||
'**/fragments/**',
|
||||
'**/graphql/**',
|
||||
'**/decorators/**',
|
||||
],
|
||||
babelOptions: {
|
||||
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
||||
@@ -112,12 +118,16 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
}),
|
||||
),
|
||||
visualizer({
|
||||
open: true,
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
filename: 'dist/stats.html',
|
||||
}) as PluginOption, // https://github.com/btd/rollup-plugin-visualizer/issues/162#issuecomment-1538265997,
|
||||
...(env.ANALYZE === 'true'
|
||||
? [
|
||||
visualizer({
|
||||
open: !process.env.CI,
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
filename: 'dist/stats.html',
|
||||
}) as PluginOption,
|
||||
]
|
||||
: []),
|
||||
],
|
||||
|
||||
optimizeDeps: {
|
||||
@@ -236,7 +246,6 @@ export default defineConfig(({ mode }) => {
|
||||
resolve: {
|
||||
alias: {
|
||||
path: 'rollup-plugin-node-polyfills/polyfills/path',
|
||||
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -24,7 +24,9 @@ export default defineConfig({
|
||||
plugins: [
|
||||
storybookTest({
|
||||
configDir: path.join(dirname, '.storybook'),
|
||||
storybookScript: 'yarn storybook --no-open',
|
||||
...(process.env.STORYBOOK_URL
|
||||
? { storybookUrl: process.env.STORYBOOK_URL }
|
||||
: { storybookScript: 'yarn storybook --no-open' }),
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
|
||||
+3
-3
@@ -2,17 +2,17 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AiAgentExecutionModule } from 'src/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module';
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
import { AiChatModule } from 'src/engine/metadata-modules/ai/ai-chat/ai-chat.module';
|
||||
import { AgentChatThreadEntity } from 'src/engine/metadata-modules/ai/ai-chat/entities/agent-chat-thread.entity';
|
||||
import { AgentTurnEntity } from 'src/engine/metadata-modules/ai/ai-agent-execution/entities/agent-turn.entity';
|
||||
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
|
||||
import { AgentTurnEvaluationEntity } from './entities/agent-turn-evaluation.entity';
|
||||
import { EvaluateAgentTurnJob } from './jobs/evaluate-agent-turn.job';
|
||||
import { RunEvaluationInputJob } from './jobs/run-evaluation-input.job';
|
||||
import { AgentTurnEvaluationEntity } from './entities/agent-turn-evaluation.entity';
|
||||
import { AgentTurnResolver } from './resolvers/agent-turn.resolver';
|
||||
import { AgentTurnGraderService } from './services/agent-turn-grader.service';
|
||||
|
||||
@@ -21,8 +21,8 @@ import { AgentTurnGraderService } from './services/agent-turn-grader.service';
|
||||
TypeOrmModule.forFeature([
|
||||
AgentTurnEvaluationEntity,
|
||||
AgentTurnEntity,
|
||||
AgentEntity,
|
||||
AgentChatThreadEntity,
|
||||
AgentEntity,
|
||||
]),
|
||||
AiAgentModule,
|
||||
AiAgentExecutionModule,
|
||||
|
||||
@@ -56,11 +56,15 @@ export default defineConfig(({ command }) => {
|
||||
tsconfigPath: tsConfigPath,
|
||||
};
|
||||
|
||||
const BUNDLED_DEPS = ['@tabler/icons-react'];
|
||||
|
||||
return {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@ui/': path.resolve(__dirname, 'src') + '/',
|
||||
'@assets/': path.resolve(__dirname, 'src/assets') + '/',
|
||||
'@tabler/icons-react':
|
||||
'@tabler/icons-react/dist/esm/icons/index.mjs',
|
||||
},
|
||||
},
|
||||
css: {
|
||||
@@ -122,7 +126,9 @@ export default defineConfig(({ command }) => {
|
||||
name: 'twenty-ui',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: Object.keys(packageJson.dependencies || {}),
|
||||
external: Object.keys(packageJson.dependencies || {}).filter(
|
||||
(dep) => !BUNDLED_DEPS.includes(dep),
|
||||
),
|
||||
output: [
|
||||
{
|
||||
assetFileNames: 'style.css',
|
||||
|
||||
Reference in New Issue
Block a user