diff --git a/README.md b/README.md index 53f5305a..e6e85d89 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,31 @@ [SPDX-License-Identifier: Apache-2.0]:: [SPDX-FileCopyrightText: 2021-2023 OKTET Labs Ltd.]:: - # Bublik UI -## Package manager - -We use pnpm as our package manager so to start you will need to install it, please refer to [pnpm documentation](https://pnpm.io/) for installing instructions - -All commands should be run like this `pnpm exec nx ...etc` or you can add alias to your .bashrc like this `alias pnx="pnpm nx --"`, then you can run like this `pnx ...` - -## Monorepo tools - -We use [Nx](https://nx.dev/getting-started/intro) for monorepo, you would need to install nx cli globally or run commands via `npx` or `pnpm dlx...` - -## Project structure - -``` -📦 dist - ┣ 📂 apps - Built production ready applications -📦 apps -┣ 📂 bublik - Bublik UI Application -📦 libs - ┣ 📂 bublik - Bublik UI application specific libs - ┃ ┣ 📂 +state - Redux state - ┃ ┣ 📂 config - Different Bublik UI configs (needed for UI to work correctly) - ┃ ┣ 📂 features - Specific Bublik UI featires implementation - ┃ ┣ 📂 router - Router helpers, encoding/decoding search params - ┣ 📂 env - Env helper lib - ┣ 📂 services - General services for data-access - ┃ ┗ 📂 bublik-api - Bublik API hooks/methods (RTK Query) - ┣ 📂 shared - Shared libraries for multiple apps to consume - ┃ ┣ 📂 charts - Apache Echarts components - ┃ ┣ 📂 hooks - Shared react hooks - ┃ ┣ 📂 icons - UI Icons - ┃ ┣ 📂 tailwind-ui - Shared UI components - ┃ ┣ 📂 types - Shared interfaces and types - ┃ ┣ 📂 utils - Shared general utils -``` - -## Config - -To create new Bublik UI frontend for deployment you would need to follow these steps: - -1. Add needed configuration to `apps/bublik/project.json` -2. `base` - where the app will be mounted it **must start with `/`** and **no** trailing slash at the end - -Example: - -```json -{ - "demo": { - "base": "/prefix", - "outputPath": "dist/apps/bublik-app" - } -} -``` - -## Local development - -Make sure you installed nrwl [nx monorepo tools](https://nx.dev/getting-started/intro) -Example: 'pnpm add -g nx' - -To start development, please follow this steps: - -1. Clone repo -2. Run `pnpm install` to install dependencies -3. Run `pnpm start` to start development server +## Before you start (skip if you want to run via docker) -## Commands +1. Make sure you installed node, you can use [fnm](https://github.com/Schniz/fnm) or [nvm](https://github.com/nvm-sh/nvm) +2. Make sure you installed [pnpm](https://pnpm.io/) +3. Make sure you installed [nx](https://nx.dev/getting-started/installation#installing-nx-globally) globally +4. Make sure you installed [docker](https://docs.docker.com/desktop/) (you can install docker desktop or just docker engine) +5. Create env file `apps/bublik/.env.local` (see `apps/bublik/.env.local.example` for reference) -- `pnpm start` - starts Bublik UI in development mode -- `pnpm test` - runs tests in parallel for all projects -- `pnpm bublik:serve:prod` - starts Bublik UI in production mode -- `pnpm bublik:storybook` - starts storybook for Bublik UI -- `pnpm bublik:build-storybook` - builds storybook for Bublik UI -- `pnpm bublik:ci:build` - builds all Bublik UI application in production mode +### To test everything working run following commands -## Misc +1. `node -v` used node version in file `.nvmrc` +2. `pnpm -v` +3. `nx --version` +4. `docker version` -- If you want to connect to particular backend remote or local you can proxy all you requests with `apps/bublik/vite.config.ts` +## Run locally (run with docker) -## Common errors and solutions +You can run UI the following way: -- `pnpm ...` - command not working - - try to run command with `pnpm exec nx ...` -- Error installing packages like `react-vtree` and `legacy-peer-deps` errors - - try to add flag `legacy-peer-deps` -- Tailwind classes not working in storybook - - try adding lib/app path to `libs/shared/storybook/tailwind.config.js` -- Storybook not finding stories in your lib/app - - try adding lib/app path to `libs/shared/storybook/.storybook/main.js` -- Storybook tailwind classes not working - - try removing `node_module` and installing with `pnpm install` +1. Create env file `apps/bublik/.env.local` (see `apps/bublik/.env.local.example` for reference) +2. Build image `pnpm run docker:build` OR `docker build -f apps/bublik/Dockerfile.dev . -t bublik-ui` +3. Run image `pnpm run docker:start` OR `docker run -it --rm -p 4200:4200 -v $(pwd):/app -v /app/node_modules --env-file apps/bublik/.env.local bublik-ui` -## Release +Caveats: +- Add flag --network host to run image command if django is served from host +- If you add new dependencies to package.json you need to rebuild image -We are using release-it to automate releases. To release new version of Bublik UI you need to run `pnpm release` and follow the instructions. -Or run `pnpm release -- --dry-run` to see what will be done. -You can also release from GitHub actions menu specifying what type of release you want to do. diff --git a/apps/bublik/.env.local.example b/apps/bublik/.env.local.example index dc8c3c4b..094f7d55 100644 --- a/apps/bublik/.env.local.example +++ b/apps/bublik/.env.local.example @@ -1,6 +1,16 @@ -# Username and password used to proxy requests to JSON logs -BUBLIK_UI_DEV_LOGS_AUTH=":" +# This configuration is necessary for proper proxying of requests to django backend + +##################################################### +# Example for connecting to ts-factory +# BUBLIK_UI_DEV_LOGS_TARGET=https://ts-factory.io +# BUBLIK_UI_DEV_BACKEND_TARGET=https://ts-factory.io +# URL_PREFIX=/bublik/v2 + +##################################################### +# This examples shows how to setup env for frontend served from `http://localhost/prefix/v2` # Target of JSON logs (protocol, host, port) -BUBLIK_UI_DEV_LOGS_TARGET="https://example.com" +BUBLIK_UI_DEV_LOGS_TARGET=http://localhost # Where backend is served (protocol, host, port) -BUBLIK_UI_DEV_BACKEND_TARGET="http://localhost:8000" +BUBLIK_UI_DEV_BACKEND_TARGET=http://localhost +# Adds prefix from where backend is served e.g (http://localhost/prefix/api/v2) +URL_PREFIX=/prefix/v2 diff --git a/apps/bublik/.gitignore b/apps/bublik/.gitignore index 33ff8f96..8c1c3dfa 100644 --- a/apps/bublik/.gitignore +++ b/apps/bublik/.gitignore @@ -1,2 +1,3 @@ # Local .env.local +.env diff --git a/apps/bublik/Dockerfile.dev b/apps/bublik/Dockerfile.dev index b4a43c13..14c94d19 100644 --- a/apps/bublik/Dockerfile.dev +++ b/apps/bublik/Dockerfile.dev @@ -1,19 +1,22 @@ -FROM node:17.9.0 +FROM node:20-slim AS base -ENV PNPM_HOME="/root/.local/share/pnpm" -ENV PATH="${PATH}:${PNPM_HOME}" +ARG PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 -RUN npm install --global pnpm +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" -RUN pnpm add -g nx +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=${PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD} +ENV BASE_URL="/v2" +RUN corepack enable + +COPY . /app WORKDIR /app -COPY package.json pnpm-lock.yaml ./ -RUN pnpm install +FROM base as runner -COPY . . +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install EXPOSE 4200 -CMD ["pnpm", "run", "nx", "serve", "--host=0.0.0.0"] +CMD ["pnpm", "run", "nx", "serve", "--host=0.0.0.0", "--base=${BASE_URL}"] diff --git a/apps/bublik/vite.config.ts b/apps/bublik/vite.config.ts index 73abc0b6..070d75a2 100644 --- a/apps/bublik/vite.config.ts +++ b/apps/bublik/vite.config.ts @@ -1,41 +1,66 @@ /* SPDX-License-Identifier: Apache-2.0 */ /* SPDX-FileCopyrightText: 2021-2023 OKTET Labs Ltd. */ -import { defineConfig, loadEnv } from 'vite'; + +import { defineConfig, HttpProxy, loadEnv, ProxyOptions } from 'vite'; import react from '@vitejs/plugin-react'; import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; import svgr from 'vite-plugin-svgr'; +const createRequestLogger = + (domain: string) => (proxy: HttpProxy.Server, _options: ProxyOptions) => { + proxy.on('error', (err, _req, _res) => { + console.log(`[${domain}] Error:`, err); + }); + proxy.on('proxyReq', (proxyReq, req, _res) => { + console.log(`[${domain}] Request:`, req.method, req.url); + }); + proxy.on('proxyRes', (proxyRes, req, _res) => { + console.log(`[${domain}] Response:`, proxyRes.statusCode, req.url); + }); + }; + export default defineConfig(async ({ mode }) => { const mdx = await import('@mdx-js/rollup'); let env: Record = {}; if (mode === 'development') { // You need to run `pnpm start again if you change env to load it correctly` - env = loadEnv(mode, process.cwd(), 'BUBLIK_UI_DEV'); + env = loadEnv(mode, process.cwd(), ''); } + const URL_PREFIX = env.BASE_URL?.replace('/v2', ''); + const DJANGO_TARGET = env.BUBLIK_UI_DEV_BACKEND_TARGET; + const LOGS_TARGET = env.BUBLIK_UI_DEV_LOGS_TARGET; + + // Derived + const API_PATHNAME = `${URL_PREFIX}/api/v2`; + const AUTH_PATHNAME = `${URL_PREFIX}/auth`; + const EXTERNAL_PATHNAME = `${URL_PREFIX}/external`; + return { + root: __dirname, server: { port: 4200, host: 'localhost', proxy: { - '/api/v2': { - target: env['BUBLIK_UI_DEV_BACKEND_TARGET'], + [API_PATHNAME]: { + target: DJANGO_TARGET, changeOrigin: true, - secure: false + secure: false, + configure: createRequestLogger('API') }, - '/auth': { - target: env['BUBLIK_UI_DEV_BACKEND_TARGET'], + [AUTH_PATHNAME]: { + target: DJANGO_TARGET, changeOrigin: true, - secure: false + secure: false, + configure: createRequestLogger('AUTH') }, - '/external': { - target: env['BUBLIK_UI_DEV_LOGS_TARGET'], + [EXTERNAL_PATHNAME]: { + target: LOGS_TARGET, changeOrigin: true, secure: false, - auth: env['BUBLIK_UI_DEV_LOGS_AUTH'], followRedirects: true, - rewrite: (path) => { + rewrite: (path: string) => { const externalUrl = /=([^&]+)/.exec(path)?.[1]; if (!externalUrl) { @@ -45,7 +70,7 @@ export default defineConfig(async ({ mode }) => { console.log(`[PROXY] Rewrite path: ${path}`); console.log(`[PROXY] External URL: ${externalUrl}`); - return externalUrl; + return externalUrl.replace(LOGS_TARGET, ''); } } } @@ -60,6 +85,9 @@ export default defineConfig(async ({ mode }) => { ], build: { + outDir: '../../dist/apps/bublik', + reportCompressedSize: true, + commonjsOptions: { transformMixedEsModules: true }, rollupOptions: { output: { manualChunks: { @@ -80,6 +108,11 @@ export default defineConfig(async ({ mode }) => { // }, test: { + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/apps/bublik', + provider: 'v8' + }, globals: true, cache: { dir: '../../node_modules/.vitest' diff --git a/libs/services/bublik-api/src/lib/endpoints/log-endpoints.ts b/libs/services/bublik-api/src/lib/endpoints/log-endpoints.ts index 91849fc1..4296a7d0 100644 --- a/libs/services/bublik-api/src/lib/endpoints/log-endpoints.ts +++ b/libs/services/bublik-api/src/lib/endpoints/log-endpoints.ts @@ -19,6 +19,7 @@ import { BublikHttpError, isBublikParsableError } from '../error-handling'; +import { config } from '@/bublik/config'; type GetLogJsonInputs = { id: string | number; @@ -63,7 +64,9 @@ export const logEndpoints = { const options: RequestInit = { credentials: 'include' }; - const response = await fetch(externalUrl, options); + const response = config.isDev + ? await fetch(`${config.rootUrl}/external?url=${externalUrl}`) + : await fetch(externalUrl, options); if (!response.ok) throw getBublikFromStatusCode(response); diff --git a/package.json b/package.json index 2e01a5a4..902e7587 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "nx": "nx", "start": "nx serve", "build": "nx build", + "docker:build": "docker build -f apps/bublik/Dockerfile.dev . -t bublik-ui", + "docker:start": "docker run -it --rm -p 4200:4200 -v $(pwd):/app -v /app/node_modules --env-file apps/bublik/.env.local bublik-ui", "bublik:build-all": "nx run bublik:build-all", "bublik:build-storybook": "nx run bublik:storybook:ci", "bublik:storybook": "nx run bublik:storybook",