Browse Source

Merge branch 'main' of github.com:zilliztech/attu into main

nameczz 2 years ago
parent
commit
f711387548
83 changed files with 3586 additions and 10234 deletions
  1. 1 0
      client/.env.development
  2. 1 0
      client/.env.production
  3. 0 24
      client/README.md
  4. 0 7
      client/config-overrides.js
  5. 29 0
      client/index.html
  6. 32 37
      client/package.json
  7. BIN
      client/public/favicon.ico
  8. 0 47
      client/public/index.html
  9. BIN
      client/public/logo.png
  10. BIN
      client/public/logo192.png
  11. BIN
      client/public/logo512.png
  12. 0 25
      client/public/manifest.json
  13. 3 3
      client/src/assets/icons/attu.svg
  14. 7 10
      client/src/components/__test__/cards/EmptyCard.spec.tsx
  15. 5 33
      client/src/components/__test__/customButton/CustomButton.spec.tsx
  16. 41 53
      client/src/components/__test__/customDialog/CustomDialog.spec.tsx
  17. 2 2
      client/src/components/__test__/customDialog/CustomDialogTitle.spec.tsx
  18. 3 2
      client/src/components/__test__/customDialog/DeleteDialogTemplate.spec.tsx
  19. 3 2
      client/src/components/__test__/customDialog/DialogTemplate.spec.tsx
  20. 49 76
      client/src/components/__test__/customInput/CustomInput.spec.tsx
  21. 10 10
      client/src/components/__test__/customInput/SearchInput.spec.tsx
  22. 0 121
      client/src/components/__test__/customSelector/CustomGroupedSelect.spec.tsx
  23. 6 30
      client/src/components/__test__/customSnackBar/CustomSnackBar.spec.tsx
  24. 12 9
      client/src/components/__test__/customToolTip/CustomToolTip.spec.tsx
  25. 40 75
      client/src/components/__test__/grid/Grid.spec.tsx
  26. 3 2
      client/src/components/__test__/grid/IconBtnCell.spec.tsx
  27. 9 0
      client/src/components/__test__/grid/LoadingTable.spec.tsx
  28. 43 121
      client/src/components/__test__/grid/Table.spec.tsx
  29. 49 87
      client/src/components/__test__/grid/TableHead.spec.tsx
  30. 30 76
      client/src/components/__test__/grid/Toolbar.spec.tsx
  31. 1 1
      client/src/components/__test__/grid/Utils.spec.ts
  32. 2 1
      client/src/components/__test__/layout/GlobalEffect.spec.tsx
  33. 33 44
      client/src/components/__test__/layout/Layout.spec.tsx
  34. 3 29
      client/src/components/__test__/menu/SimpleMenu.spec.tsx
  35. 9 36
      client/src/components/__test__/status/Status.spec.tsx
  36. 2 2
      client/src/components/customDialog/CustomDialogTitle.tsx
  37. 22 20
      client/src/components/customInput/CustomInput.tsx
  38. 1 1
      client/src/components/grid/Grid.tsx
  39. 1 1
      client/src/components/grid/LoadingTable.tsx
  40. 6 4
      client/src/components/grid/Table.tsx
  41. 3 3
      client/src/components/grid/TableHead.tsx
  42. 3 1
      client/src/components/grid/ToolBar.tsx
  43. 7 2
      client/src/components/icons/Icons.tsx
  44. 2 1
      client/src/components/icons/Types.ts
  45. 145 0
      client/src/components/insert/ImportSample.tsx
  46. 15 22
      client/src/components/layout/Layout.tsx
  47. 2 2
      client/src/http/Axios.ts
  48. 8 0
      client/src/http/Collection.ts
  49. 1 1
      client/src/http/User.ts
  50. 1 1
      client/src/i18n/cn/success.ts
  51. 4 0
      client/src/i18n/en/button.ts
  52. 1 0
      client/src/i18n/en/collection.ts
  53. 4 0
      client/src/i18n/en/insert.ts
  54. 9 4
      client/src/pages/collections/Collection.tsx
  55. 70 6
      client/src/pages/collections/Collections.tsx
  56. 6 0
      client/src/pages/collections/Types.ts
  57. 3 1
      client/src/pages/connect/AuthForm.tsx
  58. 0 2
      client/src/pages/connect/ConnectContainer.tsx
  59. 196 0
      client/src/pages/preview/Preview.tsx
  60. 2 0
      client/src/pages/query/Types.ts
  61. 1 39
      client/src/pages/schema/IndexTypeElement.tsx
  62. 9 9
      client/src/pages/user/User.tsx
  63. 1 1
      client/src/plugins/search/config.json
  64. 1 1
      client/src/plugins/system/config.json
  65. 11 12
      client/src/router/Config.ts
  66. 4 0
      client/src/utils/Common.ts
  67. 2 2
      client/src/utils/__test__/Validation.spec.ts
  68. 9 11
      client/tsconfig.json
  69. 0 9
      client/tsconfig.paths.json
  70. 25 0
      client/vite.config.ts
  71. 19 0
      client/vitest.config.ts
  72. 2178 8873
      client/yarn.lock
  73. 0 35
      server/generate-csv.ts
  74. 4 5
      server/package.json
  75. 4 4
      server/src/__tests__/__mocks__/consts.ts
  76. 18 0
      server/src/__tests__/collections/collections.service.test.ts
  77. 1 1
      server/src/app.ts
  78. 18 0
      server/src/collections/collections.controller.ts
  79. 15 2
      server/src/collections/collections.service.ts
  80. 11 6
      server/src/collections/dto.ts
  81. 1 1
      server/src/swagger.ts
  82. 52 1
      server/src/utils/Helper.ts
  83. 262 185
      server/yarn.lock

+ 1 - 0
client/.env.development

@@ -0,0 +1 @@
+VITE_BASE_URL=http://localhost:3000/

+ 1 - 0
client/.env.production

@@ -0,0 +1 @@
+VITE_BASE_URL=https://API_BASE_URL/

+ 0 - 24
client/README.md

@@ -60,27 +60,3 @@ We use class getter to define our client fields like \_field, because of our ser
 Like utils / consts / utils / hooks , we dont want put all functions or data in one file like index.ts because of maintainability.
 Like utils / consts / utils / hooks , we dont want put all functions or data in one file like index.ts because of maintainability.
 
 
 So when we need to create new file , treat the file like Class then name it.
 So when we need to create new file , treat the file like Class then name it.
-
-### Plugins Folder
-
-You can deploy any plugin developed by [template](https://github.com/zilliztech/insight-plugin-template). All client plugins should be placed at `src/plugins` folder. We have transferred `System View` and `Vector Search` to plugins. For more plugins development details please refer to [template repo](https://github.com/zilliztech/insight-plugin-template).
-
-### Alias Map
-
-As `react-app-rewire-alias` in `config-overrides.js`, we can use alias import. `insight_src/` is equal to `client/src` .
-
-### Icon
-
-We put all icons in components/icons file. Normally we use material icon.
-
-If we use custom svg, like: import { ReactComponent as CustomIcon } from xxx/xxx.svg'.
-
-It's react component because of svgr/webpack in webpack config.
-
-### Build
-
-We use react-app-rewired to change webpack config.
-
-If we want to change the webpack config, we can edit config-overrides.js file.
-
-Our build path is `./build`. And we use Attu server to host our client site.

+ 0 - 7
client/config-overrides.js

@@ -1,7 +0,0 @@
-// const path = require('path');
-const { configPaths } = require('react-app-rewire-alias');
-const { aliasDangerous } = require('react-app-rewire-alias/lib/aliasDangerous');
-
-const aliasMap = configPaths('./tsconfig.paths.json');
-
-module.exports = aliasDangerous(aliasMap);

+ 29 - 0
client/index.html

@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8" />
+  <link rel="icon" href="attu.png" />
+  <meta name="description" content="Attu, best milvus management tool" />
+  <meta name="viewport" content="width=device-width, initial-scale=1" />
+  <meta name="theme-color" content="#000000" />
+  <script src="./env-config.js"></script>
+  <script type="module" src="/src/index.tsx"></script>
+  <title>Attu</title>
+</head>
+
+<body>
+  <noscript>You need to enable JavaScript to run this app.</noscript>
+  <div id="root"></div>
+  <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+</body>
+
+</html>

+ 32 - 37
client/package.json

@@ -9,59 +9,61 @@
     "@date-io/dayjs": "1.x",
     "@date-io/dayjs": "1.x",
     "@loadable/component": "^5.15.0",
     "@loadable/component": "^5.15.0",
     "@material-ui/core": "4.11.4",
     "@material-ui/core": "4.11.4",
-    "@material-ui/icons": "^4.11.2",
+    "@material-ui/icons": "^4.11.3",
     "@material-ui/lab": "4.0.0-alpha.58",
     "@material-ui/lab": "4.0.0-alpha.58",
     "@material-ui/pickers": "^3.3.10",
     "@material-ui/pickers": "^3.3.10",
     "@mui/x-data-grid": "^4.0.0",
     "@mui/x-data-grid": "^4.0.0",
-    "@testing-library/jest-dom": "^5.11.4",
-    "@testing-library/react": "^11.1.0",
-    "@testing-library/user-event": "^12.1.10",
-    "@types/jest": "^26.0.15",
-    "@types/node": "^12.0.0",
-    "@types/papaparse": "^5.2.6",
-    "@types/react": "^17.0.0",
-    "@types/react-dom": "^17.0.0",
-    "@types/react-highlight-words": "^0.16.2",
-    "@types/react-router-dom": "^5.1.7",
-    "@types/react-syntax-highlighter": "^13.5.2",
     "axios": "^0.21.3",
     "axios": "^0.21.3",
     "dayjs": "^1.10.5",
     "dayjs": "^1.10.5",
     "file-saver": "^2.0.5",
     "file-saver": "^2.0.5",
     "i18next": "^20.3.1",
     "i18next": "^20.3.1",
     "papaparse": "^5.3.1",
     "papaparse": "^5.3.1",
     "react": "^17.0.2",
     "react": "^17.0.2",
-    "react-app-rewire-alias": "^1.1.4",
-    "react-app-rewired": "^2.1.8",
     "react-dom": "^17.0.2",
     "react-dom": "^17.0.2",
     "react-highlight-words": "^0.17.0",
     "react-highlight-words": "^0.17.0",
-    "react-i18next": "11.10.0",
+    "react-i18next": "^12.0.0",
     "react-router-dom": "^5.2.0",
     "react-router-dom": "^5.2.0",
-    "react-scripts": "4.0.3",
     "react-syntax-highlighter": "^15.4.4",
     "react-syntax-highlighter": "^15.4.4",
-    "set-value": "^4.1.0",
     "socket.io-client": "^4.1.3",
     "socket.io-client": "^4.1.3",
     "typescript": "^4.1.2",
     "typescript": "^4.1.2",
+    "vite": "^3.2.2",
+    "vite-plugin-svgr": "^0.3.0",
     "web-vitals": "^1.0.1"
     "web-vitals": "^1.0.1"
   },
   },
-  "jest": {
-    "coverageDirectory": "<rootDir>/coverage/"
+  "devDependencies": {
+    "@vitejs/plugin-react": "^2.2.0",
+    "@vitejs/plugin-react-refresh": "^1.3.6",
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "12.1.2",
+    "@testing-library/react-hooks": "^7.0.1",
+    "@testing-library/user-event": "^12.1.10",
+    "@types/file-saver": "^2.0.4",
+    "@types/loadable__component": "^5.13.4",
+    "@types/node": "^12.0.0",
+    "@types/papaparse": "^5.2.6",
+    "@types/react": "^17.0.0",
+    "@types/react-dom": "^17.0.0",
+    "@types/react-highlight-words": "^0.16.2",
+    "@types/react-router-dom": "^5.1.7",
+    "@types/react-syntax-highlighter": "^13.5.2",
+    "@types/webpack-env": "^1.16.3",
+    "@vitest/coverage-c8": "^0.25.0",
+    "jsdom": "^20.0.2",
+    "prettier": "2.3.2",
+    "vitest": "^0.24.5"
   },
   },
   "homepage": "./",
   "homepage": "./",
   "scripts": {
   "scripts": {
-    "start": "react-app-rewired start -FAST_REFRESH=true",
-    "start:plugin": "REACT_APP_PLUGIN_DEV=true react-app-rewired start -FAST_REFRESH=true",
-    "build": "react-app-rewired build",
-    "test": "react-app-rewired test",
-    "test:watch": "react-app-rewired test --watch",
-    "test:cov": "react-app-rewired test --watchAll=false --coverage",
-    "test:report": "react-app-rewired test --watchAll=false --coverage --coverageReporters='text-summary'",
-    "eject": "react-app-rewired eject",
+    "start": "vite",
+    "build": "vite build",
+    "test": "vitest",
+    "test:coverage": "vitest --coverage",
+    "test:watch": "vitest --watchAll",
     "format": "prettier --write '**/*.{ts,js,tsx,jsx,css}'"
     "format": "prettier --write '**/*.{ts,js,tsx,jsx,css}'"
   },
   },
   "eslintConfig": {
   "eslintConfig": {
     "extends": [
     "extends": [
-      "react-app",
-      "react-app/jest"
+      "react-app"
     ]
     ]
   },
   },
   "browserslist": {
   "browserslist": {
@@ -75,12 +77,5 @@
       "last 1 firefox version",
       "last 1 firefox version",
       "last 1 safari version"
       "last 1 safari version"
     ]
     ]
-  },
-  "devDependencies": {
-    "@testing-library/react-hooks": "^7.0.1",
-    "@types/file-saver": "^2.0.4",
-    "@types/loadable__component": "^5.13.4",
-    "@types/webpack-env": "^1.16.3",
-    "prettier": "2.3.2"
   }
   }
-}
+}

BIN
client/public/favicon.ico


+ 0 - 47
client/public/index.html

@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="utf-8" />
-  <link rel="icon" href="%PUBLIC_URL%/attu.png" />
-  <meta name="viewport" content="width=device-width, initial-scale=1" />
-  <meta name="theme-color" content="#000000" />
-  <meta name="description" content="Web site created using create-react-app" />
-  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
-  <!--
-      manifest.json provides metadata used when your web app is installed on a
-      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-    -->
-  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet" />
-  <link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400&display=swap" rel="stylesheet" />
-  <script src="%PUBLIC_URL%/env-config.js"></script>
-
-  <!--
-      Notice the use of %PUBLIC_URL% in the tags above.
-      It will be replaced with the URL of the `public` folder during the build.
-      Only files inside the `public` folder can be referenced from the HTML.
-
-      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
-      work correctly both with client-side routing and a non-root public URL.
-      Learn how to configure a non-root public URL by running `npm run build`.
-    -->
-  <title>Attu</title>
-</head>
-
-<body>
-  <noscript>You need to enable JavaScript to run this app.</noscript>
-  <div id="root"></div>
-  <!--
-      This HTML file is a template.
-      If you open it directly in the browser, you will see an empty page.
-
-      You can add webfonts, meta tags, or analytics to this file.
-      The build step will place the bundled scripts into the <body> tag.
-
-      To begin the development, run `npm start` or `yarn start`.
-      To create a production bundle, use `npm run build` or `yarn build`.
-    -->
-</body>
-
-</html>

BIN
client/public/logo.png


BIN
client/public/logo192.png


BIN
client/public/logo512.png


+ 0 - 25
client/public/manifest.json

@@ -1,25 +0,0 @@
-{
-  "short_name": "React App",
-  "name": "Create React App Sample",
-  "icons": [
-    {
-      "src": "favicon.ico",
-      "sizes": "64x64 32x32 24x24 16x16",
-      "type": "image/x-icon"
-    },
-    {
-      "src": "logo192.png",
-      "type": "image/png",
-      "sizes": "192x192"
-    },
-    {
-      "src": "logo512.png",
-      "type": "image/png",
-      "sizes": "512x512"
-    }
-  ],
-  "start_url": ".",
-  "display": "standalone",
-  "theme_color": "#000000",
-  "background_color": "#ffffff"
-}

+ 3 - 3
client/src/assets/icons/attu.svg

@@ -1,5 +1,5 @@
 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M15.9719 2.06803C16.0242 1.97732 16.1551 1.97732 16.2073 2.06803L19.4178 7.64077L9.56067 24.8547C6.78737 24.9881 4.22773 25.4339 2.25155 25.8838L15.9719 2.06803Z" fill="white"/>
-<path d="M26.3931 27.7963C29.8222 27.7963 33.4739 26.151 35.7404 24.9101L24.5049 5.28918C24.4527 5.19806 24.3213 5.19806 24.2691 5.28918L13.0455 24.8894C15.7146 25.1104 17.7436 25.785 19.6798 26.4288C21.7915 27.1309 23.7927 27.7963 26.3931 27.7963Z" fill="white"/>
-<path d="M19.2985 29.0903C21.1596 29.7053 22.9289 30.2899 24.4937 30.5205C28.3671 31.0914 33.7215 29.3788 36 27.2096C34.2911 30.5205 29.962 34.0598 24.4937 34.0598C20.0719 34.0598 16.6998 32.7612 13.2767 31.4429C12.4664 31.1309 11.6533 30.8178 10.8228 30.5205C8.10961 29.5494 3.4184 29.6521 1.02534 29.7045C0.604887 29.7137 0.255375 29.7213 0 29.7213C2.05063 28.1229 6.1519 27.2096 10.8228 27.2096C13.6067 27.2096 16.5526 28.183 19.2985 29.0903Z" fill="white"/>
+<path d="M15.9719 2.06803C16.0242 1.97732 16.1551 1.97732 16.2073 2.06803L19.4178 7.64077L9.56067 24.8547C6.78737 24.9881 4.22773 25.4339 2.25155 25.8838L15.9719 2.06803Z" fill="#18D4E0"/>
+<path d="M26.3931 27.7963C29.8222 27.7963 33.4739 26.151 35.7404 24.9101L24.5049 5.28918C24.4527 5.19806 24.3213 5.19806 24.2691 5.28918L13.0455 24.8894C15.7146 25.1104 17.7436 25.785 19.6798 26.4288C21.7915 27.1309 23.7927 27.7963 26.3931 27.7963Z" fill="#18D4E0"/>
+<path d="M19.2985 29.0903C21.1596 29.7053 22.9289 30.2899 24.4937 30.5205C28.3671 31.0914 33.7215 29.3788 36 27.2096C34.2911 30.5205 29.962 34.0598 24.4937 34.0598C20.0719 34.0598 16.6998 32.7612 13.2767 31.4429C12.4664 31.1309 11.6533 30.8178 10.8228 30.5205C8.10961 29.5494 3.4184 29.6521 1.02534 29.7045C0.604887 29.7137 0.255375 29.7213 0 29.7213C2.05063 28.1229 6.1519 27.2096 10.8228 27.2096C13.6067 27.2096 16.5526 28.183 19.2985 29.0903Z" fill="#18D4E0"/>
 </svg>
 </svg>

+ 7 - 10
client/src/components/__test__/cards/EmptyCard.spec.tsx

@@ -1,20 +1,17 @@
-import { render, screen, RenderResult } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
 import EmptyCard from '../../cards/EmptyCard';
 import EmptyCard from '../../cards/EmptyCard';
 import provideTheme from '../utils/provideTheme';
 import provideTheme from '../utils/provideTheme';
 
 
-let body: RenderResult;
-
 describe('test empty card component', () => {
 describe('test empty card component', () => {
-  beforeEach(() => {
-    body = render(
+  it('renders default state', () => {
+    const emptyText = Math.random().toString();
+    render(
       provideTheme(
       provideTheme(
-        <EmptyCard icon={<span className="icon">icon</span>} text="empty" />
+        <EmptyCard icon={<span className="icon">icon</span>} text={emptyText} />
       )
       )
     );
     );
-  });
 
 
-  it('renders default state', () => {
-    expect(screen.getByText('icon')).toHaveClass('icon');
-    expect(screen.getByText('empty')).toBeInTheDocument();
+    expect(screen.queryByText('icon')!.className).toEqual('icon');
+    expect(screen.queryByText(emptyText)).not.toBeNull();
   });
   });
 });
 });

+ 5 - 33
client/src/components/__test__/customButton/CustomButton.spec.tsx

@@ -1,39 +1,11 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
 import CustomButton from '../../customButton/CustomButton';
 import CustomButton from '../../customButton/CustomButton';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/Button', () => {
-  return props => {
-    const { variant, children } = props;
-    return (
-      <>
-        <div className="variant">{variant}</div>
-        <button className="button">{children}</button>;
-      </>
-    );
-  };
-});
+import { render } from '@testing-library/react';
 
 
 describe('Test CustomButton', () => {
 describe('Test CustomButton', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   test('test button props', () => {
   test('test button props', () => {
-    act(() => {
-      render(<CustomButton variant="contained">test</CustomButton>, container);
-    });
-
-    expect(container.querySelector('.button').textContent).toBe('test');
-    expect(container.querySelector('.variant').textContent).toBe('contained');
+    const result = render(
+      <CustomButton variant="contained">test</CustomButton>
+    );
+    expect(result.getByText('test').textContent).toBe('test');
   });
   });
 });
 });

+ 41 - 53
client/src/components/__test__/customDialog/CustomDialog.spec.tsx

@@ -1,56 +1,50 @@
-import { fireEvent } from '@testing-library/react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { screen, fireEvent, render } from '@testing-library/react';
 import { DialogType } from '../../../context/Types';
 import { DialogType } from '../../../context/Types';
 import CustomDialog from '../../customDialog/CustomDialog';
 import CustomDialog from '../../customDialog/CustomDialog';
+import { vi } from 'vitest';
 
 
-let container: any = null;
-
-jest.mock('react-i18next', () => ({
+vi.mock('react-i18next', () => ({
   useTranslation: () => ({
   useTranslation: () => ({
     t: (key: any) => key,
     t: (key: any) => key,
   }),
   }),
 }));
 }));
 
 
-jest.mock('@material-ui/core/Dialog', () => {
-  return props => {
-    return <div id="dialog-wrapper">{props.children}</div>;
+vi.mock('@material-ui/core/Dialog', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-wrapper">{props.children}</div>;
+    },
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/DialogTitle', () => {
-  return props => {
-    return <div id="dialog-title">{props.children}</div>;
+vi.mock('@material-ui/core/DialogTitle', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-title">{props.children}</div>;
+    },
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/DialogContent', () => {
-  return props => {
-    return <div id="dialog-content">{props.children}</div>;
+vi.mock('@material-ui/core/DialogContent', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-content">{props.children}</div>;
+    },
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/DialogActions', () => {
-  return props => {
-    return <div id="dialog-actions">{props.children}</div>;
+vi.mock('@material-ui/core/DialogActions', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-actions">{props.children}</div>;
+    },
   };
   };
 });
 });
 
 
 describe('Test Custom Dialog', () => {
 describe('Test Custom Dialog', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test notice dialog ', () => {
   it('Test notice dialog ', () => {
-    const handleClose = jest.fn();
-    const handleConfirm = jest.fn();
+    const handleClose = vi.fn();
+    const handleConfirm = vi.fn();
 
 
     const params: DialogType = {
     const params: DialogType = {
       open: true,
       open: true,
@@ -61,28 +55,26 @@ describe('Test Custom Dialog', () => {
         component: <div>123</div>,
         component: <div>123</div>,
       },
       },
     };
     };
-    act(() => {
-      render(
-        <CustomDialog {...params} onClose={handleClose}></CustomDialog>,
-        container
-      );
-    });
-
-    expect(container.querySelector('#dialog-title').textContent).toEqual(
-      params.params.title
+
+    const res = render(
+      <CustomDialog {...params} onClose={handleClose}></CustomDialog>
     );
     );
 
 
-    expect(container.querySelector('#dialog-content').textContent).toEqual(
-      '123'
+    expect(res.getByText(params.params.title!).textContent).toEqual(
+      params.params.title
     );
     );
 
 
-    container.querySelectorAll('button').forEach(v => fireEvent.click(v));
+    expect(res.getByText('123').textContent).toEqual('123');
+
+    fireEvent.click(screen.getByText('cancel'));
+    fireEvent.click(screen.getByText('confirm'));
+
     expect(handleClose).toBeCalledTimes(1);
     expect(handleClose).toBeCalledTimes(1);
     expect(handleConfirm).toBeCalledTimes(1);
     expect(handleConfirm).toBeCalledTimes(1);
   });
   });
 
 
   it('Test Custom dialog ', () => {
   it('Test Custom dialog ', () => {
-    const handleClose = jest.fn();
+    const handleClose = vi.fn();
 
 
     const params: DialogType = {
     const params: DialogType = {
       open: true,
       open: true,
@@ -91,15 +83,11 @@ describe('Test Custom Dialog', () => {
         component: <div>custom</div>,
         component: <div>custom</div>,
       },
       },
     };
     };
-    act(() => {
-      render(
-        <CustomDialog {...params} onClose={handleClose}></CustomDialog>,
-        container
-      );
-    });
-
-    expect(container.querySelector('#dialog-wrapper').textContent).toEqual(
-      'custom'
+
+    const res = render(
+      <CustomDialog {...params} onClose={handleClose}></CustomDialog>
     );
     );
+
+    expect(res.getByText('custom').textContent).toEqual('custom');
   });
   });
 });
 });

+ 2 - 2
client/src/components/__test__/customDialog/CustomDialogTitle.spec.tsx

@@ -1,16 +1,16 @@
 import { fireEvent, render } from '@testing-library/react';
 import { fireEvent, render } from '@testing-library/react';
 import CustomDialogTitle from '../../customDialog/CustomDialogTitle';
 import CustomDialogTitle from '../../customDialog/CustomDialogTitle';
+import { vi } from 'vitest';
 
 
 describe('test custom dialog title component', () => {
 describe('test custom dialog title component', () => {
   it('renders default state', () => {
   it('renders default state', () => {
     const container = render(<CustomDialogTitle>title</CustomDialogTitle>);
     const container = render(<CustomDialogTitle>title</CustomDialogTitle>);
-
     expect(container.getByText('title')).toBeInTheDocument();
     expect(container.getByText('title')).toBeInTheDocument();
     expect(container.queryByTestId('clear-icon')).toBeNull();
     expect(container.queryByTestId('clear-icon')).toBeNull();
   });
   });
 
 
   it('checks clear event', () => {
   it('checks clear event', () => {
-    const mockClearFn = jest.fn();
+    const mockClearFn = vi.fn();
     const container = render(
     const container = render(
       <CustomDialogTitle onClose={mockClearFn}>title</CustomDialogTitle>
       <CustomDialogTitle onClose={mockClearFn}>title</CustomDialogTitle>
     );
     );

+ 3 - 2
client/src/components/__test__/customDialog/DeleteDialogTemplate.spec.tsx

@@ -3,10 +3,11 @@ import DeleteTemplate from '../../customDialog/DeleteDialogTemplate';
 import provideTheme from '../utils/provideTheme';
 import provideTheme from '../utils/provideTheme';
 import { I18nextProvider } from 'react-i18next';
 import { I18nextProvider } from 'react-i18next';
 import i18n from '../../../i18n';
 import i18n from '../../../i18n';
+import { vi } from 'vitest';
 
 
 describe('test delete dialog template component', () => {
 describe('test delete dialog template component', () => {
-  const mockDeleteFn = jest.fn();
-  const mockCancelFn = jest.fn();
+  const mockDeleteFn = vi.fn();
+  const mockCancelFn = vi.fn();
 
 
   beforeEach(() => {
   beforeEach(() => {
     render(
     render(

+ 3 - 2
client/src/components/__test__/customDialog/DialogTemplate.spec.tsx

@@ -3,10 +3,11 @@ import DialogTemplate from '../../customDialog/DialogTemplate';
 import provideTheme from '../utils/provideTheme';
 import provideTheme from '../utils/provideTheme';
 import { I18nextProvider } from 'react-i18next';
 import { I18nextProvider } from 'react-i18next';
 import i18n from '../../../i18n';
 import i18n from '../../../i18n';
+import { vi } from 'vitest';
 
 
 describe('test dialog template component', () => {
 describe('test dialog template component', () => {
-  const mockCancelFn = jest.fn();
-  const mockConfirmFn = jest.fn();
+  const mockCancelFn = vi.fn();
+  const mockConfirmFn = vi.fn();
 
 
   it('renders default state and callbacks', () => {
   it('renders default state and callbacks', () => {
     render(
     render(

+ 49 - 76
client/src/components/__test__/customInput/CustomInput.spec.tsx

@@ -1,34 +1,28 @@
-import { fireEvent } from '@testing-library/react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { fireEvent, render } from '@testing-library/react';
 import CustomInput from '../../customInput/CustomInput';
 import CustomInput from '../../customInput/CustomInput';
+import provideTheme from '../utils/provideTheme';
 import {
 import {
   IAdornmentConfig,
   IAdornmentConfig,
   IIconConfig,
   IIconConfig,
   ITextfieldConfig,
   ITextfieldConfig,
 } from '../../customInput/Types';
 } from '../../customInput/Types';
+import { vi } from 'vitest';
 
 
-let container: any = null;
-
-jest.mock('@material-ui/core/styles/makeStyles', () => {
-  return () => () => ({});
-});
-
-jest.mock('@material-ui/core/FormControl', () => {
-  return props => {
+vi.mock('@material-ui/core/FormControl', () => {
+  return (props: any) => {
     const { children } = props;
     const { children } = props;
     return <div className="form-control">{children}</div>;
     return <div className="form-control">{children}</div>;
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/InputLabel', () => {
-  return props => {
+vi.mock('@material-ui/core/InputLabel', () => {
+  return (props: any) => {
     return <div className="label">{props.children}</div>;
     return <div className="label">{props.children}</div>;
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/Input', () => {
-  return props => {
+vi.mock('@material-ui/core/Input', () => {
+  return (props: any) => {
     const { type, onBlur, endAdornment } = props;
     const { type, onBlur, endAdornment } = props;
     return (
     return (
       <>
       <>
@@ -40,8 +34,8 @@ jest.mock('@material-ui/core/Input', () => {
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/TextField', () => {
-  return props => {
+vi.mock('@material-ui/core/TextField', () => {
+  return (props: any) => {
     const { helperText, onBlur, onChange, label, className } = props;
     const { helperText, onBlur, onChange, label, className } = props;
     return (
     return (
       <div className="text-field">
       <div className="text-field">
@@ -59,27 +53,16 @@ jest.mock('@material-ui/core/TextField', () => {
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/Grid', () => {
-  return props => {
+vi.mock('@material-ui/core/Grid', () => {
+  return (props: any) => {
     const { children } = props;
     const { children } = props;
     return <div className="grid">{children}</div>;
     return <div className="grid">{children}</div>;
   };
   };
 });
 });
 
 
 describe('Test CustomInput', () => {
 describe('Test CustomInput', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   test('test text type input', () => {
   test('test text type input', () => {
-    const handleBlur = jest.fn();
+    const handleBlur = vi.fn();
 
 
     const mockTextConfig: ITextfieldConfig = {
     const mockTextConfig: ITextfieldConfig = {
       variant: 'standard',
       variant: 'standard',
@@ -89,36 +72,31 @@ describe('Test CustomInput', () => {
       onBlur: handleBlur,
       onBlur: handleBlur,
     };
     };
 
 
-    act(() => {
-      render(
-        <CustomInput
-          type="text"
-          textConfig={mockTextConfig}
-          checkValid={() => true}
-        />,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('.text-field').length).toBe(1);
-    expect(container.querySelector('.text-class').textContent).toBe(
-      'classname'
-    );
-    expect(container.querySelector('.text-label').textContent).toBe(
-      'test text'
+    const res = render(
+      <CustomInput
+        type="text"
+        textConfig={mockTextConfig}
+        checkValid={() => true}
+      />
     );
     );
 
 
-    const input = container.querySelector('.text-input');
+    expect(res.getAllByText('test text').length).toBe(1);
+    expect(res.getByText('test text').textContent).toBe('test text');
+    expect(
+      res.getByText('test text').parentElement!.classList.contains('classname')
+    ).toBeTruthy();
+
+    const input = res.getByRole('textbox');
     input.focus();
     input.focus();
     input.blur();
     input.blur();
     expect(handleBlur).toHaveBeenCalledTimes(1);
     expect(handleBlur).toHaveBeenCalledTimes(1);
   });
   });
 
 
   test('test icon type input', () => {
   test('test icon type input', () => {
-    const handleChange = jest.fn();
+    const handleChange = vi.fn();
 
 
     const mockIconConfig: IIconConfig = {
     const mockIconConfig: IIconConfig = {
-      icon: <div className="icon"></div>,
+      icon: <div className="icon" role="img"></div>,
       inputType: 'icon',
       inputType: 'icon',
       inputConfig: {
       inputConfig: {
         label: 'icon text',
         label: 'icon text',
@@ -128,28 +106,25 @@ describe('Test CustomInput', () => {
       },
       },
     };
     };
 
 
-    render(
+    const res = render(
       <CustomInput
       <CustomInput
         type="icon"
         type="icon"
         iconConfig={mockIconConfig}
         iconConfig={mockIconConfig}
         checkValid={() => true}
         checkValid={() => true}
-      />,
-      container
+      />
     );
     );
 
 
-    expect(container.querySelectorAll('.grid').length).toBe(3);
-    expect(container.querySelectorAll('.icon').length).toBe(1);
-    expect(container.querySelector('.text-label').textContent).toBe(
-      'icon text'
-    );
+    // expect(res.getAllByText('.grid').length).toBe(3);
+    expect(res.getAllByRole('img').length).toBe(1);
+    expect(res.getByText('icon text').textContent).toBe('icon text');
 
 
-    const input = container.querySelector('.text-input');
+    const input = res.getByRole('textbox');
     fireEvent.change(input, { target: { value: 'trigger change' } });
     fireEvent.change(input, { target: { value: 'trigger change' } });
     expect(handleChange).toHaveBeenCalledTimes(1);
     expect(handleChange).toHaveBeenCalledTimes(1);
   });
   });
 
 
   test('test adornmentConfig type input', () => {
   test('test adornmentConfig type input', () => {
-    const mockBlurFunc = jest.fn();
+    const mockBlurFunc = vi.fn();
 
 
     const mockAdornmentConfig: IAdornmentConfig = {
     const mockAdornmentConfig: IAdornmentConfig = {
       label: 'adornment',
       label: 'adornment',
@@ -159,20 +134,20 @@ describe('Test CustomInput', () => {
       onInputBlur: mockBlurFunc,
       onInputBlur: mockBlurFunc,
     };
     };
 
 
-    render(
-      <CustomInput
-        type="adornment"
-        adornmentConfig={mockAdornmentConfig}
-        checkValid={() => true}
-      />,
-      container
+    const res = render(
+      provideTheme(
+        <CustomInput
+          type="adornment"
+          adornmentConfig={mockAdornmentConfig}
+          checkValid={() => true}
+        />
+      )
     );
     );
 
 
-    expect(container.querySelector('.label').textContent).toBe('adornment');
-    expect(container.querySelector('.type').textContent).toBe('text');
-    expect(container.querySelectorAll('.adornment-icon').length).toBe(1);
+    expect(res.getByText('adornment').textContent).toBe('adornment');
+    expect(res.getAllByRole('icon-button').length).toBe(1);
 
 
-    const input = container.querySelector('.input');
+    const input = res.getByRole('textbox');
     input.focus();
     input.focus();
     input.blur();
     input.blur();
     expect(mockBlurFunc).toHaveBeenCalledTimes(1);
     expect(mockBlurFunc).toHaveBeenCalledTimes(1);
@@ -185,10 +160,8 @@ describe('Test CustomInput', () => {
       variant: 'standard',
       variant: 'standard',
     };
     };
 
 
-    act(() => {
-      render(<CustomInput textConfig={mockTextConfig} />, container);
-    });
+    const res = render(<CustomInput textConfig={mockTextConfig} />);
 
 
-    expect(container.querySelector('.text-label').textContent).toBe('default');
+    expect(res.getByText('default').textContent).toBe('default');
   });
   });
 });
 });

+ 10 - 10
client/src/components/__test__/customInput/SearchInput.spec.tsx

@@ -2,11 +2,11 @@ import { fireEvent, render } from '@testing-library/react';
 import SearchInput from '../../customInput/SearchInput';
 import SearchInput from '../../customInput/SearchInput';
 import { I18nextProvider } from 'react-i18next';
 import { I18nextProvider } from 'react-i18next';
 import i18n from '../../../i18n';
 import i18n from '../../../i18n';
-import { Router } from 'react-router-dom';
+import { vi } from 'vitest';
 
 
-const mockHistoryPushFn = jest.fn();
+const mockHistoryPushFn = vi.fn();
 
 
-jest.mock('react-router-dom', () => ({
+vi.mock('react-router-dom', () => ({
   useHistory: () => ({
   useHistory: () => ({
     push: mockHistoryPushFn,
     push: mockHistoryPushFn,
     location: {
     location: {
@@ -15,14 +15,14 @@ jest.mock('react-router-dom', () => ({
   }),
   }),
 }));
 }));
 
 
-// clear the influence of jest.useFakeTimers
+// clear the influence of vi.useFakeTimers
 afterEach(() => {
 afterEach(() => {
-  jest.useRealTimers();
+  vi.useRealTimers();
 });
 });
 
 
 describe('test search input component', () => {
 describe('test search input component', () => {
   it('renders default state', () => {
   it('renders default state', () => {
-    const mockSearchFn = jest.fn();
+    const mockSearchFn = vi.fn();
     const container = render(
     const container = render(
       <I18nextProvider i18n={i18n}>
       <I18nextProvider i18n={i18n}>
         <SearchInput searchText="search text" onSearch={mockSearchFn} />
         <SearchInput searchText="search text" onSearch={mockSearchFn} />
@@ -35,7 +35,7 @@ describe('test search input component', () => {
   });
   });
 
 
   it('checks input value change event', () => {
   it('checks input value change event', () => {
-    const mockSearchFn = jest.fn();
+    const mockSearchFn = vi.fn();
     const container = render(
     const container = render(
       <I18nextProvider i18n={i18n}>
       <I18nextProvider i18n={i18n}>
         <SearchInput onSearch={mockSearchFn} />
         <SearchInput onSearch={mockSearchFn} />
@@ -55,9 +55,9 @@ describe('test search input component', () => {
   });
   });
 
 
   it('checks location change according to search value', () => {
   it('checks location change according to search value', () => {
-    const mockSearchFn = jest.fn();
+    const mockSearchFn = vi.fn();
     // mock setTimeout
     // mock setTimeout
-    jest.useFakeTimers();
+    vi.useFakeTimers();
 
 
     const container = render(
     const container = render(
       <I18nextProvider i18n={i18n}>
       <I18nextProvider i18n={i18n}>
@@ -69,7 +69,7 @@ describe('test search input component', () => {
     fireEvent.change(input, { target: { value: 'route' } });
     fireEvent.change(input, { target: { value: 'route' } });
     expect(mockHistoryPushFn).not.toBeCalled();
     expect(mockHistoryPushFn).not.toBeCalled();
     // fast-forward until all timers have been executed
     // fast-forward until all timers have been executed
-    jest.runAllTimers();
+    vi.runAllTimers();
     expect(mockHistoryPushFn).toBeCalled();
     expect(mockHistoryPushFn).toBeCalled();
     expect(mockHistoryPushFn).toBeCalledWith({ search: 'search=route' });
     expect(mockHistoryPushFn).toBeCalledWith({ search: 'search=route' });
   });
   });

+ 0 - 121
client/src/components/__test__/customSelector/CustomGroupedSelect.spec.tsx

@@ -1,121 +0,0 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
-import { fireEvent } from '@testing-library/react';
-import CustomGroupedSelect from '../../customSelector/CustomGroupedSelect';
-import { GroupOption } from '../../customSelector/Types';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/FormControl', () => {
-  return props => {
-    const { children } = props;
-    return <div className="form-control">{children}</div>;
-  };
-});
-
-jest.mock('@material-ui/core/Select', () => {
-  return props => {
-    const { children, onChange } = props;
-    return (
-      <select className="group-select" onChange={onChange}>
-        {children}
-      </select>
-    );
-  };
-});
-
-jest.mock('@material-ui/core/ListSubheader', () => {
-  return props => {
-    const { children } = props;
-    return <option className="group-header">{children}</option>;
-  };
-});
-
-jest.mock('@material-ui/core/MenuItem', () => {
-  return props => {
-    const { children, value } = props;
-    return (
-      <option className="group-item" value={value}>
-        {children}
-      </option>
-    );
-  };
-});
-
-describe('Test CustomGroupedSelect', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
-  test('test group select props', () => {
-    const mockOptions: GroupOption[] = [
-      {
-        label: 'Group 1',
-        children: [
-          {
-            label: 'group text 1',
-            value: 'group text 1',
-          },
-          {
-            label: 'group text 2',
-            value: 'group text 2',
-          },
-          {
-            label: 'group text 3',
-            value: 'group text 3',
-          },
-        ],
-      },
-      {
-        label: 'Group 2',
-        children: [
-          {
-            label: 'group text 11',
-            value: 'group text 11',
-          },
-          {
-            label: 'group text 22',
-            value: 'group text 22',
-          },
-          {
-            label: 'group text 33',
-            value: 'group text 33',
-          },
-        ],
-      },
-    ];
-    const handleChange = jest.fn();
-
-    act(() => {
-      render(
-        <CustomGroupedSelect
-          options={mockOptions}
-          value={''}
-          onChange={handleChange}
-        />,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('.form-control').length).toBe(1);
-    expect(container.querySelectorAll('.group-header').length).toBe(2);
-    expect(container.querySelectorAll('.group-item').length).toBe(6);
-
-    const select = container.querySelector('.group-select');
-
-    fireEvent.change(select, {
-      target: {
-        value: 'group text 2',
-      },
-    });
-    expect(handleChange).toHaveBeenCalledTimes(1);
-    expect(select.value).toBe('group text 2');
-  });
-});

+ 6 - 30
client/src/components/__test__/customSnackBar/CustomSnackBar.spec.tsx

@@ -1,44 +1,20 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
 import { SnackBarType } from '../../../context/Types';
 import { SnackBarType } from '../../../context/Types';
 import CustomSnackBar from '../../customSnackBar/CustomSnackBar';
 import CustomSnackBar from '../../customSnackBar/CustomSnackBar';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/Snackbar', () => {
-  return props => {
-    return <div id="snackbar">{props.children}</div>;
-  };
-});
+import { vi } from 'vitest';
 
 
 describe('Test Custom Dialog', () => {
 describe('Test Custom Dialog', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test Custom dialog ', () => {
   it('Test Custom dialog ', () => {
     const params: SnackBarType = {
     const params: SnackBarType = {
-      open: false,
+      open: true,
       type: 'success',
       type: 'success',
       message: 'test',
       message: 'test',
       vertical: 'top',
       vertical: 'top',
       horizontal: 'center',
       horizontal: 'center',
       autoHideDuration: 2000,
       autoHideDuration: 2000,
     };
     };
-    const handleClose = jest.fn();
-    act(() => {
-      render(<CustomSnackBar {...params} onClose={handleClose} />, container);
-    });
-
-    expect(container.querySelector('#snackbar').textContent).toEqual(
-      params.message
-    );
+    const handleClose = vi.fn();
+    render(<CustomSnackBar {...params} onClose={handleClose} />);
+    expect(screen.queryByText('test')?.textContent).toEqual(params.message);
   });
   });
 });
 });

+ 12 - 9
client/src/components/__test__/customToolTip/CustomToolTip.spec.tsx

@@ -1,18 +1,21 @@
 import { render, unmountComponentAtNode } from 'react-dom';
 import { render, unmountComponentAtNode } from 'react-dom';
 import { act } from 'react-dom/test-utils';
 import { act } from 'react-dom/test-utils';
 import CustomToolTip from '../../customToolTip/CustomToolTip';
 import CustomToolTip from '../../customToolTip/CustomToolTip';
+import { vi } from 'vitest';
 
 
 let container: any = null;
 let container: any = null;
 
 
-jest.mock('@material-ui/core/Tooltip', () => {
-  return props => {
-    return (
-      <div id="tooltip">
-        <div id="title">{props.title}</div>
-        <div id="placement">{props.placement}</div>
-        {props.children}
-      </div>
-    );
+vi.mock('@material-ui/core/Tooltip', () => {
+  return {
+    default: (props: any) => {
+      return (
+        <div id="tooltip">
+          <div id="title">{props.title}</div>
+          <div id="placement">{props.placement}</div>
+          {props.children}
+        </div>
+      );
+    },
   };
   };
 });
 });
 
 

+ 40 - 75
client/src/components/__test__/grid/Grid.spec.tsx

@@ -1,11 +1,9 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render } from '@testing-library/react';
 import AttuGrid from '../../grid/Grid';
 import AttuGrid from '../../grid/Grid';
 import { ToolBarConfig } from '../../grid/Types';
 import { ToolBarConfig } from '../../grid/Types';
+import { vi } from 'vitest';
 
 
-let container: any = null;
-
-jest.mock('react-i18next', () => {
+vi.mock('react-i18next', () => {
   return {
   return {
     useTranslation: () => ({
     useTranslation: () => ({
       t: () => ({
       t: () => ({
@@ -15,23 +13,11 @@ jest.mock('react-i18next', () => {
   };
   };
 });
 });
 
 
-jest.mock('../../grid/Table', () => {
-  return () => {
-    return <div id="table">{ }</div>;
-  };
-});
-
-jest.mock('../../grid/ToolBar', () => {
-  return () => {
-    return <div id="tool-bar"></div>;
-  };
-});
-
-jest.mock('react-router-dom', () => {
+vi.mock('react-router-dom', () => {
   return {
   return {
     useHistory: () => {
     useHistory: () => {
       return {
       return {
-        listen: () => () => { },
+        listen: () => () => {},
         location: {
         location: {
           name: '',
           name: '',
         },
         },
@@ -41,54 +27,36 @@ jest.mock('react-router-dom', () => {
 });
 });
 
 
 describe('Test Grid index', () => {
 describe('Test Grid index', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Has Table Data', () => {
   it('Has Table Data', () => {
-    act(() => {
-      render(
-        <AttuGrid
-          primaryKey="id"
-          rows={[{}]}
-          colDefinitions={[]}
-          rowCount={10}
-          toolbarConfigs={[]}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <AttuGrid
+        primaryKey="id"
+        rows={[{}]}
+        colDefinitions={[]}
+        rowCount={10}
+        toolbarConfigs={[]}
+      />
+    );
 
 
-    expect(container.querySelectorAll('#table').length).toEqual(1);
+    expect(res.getAllByRole('table').length).toEqual(1);
   });
   });
 
 
   it('Test title', () => {
   it('Test title', () => {
     const title = ['collections', 'vectors'];
     const title = ['collections', 'vectors'];
-    act(() => {
-      render(
-        <AttuGrid
-          primaryKey="id"
-          rows={[]}
-          colDefinitions={[]}
-          rowCount={0}
-          toolbarConfigs={[]}
-          title={title}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <AttuGrid
+        primaryKey="id"
+        rows={[]}
+        colDefinitions={[]}
+        rowCount={0}
+        toolbarConfigs={[]}
+        title={title}
+      />
+    );
 
 
-    const titleNodes = container.querySelectorAll('h6');
-    expect(titleNodes.length).toEqual(title.length);
-    expect(titleNodes[0].textContent).toEqual(title[0]);
-    expect(titleNodes[1].textContent).toEqual(title[1]);
+    const breadCrum = res.getAllByRole('breadcrumb');
+    expect(breadCrum.length).toEqual(1);
+    expect(breadCrum[0].textContent).toEqual(`collections›vectors`);
   });
   });
 
 
   it('Test Toolbar ', () => {
   it('Test Toolbar ', () => {
@@ -96,23 +64,20 @@ describe('Test Grid index', () => {
       {
       {
         label: 'collection',
         label: 'collection',
         icon: 'search',
         icon: 'search',
-        onClick: () => { },
-        onSearch: () => { },
+        onClick: () => {},
+        onSearch: () => {},
       },
       },
     ];
     ];
-    act(() => {
-      render(
-        <AttuGrid
-          primaryKey="id"
-          rows={[]}
-          colDefinitions={[]}
-          rowCount={0}
-          toolbarConfigs={ToolbarConfig}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <AttuGrid
+        primaryKey="id"
+        rows={[]}
+        colDefinitions={[]}
+        rowCount={0}
+        toolbarConfigs={ToolbarConfig}
+      />
+    );
 
 
-    expect(container.querySelectorAll('#tool-bar').length).toEqual(1);
+    expect(res.getAllByRole('toolbar').length).toEqual(1);
   });
   });
 });
 });

+ 3 - 2
client/src/components/__test__/grid/IconBtnCell.spec.tsx

@@ -2,6 +2,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
 import { act } from 'react-dom/test-utils';
 import { act } from 'react-dom/test-utils';
 import ActionBar from '../../grid/ActionBar';
 import ActionBar from '../../grid/ActionBar';
 import { fireEvent } from '@testing-library/react';
 import { fireEvent } from '@testing-library/react';
+import { vi } from 'vitest';
 
 
 let container: any = null;
 let container: any = null;
 
 
@@ -26,8 +27,8 @@ describe('Test Table Head', () => {
   });
   });
 
 
   it('Test Delete Icon Button', () => {
   it('Test Delete Icon Button', () => {
-    const deleteSpy = jest.fn();
-    const showDialogSpy = jest.fn();
+    const deleteSpy = vi.fn();
+    const showDialogSpy = vi.fn();
 
 
     act(() => {
     act(() => {
       render(
       render(

+ 9 - 0
client/src/components/__test__/grid/LoadingTable.spec.tsx

@@ -0,0 +1,9 @@
+import { render } from '@testing-library/react';
+import LoadingTable from '../../grid/LoadingTable';
+
+describe('Test Table', () => {
+  it('Test Table Loading status', () => {
+    const res = render(<LoadingTable count={2} />);
+    expect(res.getAllByRole('skeleton').length).toBe(2);
+  });
+});

+ 43 - 121
client/src/components/__test__/grid/Table.spec.tsx

@@ -1,9 +1,7 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen, fireEvent } from '@testing-library/react';
 import Table from '../../grid/Table';
 import Table from '../../grid/Table';
 import { ColDefinitionsType } from '../../grid/Types';
 import { ColDefinitionsType } from '../../grid/Types';
-
-let container: any = null;
+import { vi } from 'vitest';
 
 
 const colDefinitions: ColDefinitionsType[] = [
 const colDefinitions: ColDefinitionsType[] = [
   {
   {
@@ -31,133 +29,57 @@ const colDefinitions: ColDefinitionsType[] = [
   },
   },
 ];
 ];
 
 
-jest.mock('@material-ui/core/styles/makeStyles', () => {
-  return () => () => ({});
-});
-
-jest.mock('../../grid/LoadingTable.tsx', () => {
-  return () => {
-    return <div className="loading"></div>;
-  };
-});
-
 describe('Test Table', () => {
 describe('Test Table', () => {
-  let data: any[] = [];
-  let onSelected: any;
-  let isSelected: any;
-  let onSelectedAll: any;
-
-  beforeEach(() => {
-    data = [
-      {
-        id: 1,
-        name: 'czz',
-      },
-    ];
-    onSelected = jest.fn();
-    isSelected = jest.fn().mockImplementation(() => true);
-    onSelectedAll = jest.fn();
-
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
+  let data: any[] = [
+    { id: 1, name: 'foo' },
+    { id: 2, name: 'bar' },
+    { id: 3, name: 'dede' },
+  ];
+  let onSelected: any = vi.fn();
+  let isSelected: any = vi.fn();
+  let onSelectedAll: any = vi.fn();
 
 
   it('Test Basic Table', () => {
   it('Test Basic Table', () => {
-    act(() => {
-      render(
-        <Table
-          selected={[]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-        ></Table>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('input').length).toEqual(2); // check box
-    expect(container.querySelector('tr').children.length).toEqual(
-      colDefinitions.length + 1
+    const res = render(
+      <Table
+        editHeads={[]}
+        selected={[]}
+        onSelected={onSelected}
+        isSelected={isSelected}
+        onSelectedAll={onSelectedAll}
+        rows={data}
+        primaryKey="id"
+        colDefinitions={colDefinitions}
+      ></Table>
     );
     );
-    expect(container.querySelectorAll('th')[1].textContent).toEqual(
-      colDefinitions[0].label
+    expect(res.getAllByRole('checkbox').length).toEqual(4); // check box
+    expect(res.getAllByRole('row').length).toEqual(colDefinitions.length + 1);
+    expect(res.getAllByRole('cell').length).toEqual(
+      (colDefinitions.length + 1) * (data.length + 1)
     );
     );
-    expect(container.querySelectorAll('th')[2].textContent).toEqual(
+    expect(res.getAllByRole('button').length).toEqual(3);
+    expect(res.getAllByRole('cell')[2].textContent).toEqual(
       colDefinitions[1].label
       colDefinitions[1].label
     );
     );
-
-    expect(container.querySelectorAll('[aria-label="delete"]').length).toEqual(
-      1
-    );
   });
   });
 
 
   it('Test Selected function', () => {
   it('Test Selected function', () => {
-    act(() => {
-      render(
-        <Table
-          selected={[1]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-        ></Table>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('input')[1].checked).toBeTruthy();
-    expect(container.querySelectorAll('input')[0].checked).toBeTruthy();
-
-    isSelected = jest.fn().mockImplementation(() => false);
-    act(() => {
-      render(
-        <Table
-          selected={[]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-        ></Table>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('input')[1].checked).toBeFalsy();
-    expect(container.querySelectorAll('input')[0].checked).toBeFalsy();
-
-    expect(isSelected).toHaveBeenCalledTimes(1);
-  });
-
-  it('Test Table Loading status', () => {
-    act(() => {
-      render(
-        <Table
-          selected={[]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-          isLoading={true}
-        ></Table>,
-        container
-      );
-    });
+    const res: any = render(
+      <Table
+        editHeads={[]}
+        selected={[1]}
+        onSelected={onSelected}
+        isSelected={isSelected}
+        onSelectedAll={onSelectedAll}
+        rows={data}
+        primaryKey="id"
+        colDefinitions={colDefinitions}
+      ></Table>
+    );
 
 
-    expect(container.querySelectorAll('.loading').length).toBe(1);
+    const cbx = res.getAllByRole('checkbox')[1];
+    fireEvent.click(cbx);
+    expect(cbx.checked).toBeTruthy();
+    expect(onSelected).toBeCalledTimes(1);
   });
   });
 });
 });

+ 49 - 87
client/src/components/__test__/grid/TableHead.spec.tsx

@@ -1,87 +1,50 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
 import TableHead from '../../grid/TableHead';
 import TableHead from '../../grid/TableHead';
-import { fireEvent } from '@testing-library/react';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/TableHead', () => {
-  return (props: any) => {
-    return <div id="table-head">{props.children}</div>;
-  };
-});
-jest.mock('@material-ui/core/TableRow', () => {
-  return (props: any) => {
-    return <div id="table-row">{props.children}</div>;
-  };
-});
-
-jest.mock('@material-ui/core/TableCell', () => {
-  return (props: any) => {
-    return <div className="table-cell">{props.children}</div>;
-  };
-});
+import { fireEvent, render } from '@testing-library/react';
+import { vi } from 'vitest';
 
 
 describe('Test Table Head', () => {
 describe('Test Table Head', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
-  it('Test no checkbox', () => {
-    act(() => {
-      render(
-        <TableHead
-          colDefinitions={[]}
-          numSelected={0}
-          order={'desc'}
-          orderBy={'id'}
-          onSelectAllClick={() => {}}
-          onRequestSort={() => {}}
-          rowCount={0}
-          openCheckBox={false}
-        />,
-        container
-      );
-    });
-    expect(container.querySelectorAll('.table-cell').length).toEqual(0);
-  });
+  // it('Test no checkbox', () => {
+  //   const res = render(
+  //     <TableHead
+  //       colDefinitions={[]}
+  //       numSelected={0}
+  //       order={'desc'}
+  //       orderBy={'id'}
+  //       onSelectAllClick={() => {}}
+  //       handleSort={() => {}}
+  //       rowCount={0}
+  //       openCheckBox={false}
+  //     />
+  //   );
+  //   expect(res.getAllByText('.table-cell').length).toEqual(0);
+  // });
 
 
   it('Test checkbox open', () => {
   it('Test checkbox open', () => {
-    const selectAllSpy = jest.fn();
-    act(() => {
-      render(
+    const selectAllSpy = vi.fn();
+    const res = render(
+      <div>
         <TableHead
         <TableHead
           colDefinitions={[]}
           colDefinitions={[]}
           numSelected={10}
           numSelected={10}
           order={'desc'}
           order={'desc'}
           orderBy={'id'}
           orderBy={'id'}
           onSelectAllClick={selectAllSpy}
           onSelectAllClick={selectAllSpy}
-          onRequestSort={() => {}}
+          handleSort={() => {}}
           rowCount={10}
           rowCount={10}
           openCheckBox={true}
           openCheckBox={true}
-        />,
-        container
-      );
-    });
-
-    const checkboxDom = container.querySelector('input[type="checkbox"]');
-    expect(container.querySelectorAll('.table-cell').length).toEqual(1);
-    expect(checkboxDom).toBeDefined();
-
-    fireEvent.click(checkboxDom);
+        />
+      </div>
+    );
+    // screen.debug();
+    const checkboxes: any = res.getAllByRole('checkbox');
+    expect(checkboxes.length).toEqual(1);
+    fireEvent.click(checkboxes[0]);
     expect(selectAllSpy).toBeCalledTimes(1);
     expect(selectAllSpy).toBeCalledTimes(1);
-    expect(checkboxDom.checked).toBeTruthy();
+    expect(checkboxes[0].checked).toBe(true);
   });
   });
 
 
   it('Test header cells', () => {
   it('Test header cells', () => {
-    const onRequestSortSpy = jest.fn();
+    const onRequestSortSpy = vi.fn();
     const colDefinitions = [
     const colDefinitions = [
       {
       {
         id: 'id',
         id: 'id',
@@ -96,31 +59,30 @@ describe('Test Table Head', () => {
         label: 'name',
         label: 'name',
       },
       },
     ];
     ];
-    act(() => {
-      render(
-        <TableHead
-          colDefinitions={colDefinitions}
-          numSelected={10}
-          order={'desc'}
-          orderBy={'id'}
-          onSelectAllClick={() => {}}
-          onRequestSort={onRequestSortSpy}
-          rowCount={10}
-          openCheckBox={false}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <TableHead
+        colDefinitions={colDefinitions}
+        numSelected={10}
+        order={'desc'}
+        orderBy={'id'}
+        onSelectAllClick={() => {}}
+        handleSort={onRequestSortSpy}
+        rowCount={10}
+        openCheckBox={false}
+      />
+    );
 
 
-    const headerCells = container.querySelectorAll('.MuiTableSortLabel-root');
+    const headerCells = res.getAllByRole('cell');
     expect(headerCells.length).toEqual(colDefinitions.length);
     expect(headerCells.length).toEqual(colDefinitions.length);
 
 
-    fireEvent.click(headerCells[0]);
+    expect(headerCells[0].textContent).toContain('id');
+    expect(headerCells[0].textContent).toContain('sorted descending');
+
+    const sortButton = res.getAllByRole('button');
+    fireEvent.click(sortButton[0]);
     expect(onRequestSortSpy).toBeCalledTimes(1);
     expect(onRequestSortSpy).toBeCalledTimes(1);
 
 
-    fireEvent.click(headerCells[1]);
+    fireEvent.click(sortButton[0]);
     expect(onRequestSortSpy).toBeCalledTimes(2);
     expect(onRequestSortSpy).toBeCalledTimes(2);
-    expect(headerCells[0].textContent).toContain('id');
-    expect(headerCells[0].textContent).toContain('sorted descending');
   });
   });
 });
 });

+ 30 - 76
client/src/components/__test__/grid/Toolbar.spec.tsx

@@ -1,74 +1,32 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
 import Toolbar from '../../grid/ToolBar';
 import Toolbar from '../../grid/ToolBar';
 import { ToolBarConfig } from '../../grid/Types';
 import { ToolBarConfig } from '../../grid/Types';
+import { vi } from 'vitest';
 
 
-jest.mock('@material-ui/icons/Search', () => {
-  return () => {
-    return <div id="search">search</div>;
-  };
-});
-
-jest.mock('../../customButton/CustomButton', () => {
-  return () => {
-    return <div className="button">button</div>;
-  };
-});
-
-jest.mock('../../customInput/SearchInput', () => {
-  return props => {
-    return <div>{props.children}</div>;
-  };
-});
-
-jest.mock('@material-ui/core/TextField', () => {
-  return props => {
-    return <input {...props} className="input" />;
-  };
-});
-
-let container: any = null;
-
-const cb = jest.fn().mockImplementation(resolve => resolve('a'));
+const cb = vi.fn().mockImplementation(resolve => resolve('a'));
 
 
-let toolbarConfig: ToolBarConfig[] = [];
+let toolbarConfig: ToolBarConfig[] = [
+  {
+    label: 'collection',
+    icon: 'delete',
+    onClick: cb,
+    disabled: selected => selected.length > 1,
+  },
+];
 
 
 describe('Test ToolBar', () => {
 describe('Test ToolBar', () => {
-  beforeEach(() => {
-    toolbarConfig = [
-      {
-        label: 'collection',
-        icon: 'delete',
-        onClick: cb,
-        disabled: selected => selected.length > 1,
-      },
-    ];
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test only one config', () => {
   it('Test only one config', () => {
-    act(() => {
-      render(
-        <Toolbar
-          selected={[]}
-          setSelected={() => {}}
-          toolbarConfigs={toolbarConfig}
-        ></Toolbar>,
-        container
-      );
-    });
-
-    const btnDom = container.querySelector('.button');
-    expect(container.querySelectorAll('.button').length).toBe(1);
-    expect(btnDom.className.includes('disabled')).toBeFalsy();
-    expect(container.querySelector('#search')).toBeNull();
+    const res = render(
+      <Toolbar
+        selected={[]}
+        setSelected={() => {}}
+        toolbarConfigs={toolbarConfig}
+      />
+    );
+
+    const button = res.getByRole('button');
+    expect(res.getAllByRole('button').length).toBe(1);
+    expect(button.className.includes('disabled')).toBeFalsy();
   });
   });
 
 
   it('Test Search Config', () => {
   it('Test Search Config', () => {
@@ -78,17 +36,13 @@ describe('Test ToolBar', () => {
       onClick: cb,
       onClick: cb,
       onSearch: cb,
       onSearch: cb,
     });
     });
-    act(() => {
-      render(
-        <Toolbar
-          selected={[]}
-          setSelected={() => {}}
-          toolbarConfigs={toolbarConfig}
-        ></Toolbar>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('.input').length).toBe(0);
+    const res = render(
+      <Toolbar
+        selected={[]}
+        setSelected={() => {}}
+        toolbarConfigs={toolbarConfig}
+      ></Toolbar>
+    );
+    expect(res.getAllByPlaceholderText('search').length).toBe(1);
   });
   });
 });
 });

+ 1 - 1
client/src/components/__test__/grid/Utils.spec.ts

@@ -2,7 +2,7 @@ import {
   descendingComparator,
   descendingComparator,
   getComparator,
   getComparator,
   stableSort,
   stableSort,
-} from '../../grid/Utils';
+} from '../../../utils/Sort';
 
 
 describe('Test Gird Utils', () => {
 describe('Test Gird Utils', () => {
   it('Test descendingComparator', () => {
   it('Test descendingComparator', () => {

+ 2 - 1
client/src/components/__test__/layout/GlobalEffect.spec.tsx

@@ -1,10 +1,11 @@
 import { render, unmountComponentAtNode } from 'react-dom';
 import { render, unmountComponentAtNode } from 'react-dom';
 import { act } from 'react-dom/test-utils';
 import { act } from 'react-dom/test-utils';
 import GlobalEffect from '../../layout/GlobalEffect';
 import GlobalEffect from '../../layout/GlobalEffect';
+import { vi } from 'vitest';
 
 
 let container: any = null;
 let container: any = null;
 
 
-jest.mock('react-router-dom', () => {
+vi.mock('react-router-dom', () => {
   return {
   return {
     useHistory: () => ({ location: { pathname: '' } }),
     useHistory: () => ({ location: { pathname: '' } }),
   };
   };

+ 33 - 44
client/src/components/__test__/layout/Layout.spec.tsx

@@ -1,57 +1,46 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render } from '@testing-library/react';
 import Layout from '../../layout/Layout';
 import Layout from '../../layout/Layout';
 import { MuiThemeProvider } from '@material-ui/core/styles';
 import { MuiThemeProvider } from '@material-ui/core/styles';
 import { theme } from '../../../styles/theme';
 import { theme } from '../../../styles/theme';
+import { vi } from 'vitest';
 
 
-let container: any = null;
-
-jest.mock('react-i18next', () => ({
-  useTranslation: () => ({
-    t: (key: any) => key,
-  }),
-}));
+vi.mock('react-i18next', () => {
+  return {
+    useTranslation: () => ({
+      t: (key: any) => key,
+    }),
+  };
+});
 
 
-jest.mock('react-router-dom', () => ({
-  useHistory: () => ({
-    push: jest.fn(),
-  }),
-  useLocation: () => ({
-    hash: '',
-    pathname: '/use-location-mock',
-    search: '',
-    state: undefined,
-  }),
-}));
+vi.mock('react-router-dom', () => {
+  return {
+    useHistory: () => ({
+      push: vi.fn(),
+    }),
+    useLocation: () => ({
+      hash: '',
+      pathname: '/use-location-mock',
+      search: '',
+      state: undefined,
+    }),
+  };
+});
 
 
-jest.mock('../../layout/GlobalEffect', () => {
-  return () => {
-    return <div id="global">{}</div>;
+vi.mock('../../layout/GlobalEffect', () => {
+  return {
+    default: () => {
+      return <div role="global">{}</div>;
+    },
   };
   };
 });
 });
 
 
 describe('Test Layout', () => {
 describe('Test Layout', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test Render', () => {
   it('Test Render', () => {
-    act(() => {
-      render(
-        <MuiThemeProvider theme={theme}>
-          <Layout />
-        </MuiThemeProvider>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('#global').length).toEqual(1);
+    const res = render(
+      <MuiThemeProvider theme={theme}>
+        <Layout />
+      </MuiThemeProvider>
+    );
+    expect(res.getAllByRole('global').length).toEqual(1);
   });
   });
 });
 });

+ 3 - 29
client/src/components/__test__/menu/SimpleMenu.spec.tsx

@@ -1,31 +1,7 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
 import SimpleMenu from '../../menu/SimpleMenu';
 import SimpleMenu from '../../menu/SimpleMenu';
 
 
-let container: any = null;
-
-jest.mock('@material-ui/core/styles/makeStyles', () => {
-  return () => () => ({});
-});
-
-jest.mock('@material-ui/core/MenuItem', () => {
-  return () => {
-    return <div className="menu-item"></div>;
-  };
-});
-
 describe('Test Simple Menu', () => {
 describe('Test Simple Menu', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test props ', () => {
   it('Test props ', () => {
     const items = [
     const items = [
       {
       {
@@ -39,10 +15,8 @@ describe('Test Simple Menu', () => {
         },
         },
       },
       },
     ];
     ];
-    act(() => {
-      render(<SimpleMenu label="test" menuItems={items} />, container);
-    });
+    const res = render(<SimpleMenu label="test" menuItems={items} />);
 
 
-    expect(container.querySelector('button').textContent).toEqual('test');
+    expect(res.getByRole('button').textContent).toEqual('test');
   });
   });
 });
 });

+ 9 - 36
client/src/components/__test__/status/Status.spec.tsx

@@ -1,16 +1,13 @@
-import { ReactNode } from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
 import Status from '../../status/Status';
 import Status from '../../status/Status';
-import { StatusEnum } from '../../status/Types';
+import { LOADING_STATE } from '../../../consts/Milvus';
+import { render, screen } from '@testing-library/react';
+import { vi } from 'vitest';
 
 
-let container: any = null;
-
-jest.mock('@material-ui/core/styles/makeStyles', () => {
+vi.mock('@material-ui/core/styles/makeStyles', () => {
   return () => () => ({});
   return () => () => ({});
 });
 });
 
 
-jest.mock('react-i18next', () => {
+vi.mock('react-i18next', () => {
   return {
   return {
     useTranslation: () => {
     useTranslation: () => {
       return {
       return {
@@ -26,35 +23,11 @@ jest.mock('react-i18next', () => {
   };
   };
 });
 });
 
 
-jest.mock('@material-ui/core/Typography', () => {
-  return (props: { children: ReactNode }) => {
-    return <div className="label">{props.children}</div>;
-  };
-});
-
 describe('Test Status', () => {
 describe('Test Status', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test props status', () => {
   it('Test props status', () => {
-    act(() => {
-      render(<Status status={StatusEnum.loaded} />, container);
-    });
-
-    expect(container.querySelector('.label').textContent).toEqual('loaded');
-
-    act(() => {
-      render(<Status status={StatusEnum.unloaded} />, container);
-    });
-
-    expect(container.querySelector('.label').textContent).toEqual('unloaded');
+    render(<Status status={LOADING_STATE.LOADED} />);
+    expect(screen.queryByText('loaded')?.textContent).toEqual('loaded');
+    render(<Status status={LOADING_STATE.UNLOADED} />);
+    expect(screen.queryByText('unloaded')?.textContent).toEqual('unloaded');
   });
   });
 });
 });

+ 2 - 2
client/src/components/customDialog/CustomDialogTitle.tsx

@@ -6,8 +6,9 @@ import {
 } from '@material-ui/core';
 } from '@material-ui/core';
 import MuiDialogTitle from '@material-ui/core/DialogTitle';
 import MuiDialogTitle from '@material-ui/core/DialogTitle';
 import icons from '../icons/Icons';
 import icons from '../icons/Icons';
+import { theme } from '../../styles/theme';
 
 
-const getStyles = makeStyles((theme: Theme) => ({
+const getStyles = makeStyles(() => ({
   root: {
   root: {
     margin: 0,
     margin: 0,
     display: 'flex',
     display: 'flex',
@@ -20,7 +21,6 @@ const getStyles = makeStyles((theme: Theme) => ({
   icon: {
   icon: {
     fontSize: '24px',
     fontSize: '24px',
     color: theme.palette.attuDark.main,
     color: theme.palette.attuDark.main,
-
     cursor: 'pointer',
     cursor: 'pointer',
   },
   },
 }));
 }));

+ 22 - 20
client/src/components/customInput/CustomInput.tsx

@@ -32,10 +32,10 @@ const handleOnBlur = (param: IBlurParam) => {
   const input = event.target.value;
   const input = event.target.value;
   const isValid = validations
   const isValid = validations
     ? checkValid({
     ? checkValid({
-      key,
-      value: input,
-      rules: validations,
-    })
+        key,
+        value: input,
+        rules: validations,
+      })
     : true;
     : true;
 
 
   if (isValid) {
   if (isValid) {
@@ -52,10 +52,10 @@ const handleOnChange = (param: IChangeParam) => {
   const input = event.target.value;
   const input = event.target.value;
   const isValid = validations
   const isValid = validations
     ? checkValid({
     ? checkValid({
-      key,
-      value: input,
-      rules: validations,
-    })
+        key,
+        value: input,
+        rules: validations,
+      })
     : true;
     : true;
 
 
   if (isValid) {
   if (isValid) {
@@ -90,19 +90,20 @@ const getAdornmentInput = (
   const classes = getAdornmentStyles();
   const classes = getAdornmentStyles();
 
 
   const param = {
   const param = {
-    cb: onInputBlur || (() => { }),
+    cb: onInputBlur || (() => {}),
     validations: validations || [],
     validations: validations || [],
     checkValid,
     checkValid,
   };
   };
 
 
   const info = validInfo ? validInfo[key] : null;
   const info = validInfo ? validInfo[key] : null;
+  const type = isPasswordType ? (showPassword ? 'text' : 'password') : 'text';
 
 
   return (
   return (
     <FormControl>
     <FormControl>
       <InputLabel htmlFor="standard-adornment-password">{label}</InputLabel>
       <InputLabel htmlFor="standard-adornment-password">{label}</InputLabel>
       <Input
       <Input
         classes={{ root: `${inputClass || {}}` }}
         classes={{ root: `${inputClass || {}}` }}
-        type={isPasswordType ? (showPassword ? 'text' : 'password') : 'text'}
+        type={type}
         onBlur={e => {
         onBlur={e => {
           handleOnBlur({ event: e, key, param });
           handleOnBlur({ event: e, key, param });
         }}
         }}
@@ -112,13 +113,13 @@ const getAdornmentInput = (
             key,
             key,
             param: {
             param: {
               ...param,
               ...param,
-              cb: onInputChange || (() => { }),
+              cb: onInputChange || (() => {}),
             },
             },
           });
           });
         }}
         }}
         endAdornment={
         endAdornment={
           <InputAdornment position="end">
           <InputAdornment position="end">
-            <IconButton onClick={onIconClick || (() => { })} edge="end">
+            <IconButton onClick={onIconClick || (() => {})} edge="end" role="icon-button">
               {isPasswordType
               {isPasswordType
                 ? showPassword
                 ? showPassword
                   ? Icons.visible({ classes: { root: classes.icon } })
                   ? Icons.visible({ classes: { root: classes.icon } })
@@ -128,6 +129,7 @@ const getAdornmentInput = (
           </InputAdornment>
           </InputAdornment>
         }
         }
         inputProps={{
         inputProps={{
+          'role': 'textbox',
           'data-cy': key,
           'data-cy': key,
         }}
         }}
       />
       />
@@ -162,10 +164,10 @@ const getIconInput = (
         {inputType === 'icon'
         {inputType === 'icon'
           ? getTextfield(inputConfig as ITextfieldConfig, checkValid, validInfo)
           ? getTextfield(inputConfig as ITextfieldConfig, checkValid, validInfo)
           : getAdornmentInput(
           : getAdornmentInput(
-            inputConfig as IAdornmentConfig,
-            checkValid,
-            validInfo
-          )}
+              inputConfig as IAdornmentConfig,
+              checkValid,
+              validInfo
+            )}
       </Grid>
       </Grid>
     </Grid>
     </Grid>
   );
   );
@@ -196,7 +198,7 @@ const getTextfield = (
   }
   }
 
 
   const param = {
   const param = {
-    cb: onBlur || (() => { }),
+    cb: onBlur || (() => {}),
     validations: validations || [],
     validations: validations || [],
     checkValid,
     checkValid,
   };
   };
@@ -214,8 +216,8 @@ const getTextfield = (
       placeholder={placeholder || ''}
       placeholder={placeholder || ''}
       inputProps={
       inputProps={
         inputProps
         inputProps
-          ? { ...inputProps, ...defaultInputProps }
-          : { ...defaultInputProps }
+          ? { ...inputProps, ...defaultInputProps, role: 'textbox' }
+          : { ...defaultInputProps, role: 'textbox' }
       }
       }
       error={info?.result && info.errText !== ''}
       error={info?.result && info.errText !== ''}
       InputProps={InputProps ? { ...InputProps } : {}}
       InputProps={InputProps ? { ...InputProps } : {}}
@@ -233,7 +235,7 @@ const getTextfield = (
         handleOnChange({
         handleOnChange({
           event,
           event,
           key,
           key,
-          param: { ...param, cb: onChange || (() => { }) },
+          param: { ...param, cb: onChange || (() => {}) },
         });
         });
       }}
       }}
     />
     />

+ 1 - 1
client/src/components/grid/Grid.tsx

@@ -177,7 +177,7 @@ const AttuGrid: FC<AttuGridType> = props => {
     >
     >
       {title && (
       {title && (
         <Grid item xs={12} className={classes.tableTitle}>
         <Grid item xs={12} className={classes.tableTitle}>
-          <Breadcrumbs separator="›" aria-label="breadcrumb">
+          <Breadcrumbs separator="›" aria-label="breadcrumb" role="breadcrumb">
             {title.map(
             {title.map(
               (v: any, i: number) =>
               (v: any, i: number) =>
                 v && (
                 v && (

+ 1 - 1
client/src/components/grid/LoadingTable.tsx

@@ -29,7 +29,7 @@ const LoadingTable = (props: { wrapperClass?: string; count: number }) => {
   return (
   return (
     <div className={`${classes.wrapper} ${wrapperClass}`}>
     <div className={`${classes.wrapper} ${wrapperClass}`}>
       {rows.map((row, index) => (
       {rows.map((row, index) => (
-        <div key={index} className={classes.tr}>
+        <div key={index} className={classes.tr} role="skeleton">
           <Skeleton height={16} classes={{ root: classes.skeleton }} />
           <Skeleton height={16} classes={{ root: classes.skeleton }} />
           <Skeleton height={16} classes={{ root: classes.skeleton }} />
           <Skeleton height={16} classes={{ root: classes.skeleton }} />
         </div>
         </div>

+ 6 - 4
client/src/components/grid/Table.tsx

@@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({
   },
   },
   tableCell: {
   tableCell: {
     background: theme.palette.common.white,
     background: theme.palette.common.white,
-    paddingLeft: theme.spacing(2),
+    padding: `${theme.spacing(1.5)} ${theme.spacing(2)}`,
   },
   },
   hoverActionCell: {
   hoverActionCell: {
     transition: '0.2s all',
     transition: '0.2s all',
@@ -213,7 +213,6 @@ const EnhancedTable: FC<TableType> = props => {
                       hover={showHoverStyle}
                       hover={showHoverStyle}
                       key={'row' + row[primaryKey] + index}
                       key={'row' + row[primaryKey] + index}
                       onClick={event => onSelected(event, row)}
                       onClick={event => onSelected(event, row)}
-                      role="checkbox"
                       aria-checked={isItemSelected}
                       aria-checked={isItemSelected}
                       tabIndex={-1}
                       tabIndex={-1}
                       selected={isItemSelected && !disableSelect}
                       selected={isItemSelected && !disableSelect}
@@ -229,7 +228,10 @@ const EnhancedTable: FC<TableType> = props => {
                           <Checkbox
                           <Checkbox
                             checked={isItemSelected}
                             checked={isItemSelected}
                             color="primary"
                             color="primary"
-                            inputProps={{ 'aria-labelledby': labelId }}
+                            inputProps={{
+                              'aria-labelledby': labelId,
+                              role: 'checkbox',
+                            }}
                           />
                           />
                         </TableCell>
                         </TableCell>
                       )}
                       )}
@@ -247,7 +249,7 @@ const EnhancedTable: FC<TableType> = props => {
                                 ? classes.hoverActionCell
                                 ? classes.hoverActionCell
                                 : ''
                                 : ''
                             }`}
                             }`}
-                            key="manage"
+                            key={colDef.id}
                             style={cellStyle}
                             style={cellStyle}
                           >
                           >
                             <ActionBar
                             <ActionBar

+ 3 - 3
client/src/components/grid/TableHead.tsx

@@ -29,7 +29,6 @@ const useStyles = makeStyles(theme => ({
     // borderBottom: 'none',
     // borderBottom: 'none',
   },
   },
   tableHeader: {
   tableHeader: {
-    textTransform: 'capitalize',
     color: 'rgba(0, 0, 0, 0.6)',
     color: 'rgba(0, 0, 0, 0.6)',
     fontSize: '12.8px',
     fontSize: '12.8px',
   },
   },
@@ -58,13 +57,13 @@ const EnhancedTableHead: FC<TableHeadType> = props => {
     <TableHead>
     <TableHead>
       <TableRow className={classes.tableRow}>
       <TableRow className={classes.tableRow}>
         {openCheckBox && (
         {openCheckBox && (
-          <TableCell padding="checkbox">
+          <TableCell padding="checkbox" role="cell">
             <Checkbox
             <Checkbox
               color="primary"
               color="primary"
               indeterminate={numSelected > 0 && numSelected < rowCount}
               indeterminate={numSelected > 0 && numSelected < rowCount}
               checked={rowCount > 0 && numSelected === rowCount}
               checked={rowCount > 0 && numSelected === rowCount}
               onChange={onSelectAllClick}
               onChange={onSelectAllClick}
-              inputProps={{ 'aria-label': 'select all desserts' }}
+              inputProps={{ 'aria-label': 'select all desserts', 'role': 'checkbox' }}
             />
             />
           </TableCell>
           </TableCell>
         )}
         )}
@@ -78,6 +77,7 @@ const EnhancedTableHead: FC<TableHeadType> = props => {
               orderBy === (headCell.sortBy || headCell.id) ? order : false
               orderBy === (headCell.sortBy || headCell.id) ? order : false
             }
             }
             className={classes.tableCell}
             className={classes.tableCell}
+            role="cell"
           >
           >
             {headCell.label && handleSort && !headCell.notSort ? (
             {headCell.label && handleSort && !headCell.notSort ? (
               <TableSortLabel
               <TableSortLabel

+ 3 - 1
client/src/components/grid/ToolBar.tsx

@@ -59,7 +59,7 @@ const CustomToolBar: FC<ToolBarType> = props => {
 
 
   return (
   return (
     <>
     <>
-      <Grid container>
+      <Grid container role="toolbar">
         <Grid item xs={8}>
         <Grid item xs={8}>
           {leftConfigs.map((c, i) => {
           {leftConfigs.map((c, i) => {
             const isSelect = c.type === 'select' || c.type === 'groupSelect';
             const isSelect = c.type === 'select' || c.type === 'groupSelect';
@@ -86,6 +86,7 @@ const CustomToolBar: FC<ToolBarType> = props => {
                 variant={c.btnVariant || 'contained'}
                 variant={c.btnVariant || 'contained'}
                 tooltip={tooltip}
                 tooltip={tooltip}
                 className={classes.btn}
                 className={classes.btn}
+                role="button"
               >
               >
                 <Typography variant="button">{c.label}</Typography>
                 <Typography variant="button">{c.label}</Typography>
               </CustomButton>
               </CustomButton>
@@ -97,6 +98,7 @@ const CustomToolBar: FC<ToolBarType> = props => {
                 onClick={c.onClick}
                 onClick={c.onClick}
                 tooltip={tooltip}
                 tooltip={tooltip}
                 disabled={disabled}
                 disabled={disabled}
+                role="button"
               >
               >
                 {Icon}
                 {Icon}
               </CustomIconButton>
               </CustomIconButton>

+ 7 - 2
client/src/components/icons/Icons.tsx

@@ -19,7 +19,7 @@ import ExitToAppIcon from '@material-ui/icons/ExitToApp';
 import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
 import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
 import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
 import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
 import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
 import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
-import CachedIcon from '@material-ui/icons/Cached';
+import RefreshIcon from '@material-ui/icons/Refresh';
 import FilterListIcon from '@material-ui/icons/FilterList';
 import FilterListIcon from '@material-ui/icons/FilterList';
 import AlternateEmailIcon from '@material-ui/icons/AlternateEmail';
 import AlternateEmailIcon from '@material-ui/icons/AlternateEmail';
 import DatePicker from '@material-ui/icons/Event';
 import DatePicker from '@material-ui/icons/Event';
@@ -61,7 +61,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   rightArrow: (props = {}) => <ArrowForwardIosIcon {...props} />,
   rightArrow: (props = {}) => <ArrowForwardIosIcon {...props} />,
   remove: (props = {}) => <RemoveCircleOutlineIcon {...props} />,
   remove: (props = {}) => <RemoveCircleOutlineIcon {...props} />,
   dropdown: (props = {}) => <ArrowDropDownIcon {...props} />,
   dropdown: (props = {}) => <ArrowDropDownIcon {...props} />,
-  refresh: (props = {}) => <CachedIcon {...props} />,
+  refresh: (props = {}) => <RefreshIcon {...props} />,
   filter: (props = {}) => <FilterListIcon {...props} />,
   filter: (props = {}) => <FilterListIcon {...props} />,
   alias: (props = {}) => <AlternateEmailIcon {...props} />,
   alias: (props = {}) => <AlternateEmailIcon {...props} />,
   datePicker: (props = {}) => <DatePicker {...props} />,
   datePicker: (props = {}) => <DatePicker {...props} />,
@@ -114,6 +114,11 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   copyExpression: (props = {}) => (
   copyExpression: (props = {}) => (
     <SvgIcon viewBox="0 0 16 16" component={CopyIcon} {...props} />
     <SvgIcon viewBox="0 0 16 16" component={CopyIcon} {...props} />
   ),
   ),
+  source: (props = {}) => (
+    <SvgIcon viewBox="0 0 24 24" {...props}>
+      <path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-6 10H6v-2h8v2zm4-4H6v-2h12v2z"></path>
+    </SvgIcon>
+  ),
 };
 };
 
 
 export default icons;
 export default icons;

+ 2 - 1
client/src/components/icons/Types.ts

@@ -36,4 +36,5 @@ export type IconsType =
   | 'copyExpression'
   | 'copyExpression'
   | 'alias'
   | 'alias'
   | 'datePicker'
   | 'datePicker'
-  | 'download';
+  | 'download'
+  | 'source';

+ 145 - 0
client/src/components/insert/ImportSample.tsx

@@ -0,0 +1,145 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { FC, useState, useContext } from 'react';
+import { useTranslation } from 'react-i18next';
+import DialogTemplate from '../customDialog/DialogTemplate';
+import CustomSelector from '../customSelector/CustomSelector';
+import { rootContext } from '../../context/Root';
+import { InsertStatusEnum } from './Types';
+
+const getStyles = makeStyles((theme: Theme) => {
+  return {
+    icon: {
+      fontSize: '16px',
+    },
+
+    selectors: {
+      '& .selectorWrapper': {
+        display: 'flex',
+        flexDirection: 'column',
+        marginBottom: theme.spacing(2),
+
+        '& .selectLabel': {
+          fontSize: '14px',
+          lineHeight: '20px',
+          color: theme.palette.attuDark.main,
+        },
+
+        '& .description': {
+          color: theme.palette.attuGrey.dark,
+          marginBottom: theme.spacing(1),
+          fontSize: 12,
+        },
+      },
+
+      '& .selector': {
+        minWidth: '128px',
+      },
+    },
+  };
+});
+
+/**
+ * this component contains processes during insert
+ * including import, preview and status
+ */
+
+const ImportSample: FC<{ collection: string; handleImport: Function }> =
+  props => {
+    const classes = getStyles();
+    const [size, setSize] = useState<string>('100');
+    const [insertStatus, setInsertStatus] = useState<InsertStatusEnum>(
+      InsertStatusEnum.init
+    );
+
+    const { t: insertTrans } = useTranslation('insert');
+    const { t: btnTrans } = useTranslation('btn');
+    const { handleCloseDialog, openSnackBar } = useContext(rootContext);
+    // selected collection name
+
+    const sizeOptions = [
+      {
+        label: '100',
+        value: '100',
+      },
+      {
+        label: '1k',
+        value: '1000',
+      },
+      {
+        label: '10k',
+        value: '10000',
+      },
+      {
+        label: '50k',
+        value: '50000',
+      },
+    ];
+
+    const handleNext = async () => {
+      if (insertStatus === InsertStatusEnum.success) {
+        handleCloseDialog();
+        return;
+      }
+      // start loading
+      setInsertStatus(InsertStatusEnum.loading);
+      const { result, msg } = await props.handleImport(props.collection, size);
+
+      if (!result) {
+        openSnackBar(msg, 'error');
+        setInsertStatus(InsertStatusEnum.init);
+        return;
+      }
+      setInsertStatus(InsertStatusEnum.success);
+      // hide dialog
+      handleCloseDialog();
+    };
+
+    return (
+      <DialogTemplate
+        title={insertTrans('importSampleData', {
+          collection: props.collection,
+        })}
+        handleClose={handleCloseDialog}
+        confirmLabel={
+          insertStatus === InsertStatusEnum.init
+            ? btnTrans('import')
+            : insertStatus === InsertStatusEnum.loading
+            ? btnTrans('importing')
+            : insertStatus === InsertStatusEnum.success
+            ? btnTrans('done')
+            : insertStatus
+        }
+        handleConfirm={handleNext}
+        confirmDisabled={false}
+        showActions={true}
+        showCancel={false}
+        // don't show close icon when insert not finish
+        // showCloseIcon={insertStatus !== InsertStatusEnum.loading}
+      >
+        <form className={classes.selectors}>
+          <div className="selectorWrapper">
+            <div className="description">
+              <Typography variant="inherit" component="p">
+                {insertTrans('importSampleDataDesc')}
+              </Typography>
+            </div>
+
+            <CustomSelector
+              label={insertTrans('sampleDataSize')}
+              options={sizeOptions}
+              wrapperClass="selector"
+              labelClass="selectLabel"
+              value={size}
+              variant="filled"
+              onChange={(e: { target: { value: unknown } }) => {
+                const size = e.target.value;
+                setSize(size as string);
+              }}
+            />
+          </div>
+        </form>
+      </DialogTemplate>
+    );
+  };
+
+export default ImportSample;

+ 15 - 22
client/src/components/layout/Layout.tsx

@@ -10,9 +10,6 @@ import { useHistory, useLocation } from 'react-router-dom';
 import { authContext } from '../../context/Auth';
 import { authContext } from '../../context/Auth';
 import { rootContext } from '../../context/Root';
 import { rootContext } from '../../context/Root';
 import { IconsType } from '../icons/Types';
 import { IconsType } from '../icons/Types';
-import loadable from '@loadable/component';
-
-const PLUGIN_DEV = process.env?.REACT_APP_PLUGIN_DEV;
 
 
 const useStyles = makeStyles((theme: Theme) =>
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
   createStyles({
@@ -79,16 +76,16 @@ const Layout = (props: any) => {
       label: navTrans('overview'),
       label: navTrans('overview'),
       onClick: () => history.push('/'),
       onClick: () => history.push('/'),
     },
     },
-    {
-      icon: icons.navPerson,
-      label: navTrans('user'),
-      onClick: () => history.push('/users'),
-    },
     {
     {
       icon: icons.navCollection,
       icon: icons.navCollection,
       label: navTrans('collection'),
       label: navTrans('collection'),
       onClick: () => history.push('/collections'),
       onClick: () => history.push('/collections'),
     },
     },
+    {
+      icon: icons.navPerson,
+      label: navTrans('user'),
+      onClick: () => history.push('/users'),
+    },
     // {
     // {
     //   icon: icons.navSearch,
     //   icon: icons.navSearch,
     //   label: navTrans('search'),
     //   label: navTrans('search'),
@@ -98,10 +95,11 @@ const Layout = (props: any) => {
     // },
     // },
   ];
   ];
 
 
-  function importAll(r: any, outOfRoot = false) {
-    r.keys().forEach((key: any) => {
-      const content = r(key);
+  function importAll(r: any) {
+    Object.keys(r).forEach((key: any) => {
+      const content = r[key];
       const pathName = content.client?.path;
       const pathName = content.client?.path;
+
       if (!pathName) return;
       if (!pathName) return;
       const result: NavMenuItem = {
       const result: NavMenuItem = {
         icon: icons.navOverview,
         icon: icons.navOverview,
@@ -109,27 +107,22 @@ const Layout = (props: any) => {
       };
       };
       result.onClick = () => history.push(`/${pathName}`);
       result.onClick = () => history.push(`/${pathName}`);
       const iconName: IconsType = content.client?.iconName;
       const iconName: IconsType = content.client?.iconName;
-      const iconEntry = content.client?.icon;
-      const dirName = key.split('/config.json').shift().split('/')[1];
-      // const fileEntry = content.client?.entry;
       if (iconName) {
       if (iconName) {
         result.icon = icons[iconName];
         result.icon = icons[iconName];
-      } else if (iconEntry) {
-        const customIcon = outOfRoot
-          ? loadable(() => import(`all_plugins/${dirName}/client/${iconEntry}`))
-          : loadable(() => import(`../../plugins/${dirName}/${iconEntry}`));
-        result.icon = customIcon;
       }
       }
       content.client?.iconActiveClass &&
       content.client?.iconActiveClass &&
         (result.iconActiveClass = content.client?.iconActiveClass);
         (result.iconActiveClass = content.client?.iconActiveClass);
       content.client?.iconNormalClass &&
       content.client?.iconNormalClass &&
         (result.iconNormalClass = content.client?.iconNormalClass);
         (result.iconNormalClass = content.client?.iconNormalClass);
+
       menuItems.push(result);
       menuItems.push(result);
     });
     });
   }
   }
-  importAll(require.context('../../plugins', true, /config\.json$/));
-  PLUGIN_DEV &&
-    importAll(require.context('all_plugins/', true, /config\.json$/), true);
+  const pluginConfigs = import.meta.glob(`../../plugins/**/config.json`, {
+    eager: true,
+  });
+
+  importAll(pluginConfigs);
 
 
   return (
   return (
     <div className={classes.root}>
     <div className={classes.root}>

+ 2 - 2
client/src/http/Axios.ts

@@ -2,12 +2,12 @@ import axios from 'axios';
 import { MILVUS_ADDRESS } from '../consts/Localstorage';
 import { MILVUS_ADDRESS } from '../consts/Localstorage';
 // import { SESSION } from '../consts/Localstorage';
 // import { SESSION } from '../consts/Localstorage';
 
 
-// console.log(process.env.NODE_ENV, 'api:', process.env.REACT_APP_BASE_URL);
+// console.log(import.meta.env.NODE_ENV, 'api:', import.meta.env.VITE_BASE_URL);
 // console.log('docker env', (window as any)._env_);
 // console.log('docker env', (window as any)._env_);
 const isElectron =
 const isElectron =
   (window as any)._env_ && (window as any)._env_.IS_ELECTRON === 'yes';
   (window as any)._env_ && (window as any)._env_.IS_ELECTRON === 'yes';
 export const url =
 export const url =
-  process.env.NODE_ENV === 'development' || isElectron
+  import.meta.env.MODE === 'development' || isElectron
     ? (window as any)._env_ && (window as any)._env_.HOST_URL
     ? (window as any)._env_ && (window as any)._env_.HOST_URL
     : '';
     : '';
 
 

+ 8 - 0
client/src/http/Collection.ts

@@ -3,6 +3,7 @@ import {
   CollectionView,
   CollectionView,
   DeleteEntitiesReq,
   DeleteEntitiesReq,
   InsertDataParam,
   InsertDataParam,
+  LoadSampleParam
 } from '../pages/collections/Types';
 } from '../pages/collections/Types';
 import { Field } from '../pages/schema/Types';
 import { Field } from '../pages/schema/Types';
 import { VectorSearchParam } from '../types/SearchTypes';
 import { VectorSearchParam } from '../types/SearchTypes';
@@ -88,6 +89,13 @@ export class CollectionHttp extends BaseModel implements CollectionView {
     });
     });
   }
   }
 
 
+  static importSample(collectionName: string, param: LoadSampleParam) {
+    return super.create({
+      path: `${this.COLLECTIONS_URL}/${collectionName}/importSample`,
+      data: param,
+    });
+  }
+
   static deleteEntities(collectionName: string, param: DeleteEntitiesReq) {
   static deleteEntities(collectionName: string, param: DeleteEntitiesReq) {
     return super.update({
     return super.update({
       path: `${this.COLLECTIONS_URL}/${collectionName}/entities`,
       path: `${this.COLLECTIONS_URL}/${collectionName}/entities`,

+ 1 - 1
client/src/http/User.ts

@@ -2,7 +2,7 @@ import {
   CreateUserParams,
   CreateUserParams,
   DeleteUserParams,
   DeleteUserParams,
   UpdateUserParams,
   UpdateUserParams,
-} from 'insight_src/pages/user/Types';
+} from '../pages/user/Types';
 import BaseModel from './BaseModel';
 import BaseModel from './BaseModel';
 
 
 export class UserHttp extends BaseModel {
 export class UserHttp extends BaseModel {

+ 1 - 1
client/src/i18n/cn/success.ts

@@ -2,7 +2,7 @@ const successTrans = {
   connect: 'Connection to milvus successful',
   connect: 'Connection to milvus successful',
   create: `{{name}} has been created`,
   create: `{{name}} has been created`,
   load: `{{name}} has been loaded`,
   load: `{{name}} has been loaded`,
-  delete: `{{name}} successfully deleted`,
+  delete: `Delete {{name}} successfully`,
   release: `{{name}} has been released`,
   release: `{{name}} has been released`,
 };
 };
 
 

+ 4 - 0
client/src/i18n/en/button.ts

@@ -12,11 +12,15 @@ const btnTrans = {
   release: 'Release',
   release: 'Release',
   load: 'Load',
   load: 'Load',
   insert: 'Import Data',
   insert: 'Import Data',
+  refresh: 'Refresh',
   next: 'Next',
   next: 'Next',
   previous: 'Previous',
   previous: 'Previous',
   done: 'Done',
   done: 'Done',
   vectorSearch: 'Vector search',
   vectorSearch: 'Vector search',
   query: 'Query',
   query: 'Query',
+  importSampleData: 'Import Sample data',
+  loading: 'Loading...',
+  importing: 'Importing...'
 };
 };
 
 
 export default btnTrans;
 export default btnTrans;

+ 1 - 0
client/src/i18n/en/collection.ts

@@ -65,6 +65,7 @@ const collectionTrans = {
   partitionTab: 'Partitions',
   partitionTab: 'Partitions',
   schemaTab: 'Schema',
   schemaTab: 'Schema',
   queryTab: 'Data Query',
   queryTab: 'Data Query',
+  previewTab: 'Data Preview',
   startTip: 'Start your data query',
   startTip: 'Start your data query',
   exprPlaceHolder: 'Please enter your query by using advanced filter ->',
   exprPlaceHolder: 'Please enter your query by using advanced filter ->',
 };
 };

+ 4 - 0
client/src/i18n/en/insert.ts

@@ -28,6 +28,10 @@ const insertTrans = {
   statusLoadingTip: 'Please wait patiently, thank you',
   statusLoadingTip: 'Please wait patiently, thank you',
   statusSuccess: 'Import Data Successfully!',
   statusSuccess: 'Import Data Successfully!',
   statusError: 'Import Data Failed!',
   statusError: 'Import Data Failed!',
+
+  importSampleData: 'Import sample data into {{collection}}',
+  sampleDataSize: 'Choose sample data size',
+  importSampleDataDesc: `Import random data based on the collection's schema.`
 };
 };
 
 
 export default insertTrans;
 export default insertTrans;

+ 9 - 4
client/src/pages/collections/Collection.tsx

@@ -9,6 +9,7 @@ import { useMemo } from 'react';
 import { parseLocationSearch } from '../../utils/Format';
 import { parseLocationSearch } from '../../utils/Format';
 import Schema from '../schema/Schema';
 import Schema from '../schema/Schema';
 import Query from '../query/Query';
 import Query from '../query/Query';
+import Preview from '../preview/Preview';
 
 
 enum TAB_EMUM {
 enum TAB_EMUM {
   'schema',
   'schema',
@@ -40,10 +41,6 @@ const Collection = () => {
   };
   };
 
 
   const tabs: ITab[] = [
   const tabs: ITab[] = [
-    {
-      label: collectionTrans('queryTab'),
-      component: <Query collectionName={collectionName} />,
-    },
     {
     {
       label: collectionTrans('schemaTab'),
       label: collectionTrans('schemaTab'),
       component: <Schema collectionName={collectionName} />,
       component: <Schema collectionName={collectionName} />,
@@ -52,6 +49,14 @@ const Collection = () => {
       label: collectionTrans('partitionTab'),
       label: collectionTrans('partitionTab'),
       component: <Partitions collectionName={collectionName} />,
       component: <Partitions collectionName={collectionName} />,
     },
     },
+    {
+      label: collectionTrans('previewTab'),
+      component: <Preview collectionName={collectionName} />,
+    },
+    {
+      label: collectionTrans('queryTab'),
+      component: <Query collectionName={collectionName} />,
+    },
   ];
   ];
 
 
   return (
   return (

+ 70 - 6
client/src/pages/collections/Collections.tsx

@@ -9,6 +9,7 @@ import {
   CollectionView,
   CollectionView,
   DataTypeEnum,
   DataTypeEnum,
   InsertDataParam,
   InsertDataParam,
+  LoadSampleParam,
 } from './Types';
 } from './Types';
 import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
 import { ColDefinitionsType, ToolBarConfig } from '../../components/grid/Types';
 import { usePaginationHook } from '../../hooks/Pagination';
 import { usePaginationHook } from '../../hooks/Pagination';
@@ -31,6 +32,7 @@ import {
 import Highlighter from 'react-highlight-words';
 import Highlighter from 'react-highlight-words';
 import { parseLocationSearch } from '../../utils/Format';
 import { parseLocationSearch } from '../../utils/Format';
 import InsertContainer from '../../components/insert/Container';
 import InsertContainer from '../../components/insert/Container';
+import ImportSample from '../../components/insert/ImportSample';
 import { MilvusHttp } from '../../http/Milvus';
 import { MilvusHttp } from '../../http/Milvus';
 import { LOADING_STATE } from '../../consts/Milvus';
 import { LOADING_STATE } from '../../consts/Milvus';
 import { webSokcetContext } from '../../context/WebSocket';
 import { webSokcetContext } from '../../context/WebSocket';
@@ -88,6 +90,7 @@ const Collections = () => {
   const LoadIcon = icons.load;
   const LoadIcon = icons.load;
   const ReleaseIcon = icons.release;
   const ReleaseIcon = icons.release;
   const InfoIcon = icons.info;
   const InfoIcon = icons.info;
+  const SourceIcon = icons.source;
 
 
   const searchedCollections = useMemo(
   const searchedCollections = useMemo(
     () => collections.filter(collection => collection._name.includes(search)),
     () => collections.filter(collection => collection._name.includes(search)),
@@ -183,6 +186,28 @@ const Collections = () => {
     }
     }
   };
   };
 
 
+  const handleImportSample = async (
+    collectionName: string,
+    size: string
+  ): Promise<{ result: boolean; msg: string }> => {
+    const param: LoadSampleParam = {
+      collection_name: collectionName,
+      size: size,
+    };
+    try {
+      await CollectionHttp.importSample(collectionName, param);
+      await MilvusHttp.flush(collectionName);
+      return { result: true, msg: '' };
+    } catch (err: any) {
+      const {
+        response: {
+          data: { message },
+        },
+      } = err;
+      return { result: false, msg: message || '' };
+    }
+  };
+
   const handleCreateCollection = async (param: CollectionCreateParam) => {
   const handleCreateCollection = async (param: CollectionCreateParam) => {
     const data: CollectionCreateParam = JSON.parse(JSON.stringify(param));
     const data: CollectionCreateParam = JSON.parse(JSON.stringify(param));
     const vectorType = [DataTypeEnum.BinaryVector, DataTypeEnum.FloatVector];
     const vectorType = [DataTypeEnum.BinaryVector, DataTypeEnum.FloatVector];
@@ -294,6 +319,14 @@ const Collections = () => {
         collectionList.length === 0 || selectedCollections.length > 1,
         collectionList.length === 0 || selectedCollections.length > 1,
       btnVariant: 'outlined',
       btnVariant: 'outlined',
     },
     },
+    {
+      type: 'iconBtn',
+      onClick: () => {
+        fetchData();
+      },
+      label: collectionTrans('delete'),
+      icon: 'refresh',
+    },
     {
     {
       type: 'iconBtn',
       type: 'iconBtn',
       onClick: () => {
       onClick: () => {
@@ -369,12 +402,6 @@ const Collections = () => {
       sortBy: '_status',
       sortBy: '_status',
       label: collectionTrans('status'),
       label: collectionTrans('status'),
     },
     },
-    {
-      id: 'consistency_level',
-      align: 'left',
-      disablePadding: false,
-      label: collectionTrans('consistencyLevel'),
-    },
     {
     {
       id: '_rowCount',
       id: '_rowCount',
       align: 'left',
       align: 'left',
@@ -388,6 +415,12 @@ const Collections = () => {
         </span>
         </span>
       ),
       ),
     },
     },
+    {
+      id: 'consistency_level',
+      align: 'left',
+      disablePadding: false,
+      label: collectionTrans('consistencyLevel'),
+    },
     {
     {
       id: '_desc',
       id: '_desc',
       align: 'left',
       align: 'left',
@@ -436,6 +469,37 @@ const Collections = () => {
         },
         },
       ],
       ],
     },
     },
+    {
+      id: 'import',
+      align: 'center',
+      disablePadding: false,
+      label: '',
+      showActionCell: true,
+      isHoverAction: true,
+      actionBarConfigs: [
+        {
+          onClick: (e: React.MouseEvent, row: CollectionView) => {
+            setDialog({
+              open: true,
+              type: 'custom',
+              params: {
+                component: (
+                  <ImportSample
+                    collection={row._name}
+                    handleImport={handleImportSample}
+                  />
+                ),
+              },
+            });
+          },
+          icon: 'source',
+          label: 'Import',
+          showIconMethod: 'renderFn',
+          getLabel: () => 'Import sample data',
+          renderIconFn: (row: CollectionView) => <SourceIcon />,
+        },
+      ],
+    },
   ];
   ];
 
 
   const handleSelectChange = (value: any) => {
   const handleSelectChange = (value: any) => {

+ 6 - 0
client/src/pages/collections/Types.ts

@@ -110,6 +110,12 @@ export interface InsertDataParam {
   fields_data: any[];
   fields_data: any[];
 }
 }
 
 
+export interface LoadSampleParam {
+  collection_name: string;
+  // e.g. [{vector: [1,2,3], age: 10}]
+  size: string;
+}
+
 export interface DeleteEntitiesReq {
 export interface DeleteEntitiesReq {
   expr: string;
   expr: string;
   partition_name?: string;
   partition_name?: string;

+ 3 - 1
client/src/pages/connect/AuthForm.tsx

@@ -31,7 +31,7 @@ const useStyles = makeStyles((theme: Theme) => ({
     alignItems: 'center',
     alignItems: 'center',
     padding: theme.spacing(3),
     padding: theme.spacing(3),
     margin: '0 auto',
     margin: '0 auto',
-
+    flexDirection: 'column',
     '& .title': {
     '& .title': {
       margin: 0,
       margin: 0,
       color: '#323232',
       color: '#323232',
@@ -41,6 +41,8 @@ const useStyles = makeStyles((theme: Theme) => ({
   logo: {
   logo: {
     width: '42px',
     width: '42px',
     height: 'auto',
     height: 'auto',
+    marginBottom: '8px',
+    display: 'block'
   },
   },
   input: {
   input: {
     margin: theme.spacing(3, 0, 0.5),
     margin: theme.spacing(3, 0, 0.5),

+ 0 - 2
client/src/pages/connect/ConnectContainer.tsx

@@ -1,12 +1,10 @@
 import { makeStyles, Theme } from '@material-ui/core';
 import { makeStyles, Theme } from '@material-ui/core';
 import { ReactElement } from 'react';
 import { ReactElement } from 'react';
-import backgroundPath from '../../assets/imgs/connectContainer/background.png';
 
 
 const getContainerStyles = makeStyles((theme: Theme) => ({
 const getContainerStyles = makeStyles((theme: Theme) => ({
   wrapper: {
   wrapper: {
     width: '100%',
     width: '100%',
     height: '90%',
     height: '90%',
-    backgroundImage: `url(${backgroundPath})`,
     backgroundRepeat: 'no-repeat',
     backgroundRepeat: 'no-repeat',
     backgroundSize: 'cover',
     backgroundSize: 'cover',
   },
   },

+ 196 - 0
client/src/pages/preview/Preview.tsx

@@ -0,0 +1,196 @@
+import { FC, useEffect, useState, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import AttuGrid from '../../components/grid/Grid';
+import { getQueryStyles } from '../query/Styles';
+import { CollectionHttp } from '../../http/Collection';
+import { FieldHttp } from '../../http/Field';
+import { IndexHttp } from '../../http/Index';
+import { usePaginationHook } from '../../hooks/Pagination';
+import CopyButton from '../../components/advancedSearch/CopyButton';
+import { ToolBarConfig } from '../../components/grid/Types';
+import CustomToolBar from '../../components/grid/ToolBar';
+import { DataTypeStringEnum } from '../collections/Types';
+import { generateVector } from '../../utils/Common';
+
+import {
+  INDEX_CONFIG,
+  DEFAULT_SEARCH_PARAM_VALUE_MAP,
+} from '../../consts/Milvus';
+
+const Preview: FC<{
+  collectionName: string;
+}> = ({ collectionName }) => {
+  const [fields, setFields] = useState<any[]>([]);
+  const [tableLoading, setTableLoading] = useState<any>();
+  const [queryResult, setQueryResult] = useState<any>();
+  const [primaryKey, setPrimaryKey] = useState<string>('');
+  const { t: commonTrans } = useTranslation();
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const copyTrans = commonTrans('copy');
+
+  const classes = getQueryStyles();
+  // Format result list
+  const queryResultMemo = useMemo(
+    () =>
+      queryResult?.map((resultItem: { [key: string]: any }) => {
+        // Iterate resultItem keys, then format vector(array) items.
+        const tmp = Object.keys(resultItem).reduce(
+          (prev: { [key: string]: any }, item: string) => {
+            if (Array.isArray(resultItem[item])) {
+              const list2Str = JSON.stringify(resultItem[item]);
+              prev[item] = (
+                <div className={classes.vectorTableCell}>
+                  <div>{list2Str}</div>
+                  <CopyButton
+                    label={copyTrans.label}
+                    value={list2Str}
+                    className={classes.copyBtn}
+                  />
+                </div>
+              );
+            } else {
+              prev[item] = `${resultItem[item]}`;
+            }
+            return prev;
+          },
+          {}
+        );
+        return tmp;
+      }),
+    [queryResult, classes.vectorTableCell, classes.copyBtn, copyTrans.label]
+  );
+
+  const {
+    pageSize,
+    handlePageSize,
+    currentPage,
+    handleCurrentPage,
+    total,
+    data: result,
+  } = usePaginationHook(queryResultMemo || []);
+
+  const handlePageChange = (e: any, page: number) => {
+    handleCurrentPage(page);
+  };
+
+  const loadData = async (collectionName: string) => {
+    // get schema list
+    const schemaList = await FieldHttp.getFields(collectionName);
+    const nameList = schemaList.map(v => ({
+      name: v.name,
+      type: v.data_type,
+    }));
+
+    const id = schemaList.find(v => v._isPrimaryKey === true);
+    const primaryKey = id?._fieldName || '';
+    const delimiter = id?.data_type === 'Int64' ? '' : '"';
+
+    const vectorField = schemaList.find(
+      v => v.data_type === 'FloatVector' || v.data_type === 'BinaryVector'
+    );
+
+    const anns_field = vectorField?._fieldName!;
+    const dim = Number(vectorField?._dimension);
+    const vectors = [generateVector(dim)];
+    // get search params
+    const indexesInfo = await IndexHttp.getIndexInfo(collectionName);
+    const indexType =
+      indexesInfo.length == 0 ? 'FLAT' : indexesInfo[0]._indexType;
+    const indexConfig = INDEX_CONFIG[indexType];
+    const metric_type =
+      indexesInfo.length === 0 ? 'L2' : indexesInfo[0]._metricType;
+    const searchParamKey = indexConfig.search[0];
+    const searchParamValue = DEFAULT_SEARCH_PARAM_VALUE_MAP[searchParamKey];
+    const searchParam = { [searchParamKey]: searchParamValue };
+    const params = `${JSON.stringify(searchParam)}`;
+    setPrimaryKey(primaryKey);
+    // Temporarily hide bool field due to incorrect return from SDK.
+    const fieldWithoutBool = nameList.filter(
+      i => i.type !== DataTypeStringEnum.Bool
+    );
+
+    // set fields
+    setFields(fieldWithoutBool);
+
+    // set loading
+    setTableLoading(true);
+
+    try {
+      // first, search random data to get random id
+      const searchRes = await CollectionHttp.vectorSearchData(collectionName, {
+        search_params: {
+          topk: 100,
+          anns_field,
+          metric_type,
+          params,
+        },
+        expr: '',
+        vectors,
+        output_fields: [primaryKey],
+        vector_type: Number(vectorField?._fieldId),
+      });
+
+      // compose random id list expression
+      const expr = `${primaryKey} in [${searchRes.results
+        .map((d: any) => `${delimiter}${d.id}${delimiter}`)
+        .join(',')}]`;
+
+      // query by random id
+      const res = await CollectionHttp.queryData(collectionName, {
+        expr: expr,
+        output_fields: fieldWithoutBool.map(i => i.name),
+      });
+
+      const result = res.data;
+      setQueryResult(result);
+    } catch (err) {
+      setQueryResult([]);
+    } finally {
+      setTableLoading(false);
+    }
+  };
+
+  // Get fields at first or collection name changed.
+  useEffect(() => {
+    collectionName && loadData(collectionName);
+  }, [collectionName]);
+
+  const toolbarConfigs: ToolBarConfig[] = [
+    {
+      type: 'iconBtn',
+      onClick: () => {
+        loadData(collectionName);
+      },
+      label: collectionTrans('refresh'),
+      icon: 'refresh',
+    },
+  ];
+
+  return (
+    <div className={classes.root}>
+      <CustomToolBar toolbarConfigs={toolbarConfigs} />
+
+      <AttuGrid
+        toolbarConfigs={[]}
+        colDefinitions={fields.map(i => ({
+          id: i.name,
+          align: 'left',
+          disablePadding: false,
+          label: i.name,
+        }))}
+        primaryKey={primaryKey}
+        openCheckBox={false}
+        isLoading={!!tableLoading}
+        rows={result}
+        rowCount={total}
+        page={currentPage}
+        onChangePage={handlePageChange}
+        rowsPerPage={pageSize}
+        setRowsPerPage={handlePageSize}
+      />
+    </div>
+  );
+};
+
+export default Preview;

+ 2 - 0
client/src/pages/query/Types.ts

@@ -3,4 +3,6 @@ export interface QueryParam {
   partitions_names?: string[];
   partitions_names?: string[];
   output_fields?: string[];
   output_fields?: string[];
   travel_timestamp?: string;
   travel_timestamp?: string;
+  limit?: number;
+  offset?: number;
 }
 }

+ 1 - 39
client/src/pages/schema/IndexTypeElement.tsx

@@ -21,7 +21,6 @@ import icons from '../../components/icons/Icons';
 import { rootContext } from '../../context/Root';
 import { rootContext } from '../../context/Root';
 import CreateIndex from './Create';
 import CreateIndex from './Create';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
-// import CustomLinearProgress from '../../components/customProgress/CustomLinearProgress';
 import StatusIcon from '../../components/status/StatusIcon';
 import StatusIcon from '../../components/status/StatusIcon';
 import { ChildrenStatusType } from '../../components/status/Types';
 import { ChildrenStatusType } from '../../components/status/Types';
 
 
@@ -82,7 +81,7 @@ const IndexTypeElement: FC<{
   cb: (collectionName: string) => void;
   cb: (collectionName: string) => void;
 }> = ({ data, collectionName, cb }) => {
 }> = ({ data, collectionName, cb }) => {
   const classes = useStyles();
   const classes = useStyles();
-  // set empty string as defalut status
+  // set empty string as default status
   const [status, setStatus] = useState<string>(IndexState.Default);
   const [status, setStatus] = useState<string>(IndexState.Default);
 
 
   const { t: indexTrans } = useTranslation('index');
   const { t: indexTrans } = useTranslation('index');
@@ -104,9 +103,6 @@ const IndexTypeElement: FC<{
   );
   );
 
 
   const fetchStatus = useCallback(async () => {
   const fetchStatus = useCallback(async () => {
-    if (timer) {
-      clearTimeout(timer);
-    }
     // prevent delete index trigger fetching index status
     // prevent delete index trigger fetching index status
     if (data._indexType !== '' && status !== IndexState.Delete) {
     if (data._indexType !== '' && status !== IndexState.Delete) {
       timer = setTimeout(async () => {
       timer = setTimeout(async () => {
@@ -124,40 +120,6 @@ const IndexTypeElement: FC<{
     }
     }
   }, [collectionName, data, status]);
   }, [collectionName, data, status]);
 
 
-  // const fetchProgress = useCallback(() => {
-  //   if (timer) {
-  //     clearTimeout(timer);
-  //   }
-  //   if (data._indexType !== '' && isIndexCreating) {
-  //     timer = setTimeout(async () => {
-  //       try {
-  //         const res = await IndexHttp.getIndexBuildProgress(
-  //           collectionName,
-  //           data._fieldName,
-  //           data._indexName
-  //         );
-
-  //         const { indexed_rows, total_rows } = res;
-  //         const percent = Number(indexed_rows) / Number(total_rows);
-  //         const value = Math.floor(percent * 100);
-  //         setCreateProgress(value);
-
-  //         if (value !== 100) {
-  //           fetchProgress();
-  //         } else {
-  //           timer && clearTimeout(timer);
-  //           // reset build progress
-  //           setCreateProgress(0);
-  //           // change index create status
-  //           setStatus(IndexState.Finished);
-  //         }
-  //       } catch (error) {
-  //         setStatus(IndexState.Finished);
-  //       }
-  //     }, 500);
-  //   }
-  // }, [collectionName, data, isIndexCreating]);
-
   useEffect(() => {
   useEffect(() => {
     fetchStatus();
     fetchStatus();
   }, [fetchStatus]);
   }, [fetchStatus]);

+ 9 - 9
client/src/pages/user/User.tsx

@@ -1,24 +1,24 @@
-import { UserHttp } from '../../http/User';
 import React, { useContext, useEffect, useState } from 'react';
 import React, { useContext, useEffect, useState } from 'react';
-import AttuGrid from 'insight_src/components/grid/Grid';
+import { makeStyles, Theme } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import { UserHttp } from '../../http/User';
+import AttuGrid from '../../components/grid/Grid';
 import {
 import {
   ColDefinitionsType,
   ColDefinitionsType,
   ToolBarConfig,
   ToolBarConfig,
-} from 'insight_src/components/grid/Types';
-import { makeStyles, Theme } from '@material-ui/core';
+} from '../../components/grid/Types';
 import {
 import {
   CreateUserParams,
   CreateUserParams,
   DeleteUserParams,
   DeleteUserParams,
   UpdateUserParams,
   UpdateUserParams,
   UserData,
   UserData,
 } from './Types';
 } from './Types';
-import { rootContext } from 'insight_src/context/Root';
+import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
+import { rootContext } from '../../context/Root';
+import { useNavigationHook } from '../../hooks/Navigation';
+import { ALL_ROUTER_TYPES } from '../../router/Types';
 import CreateUser from './Create';
 import CreateUser from './Create';
-import { useTranslation } from 'react-i18next';
-import DeleteTemplate from 'insight_src/components/customDialog/DeleteDialogTemplate';
 import UpdateUser from './Update';
 import UpdateUser from './Update';
-import { useNavigationHook } from 'insight_src/hooks/Navigation';
-import { ALL_ROUTER_TYPES } from 'insight_src/router/Types';
 
 
 const useStyles = makeStyles((theme: Theme) => ({
 const useStyles = makeStyles((theme: Theme) => ({
   actionButton: {
   actionButton: {

+ 1 - 1
client/src/plugins/search/config.json

@@ -3,7 +3,7 @@
   "version": "0.1.0",
   "version": "0.1.0",
   "client": {
   "client": {
     "path": "search",
     "path": "search",
-    "entry": "VectorSearch.tsx",
+    "entry": "VectorSearch",
     "label": "Vector Search",
     "label": "Vector Search",
     "iconName": "navSearch",
     "iconName": "navSearch",
     "auth": true,
     "auth": true,

+ 1 - 1
client/src/plugins/system/config.json

@@ -3,7 +3,7 @@
   "version": "0.1.0",
   "version": "0.1.0",
   "client": {
   "client": {
     "path": "system",
     "path": "system",
-    "entry": "SystemView.tsx",
+    "entry": "SystemView",
     "label": "System View",
     "label": "System View",
     "iconName": "navSystem",
     "iconName": "navSystem",
     "auth": true
     "auth": true

+ 11 - 12
client/src/router/Config.ts

@@ -7,8 +7,6 @@ import { RouterConfigType } from './Types';
 import loadable from '@loadable/component';
 import loadable from '@loadable/component';
 import Users from '../pages/user/User';
 import Users from '../pages/user/User';
 
 
-const PLUGIN_DEV = process.env.REACT_APP_PLUGIN_DEV;
-
 const RouterConfig: RouterConfigType[] = [
 const RouterConfig: RouterConfigType[] = [
   {
   {
     path: '/',
     path: '/',
@@ -33,18 +31,17 @@ const RouterConfig: RouterConfigType[] = [
   { path: '/users', component: Users, auth: true },
   { path: '/users', component: Users, auth: true },
 ];
 ];
 
 
-function importAll(r: any, outOfRoot = false) {
-  r.keys().forEach((key: any) => {
-    const content = r(key);
+async function importAll(r: any) {
+  Object.keys(r).forEach((key: any) => {
+    const content = r[key];
     const dirName = key.split('/config.json').shift().split('/')[1];
     const dirName = key.split('/config.json').shift().split('/')[1];
     const pathName = content.client?.path;
     const pathName = content.client?.path;
     const fileEntry = content.client?.entry;
     const fileEntry = content.client?.entry;
     if (!pathName || !fileEntry) return;
     if (!pathName || !fileEntry) return;
-    // console.log(content);
     const auth = content.client?.auth || false;
     const auth = content.client?.auth || false;
-    const OtherComponent = outOfRoot
-      ? loadable(() => import(`all_plugins/${dirName}/client/${fileEntry}`))
-      : loadable(() => import(`../plugins/${dirName}/${fileEntry}`));
+    const OtherComponent = loadable(
+      () => import(`../${dirName}/${pathName}/${fileEntry}.tsx`)
+    );
     RouterConfig.push({
     RouterConfig.push({
       path: `/${pathName}`,
       path: `/${pathName}`,
       component: OtherComponent,
       component: OtherComponent,
@@ -52,8 +49,10 @@ function importAll(r: any, outOfRoot = false) {
     });
     });
   });
   });
 }
 }
-importAll(require.context('../plugins/', true, /config\.json$/));
-PLUGIN_DEV &&
-  importAll(require.context('all_plugins/', true, /config\.json$/), true);
+
+const pluginConfigs = import.meta.glob(`../plugins/**/config.json`, {
+  eager: true,
+});
+importAll(pluginConfigs);
 
 
 export default RouterConfig;
 export default RouterConfig;

+ 4 - 0
client/src/utils/Common.ts

@@ -61,3 +61,7 @@ export const generateHashCode = (source: string) => {
 export const generateIdByHash = (salt?: string) => {
 export const generateIdByHash = (salt?: string) => {
   return generateHashCode(`${new Date().getTime().toString()}-${salt}`);
   return generateHashCode(`${new Date().getTime().toString()}-${salt}`);
 };
 };
+
+export const generateVector = (dim: number) => {
+  return Array.from({ length: dim }).map(() => Math.random());
+};

+ 2 - 2
client/src/utils/__test__/Validation.spec.ts

@@ -45,8 +45,8 @@ describe('Test validation utils', () => {
     expect(
     expect(
       getCheckResult({
       getCheckResult({
         value: '12345',
         value: '12345',
-        extraParam: { lenMin: 8 },
-        rule: 'length',
+        extraParam: { min: 8 },
+        rule: 'range',
       })
       })
     ).toBeFalsy();
     ).toBeFalsy();
   });
   });

+ 9 - 11
client/tsconfig.json

@@ -1,11 +1,14 @@
 {
 {
   "compilerOptions": {
   "compilerOptions": {
-    "target": "es5",
-    "lib": [
-      "dom",
-      "dom.iterable",
-      "esnext"
+    "target": "ESNext",
+    "lib": ["dom", "dom.iterable"],
+    "types": [
+      "vite/client",
+      "vite-plugin-svgr/client",
+      "jest",
+      "@testing-library/jest-dom"
     ],
     ],
+
     "allowJs": true,
     "allowJs": true,
     "skipLibCheck": true,
     "skipLibCheck": true,
     "esModuleInterop": true,
     "esModuleInterop": true,
@@ -20,10 +23,5 @@
     "noEmit": true,
     "noEmit": true,
     "jsx": "react-jsx"
     "jsx": "react-jsx"
   },
   },
-  "include": [
-    "src",
-    "../../src",
-    "all_plugins"
-  ],
-  "extends": "./tsconfig.paths.json"
+  "include": ["src", "../../src"]
 }
 }

+ 0 - 9
client/tsconfig.paths.json

@@ -1,9 +0,0 @@
-{
-  "compilerOptions": {
-    "baseUrl": ".",
-    "paths": {
-      "all_plugins/*": ["src/plugins"],
-      "insight_src/*": ["src/*"]
-    }
-  }
-}

+ 25 - 0
client/vite.config.ts

@@ -0,0 +1,25 @@
+import { defineConfig } from 'vite';
+import reactRefresh from '@vitejs/plugin-react';
+const svgrPlugin = require('vite-plugin-svgr');
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  base: '',
+  // This changes the out put dir from dist to build
+  // comment this out if that isn't relevant for your project
+  build: {
+    outDir: 'build',
+  },
+  server: {
+    port: 3001,
+  },
+  plugins: [
+    reactRefresh(),
+    svgrPlugin({
+      svgrOptions: {
+        icon: true,
+        // ...svgr options (https://react-svgr.com/docs/options/)
+      },
+    }),
+  ],
+});

+ 19 - 0
client/vitest.config.ts

@@ -0,0 +1,19 @@
+/// <reference types="vitest" />
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import * as path from 'path';
+
+export default defineConfig({
+  plugins: [react()],
+  test: {
+    // include: ["src/**.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
+    environment: 'jsdom',
+    globals: true,
+    setupFiles: ['src/setupTests.ts'],
+  },
+  resolve: {
+    alias: {
+      '@': path.resolve(__dirname, './src'),
+    },
+  },
+});

File diff suppressed because it is too large
+ 2178 - 8873
client/yarn.lock


+ 0 - 35
server/generate-csv.ts

@@ -1,35 +0,0 @@
-import { createObjectCsvWriter as createCsvWriter } from 'csv-writer';
-
-// use to test vector insert
-const csvWriter = createCsvWriter({
-  path: './vectors.csv',
-  header: [{ id: 'vector', title: 'vector' }],
-});
-
-const records: any[] = [];
-
-const generateVector = (dimension: number) => {
-  let index = 0;
-  const vectors: any[] = [];
-  while (index < dimension) {
-    vectors.push(1 + Math.random());
-    index++;
-  }
-  return JSON.stringify(vectors);
-};
-
-while (records.length < 1000) {
-  const value = generateVector(960);
-  records.push({
-    vector: value,
-    // name: `${records.length}_id`,
-    // age: records.length * 2,
-    // job: Math.random() * 1000 > 500 ? 'designer' : 'programer',
-  });
-}
-
-csvWriter
-  .writeRecords(records) // returns a promise
-  .then(() => {
-    console.log('...Done');
-  });

+ 4 - 5
server/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "attu",
   "name": "attu",
-  "version": "2.1.1",
+  "version": "2.1.4",
   "license": "MIT",
   "license": "MIT",
   "author": {
   "author": {
     "name": "zilliz",
     "name": "zilliz",
@@ -63,8 +63,8 @@
     "@types/swagger-jsdoc": "^6.0.1",
     "@types/swagger-jsdoc": "^6.0.1",
     "@types/swagger-ui-express": "^4.1.3",
     "@types/swagger-ui-express": "^4.1.3",
     "@types/ws": "^8.2.0",
     "@types/ws": "^8.2.0",
-    "electron": "^16.0.2",
-    "electron-builder": "^22.14.5",
+    "electron": "^21.0.1",
+    "electron-builder": "^23.6.0",
     "jest": "^27.3.1",
     "jest": "^27.3.1",
     "nodemon": "^2.0.14",
     "nodemon": "^2.0.14",
     "prettier": "^2.4.1",
     "prettier": "^2.4.1",
@@ -124,8 +124,7 @@
     "linux": {
     "linux": {
       "icon": "./build/attu.icns",
       "icon": "./build/attu.icns",
       "target": [
       "target": [
-        "deb",
-        "rpm"
+        "deb"
       ]
       ]
     },
     },
     "mac": {
     "mac": {

+ 4 - 4
server/src/__tests__/__mocks__/consts.ts

@@ -17,7 +17,7 @@ export const mockCollections = [
       fields: [
       fields: [
         {
         {
           name: 'vector_field',
           name: 'vector_field',
-          data_type: 'data_type',
+          data_type: 'FloatVector',
           type_params: [
           type_params: [
             {
             {
               key: 'dim',
               key: 'dim',
@@ -44,7 +44,7 @@ export const mockCollections = [
       fields: [
       fields: [
         {
         {
           name: 'vector_field',
           name: 'vector_field',
-          data_type: 'data_type',
+          data_type: 'FloatVector',
           type_params: [
           type_params: [
             {
             {
               key: 'dim',
               key: 'dim',
@@ -91,7 +91,7 @@ export const mockGetAllCollectionsData = [
       fields: [
       fields: [
         {
         {
           name: 'vector_field',
           name: 'vector_field',
-          data_type: 'data_type',
+          data_type: 'FloatVector',
           type_params: [
           type_params: [
             {
             {
               key: 'dim',
               key: 'dim',
@@ -121,7 +121,7 @@ export const mockGetAllCollectionsData = [
       fields: [
       fields: [
         {
         {
           name: 'vector_field',
           name: 'vector_field',
-          data_type: 'data_type',
+          data_type: 'FloatVector',
           type_params: [
           type_params: [
             {
             {
               key: 'dim',
               key: 'dim',

+ 18 - 0
server/src/__tests__/collections/collections.service.test.ts

@@ -171,6 +171,24 @@ describe('Test collections service', () => {
     }
     }
   });
   });
 
 
+  test('test importSample method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      size: 2
+    };
+    const res = await service.importSample(mockParam);
+    expect(res.data.fields_data.length).toEqual(2);
+
+    try {
+      await service.importSample({
+        collection_name: '',
+        size: 20
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
   test('test vectorSearch method', async () => {
   test('test vectorSearch method', async () => {
     const mockParam = {
     const mockParam = {
       collection_name: 'c1',
       collection_name: 'c1',

+ 1 - 1
server/src/app.ts

@@ -143,6 +143,6 @@ getDirectories(SRC_PLUGIN_DIR, async (dirErr: Error, dirRes: string[]) => {
   app.use(ErrorMiddleware);
   app.use(ErrorMiddleware);
   // start server
   // start server
   server.listen(PORT, () => {
   server.listen(PORT, () => {
-    console.log(chalk.green.bold(`Insight Server started on port ${PORT} :)`));
+    console.log(chalk.green.bold(`Attu Server started on port ${PORT} :)`));
   });
   });
 });
 });

+ 18 - 0
server/src/collections/collections.controller.ts

@@ -6,6 +6,7 @@ import {
   CreateAliasDto,
   CreateAliasDto,
   CreateCollectionDto,
   CreateCollectionDto,
   InsertDataDto,
   InsertDataDto,
+  ImportSampleDto,
   VectorSearchDto,
   VectorSearchDto,
   QueryDto,
   QueryDto,
 } from './dto';
 } from './dto';
@@ -59,6 +60,12 @@ export class CollectionController {
       this.insert.bind(this)
       this.insert.bind(this)
     );
     );
 
 
+    this.router.post(
+      '/:name/importSample',
+      dtoValidationMiddleware(ImportSampleDto),
+      this.importSample.bind(this)
+    );
+
     // we need use req.body, so we can't use delete here
     // we need use req.body, so we can't use delete here
     this.router.put('/:name/entities', this.deleteEntities.bind(this));
     this.router.put('/:name/entities', this.deleteEntities.bind(this));
 
 
@@ -208,6 +215,17 @@ export class CollectionController {
     }
     }
   }
   }
 
 
+  async importSample(req: Request, res: Response, next: NextFunction) {
+    const data = req.body;
+    try {
+      const result = await this.collectionsService.importSample({
+        ...data,
+      });
+      res.send(result);
+    } catch (error) {
+      next(error);
+    }
+  }
   async deleteEntities(req: Request, res: Response, next: NextFunction) {
   async deleteEntities(req: Request, res: Response, next: NextFunction) {
     const name = req.params?.name;
     const name = req.params?.name;
     const data = req.body;
     const data = req.body;

+ 15 - 2
server/src/collections/collections.service.ts

@@ -11,7 +11,7 @@ import {
   SearchReq,
   SearchReq,
 } from '@zilliz/milvus2-sdk-node/dist/milvus/types';
 } from '@zilliz/milvus2-sdk-node/dist/milvus/types';
 import { throwErrorFromSDK } from '../utils/Error';
 import { throwErrorFromSDK } from '../utils/Error';
-import { findKeyValue } from '../utils/Helper';
+import { findKeyValue, genRows } from '../utils/Helper';
 import { ROW_COUNT } from '../utils/Const';
 import { ROW_COUNT } from '../utils/Const';
 import {
 import {
   AlterAliasReq,
   AlterAliasReq,
@@ -20,7 +20,7 @@ import {
   ShowCollectionsReq,
   ShowCollectionsReq,
   ShowCollectionsType,
   ShowCollectionsType,
 } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Collection';
 } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Collection';
-import { QueryDto } from './dto';
+import { QueryDto, ImportSampleDto } from './dto';
 import { DeleteEntitiesReq } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Data';
 import { DeleteEntitiesReq } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Data';
 
 
 export class CollectionsService {
 export class CollectionsService {
@@ -258,4 +258,17 @@ export class CollectionsService {
     }
     }
     return data;
     return data;
   }
   }
+
+  /**
+   * Load sample data into collection
+   */
+  async importSample({ collection_name, size }: ImportSampleDto) {
+    const collectionInfo = await this.describeCollection({ collection_name });
+    const fields_data = genRows(
+      collectionInfo.schema.fields,
+      parseInt(size, 10)
+    );
+
+    return await this.insert({ collection_name, fields_data });
+  }
 }
 }

+ 11 - 6
server/src/collections/dto.ts

@@ -8,13 +8,13 @@ import {
   IsEnum,
   IsEnum,
   ArrayMinSize,
   ArrayMinSize,
   IsObject,
   IsObject,
-} from "class-validator";
+} from 'class-validator';
 import {
 import {
   FieldType,
   FieldType,
   ShowCollectionsType,
   ShowCollectionsType,
-} from "@zilliz/milvus2-sdk-node/dist/milvus/types/Collection";
-import { DataType } from "@zilliz/milvus2-sdk-node/dist/milvus/types/Common";
-import { SearchParam } from "@zilliz/milvus2-sdk-node/dist/milvus/types";
+} from '@zilliz/milvus2-sdk-node/dist/milvus/types/Collection';
+import { DataType } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Common';
+import { SearchParam } from '@zilliz/milvus2-sdk-node/dist/milvus/types';
 
 
 enum VectorTypes {
 enum VectorTypes {
   Binary = DataType.BinaryVector,
   Binary = DataType.BinaryVector,
@@ -36,7 +36,7 @@ export class CreateCollectionDto {
 
 
 export class ShowCollectionsDto {
 export class ShowCollectionsDto {
   @IsOptional()
   @IsOptional()
-  @IsEnum(ShowCollectionsType, { message: "Type allow all->0 inmemory->1" })
+  @IsEnum(ShowCollectionsType, { message: 'Type allow all->0 inmemory->1' })
   readonly type: ShowCollectionsType;
   readonly type: ShowCollectionsType;
 }
 }
 
 
@@ -48,6 +48,11 @@ export class InsertDataDto {
   readonly fields_data: any[];
   readonly fields_data: any[];
 }
 }
 
 
+export class ImportSampleDto {
+  readonly collection_name?: string;
+  readonly size: string;
+}
+
 export class VectorSearchDto {
 export class VectorSearchDto {
   @IsOptional()
   @IsOptional()
   partition_names?: string[];
   partition_names?: string[];
@@ -67,7 +72,7 @@ export class VectorSearchDto {
   @IsOptional()
   @IsOptional()
   output_fields?: string[];
   output_fields?: string[];
 
 
-  @IsEnum(VectorTypes, { message: "Type allow all->0 inmemory->1" })
+  @IsEnum(VectorTypes, { message: 'Type allow all->0 inmemory->1' })
   vector_type: DataType.BinaryVector | DataType.FloatVector;
   vector_type: DataType.BinaryVector | DataType.FloatVector;
 }
 }
 
 

+ 1 - 1
server/src/swagger.ts

@@ -10,7 +10,7 @@ export const surveSwaggerSpecification = () => {
     definition: {
     definition: {
       openapi: "3.0.0",
       openapi: "3.0.0",
       info: {
       info: {
-        title: "Insight server",
+        title: "Attu server",
         version: "1.0.0",
         version: "1.0.0",
       },
       },
       servers: [{ url: "/api/v1" }],
       servers: [{ url: "/api/v1" }],

+ 52 - 1
server/src/utils/Helper.ts

@@ -1,4 +1,55 @@
 import { KeyValuePair } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Common';
 import { KeyValuePair } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Common';
+import { FieldSchema } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Response';
 
 
 export const findKeyValue = (obj: KeyValuePair[], key: string) =>
 export const findKeyValue = (obj: KeyValuePair[], key: string) =>
-  obj.find((v) => v.key === key)?.value;
+  obj.find(v => v.key === key)?.value;
+
+export const genDataByType = ({ data_type, type_params }: FieldSchema) => {
+  switch (data_type) {
+    case 'Bool':
+      return Math.random() > 0.5;
+    case 'Int8':
+      return Math.floor(Math.random() * 127);
+    case 'Int16':
+      return Math.floor(Math.random() * 32767);
+    case 'Int32':
+      return Math.floor(Math.random() * 214748364);
+    case 'Int64':
+      return Math.floor(Math.random() * 214748364);
+    case 'FloatVector':
+      return Array.from({ length: (type_params as any)[0].value }).map(() =>
+        Math.random()
+      );
+    case 'VarChar':
+      return makeRandomId((type_params as any)[0].value);
+  }
+};
+
+export const genRow = (fields: FieldSchema[]) => {
+  const result: any = {};
+  fields.forEach(field => {
+    if (!field.autoID) {
+      result[field.name] = genDataByType(field);
+    }
+  });
+  return result;
+};
+
+export const genRows = (fields: FieldSchema[], size: number) => {
+  const result = [];
+  for (let i = 0; i < size; i++) {
+    result[i] = genRow(fields);
+  }
+  return result;
+};
+
+export const makeRandomId = (length: number): string => {
+  let result = '';
+  const characters =
+    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+  const charactersLength = characters.length;
+  for (let i = 0; i < length; i++) {
+    result += characters.charAt(Math.floor(Math.random() * charactersLength));
+  }
+  return result;
+};

+ 262 - 185
server/yarn.lock

@@ -375,10 +375,10 @@
     ajv "^6.12.0"
     ajv "^6.12.0"
     ajv-keywords "^3.4.1"
     ajv-keywords "^3.4.1"
 
 
-"@electron/get@^1.13.0":
-  version "1.13.1"
-  resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.1.tgz#42a0aa62fd1189638bd966e23effaebb16108368"
-  integrity sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==
+"@electron/get@^1.14.1":
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
+  integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
   dependencies:
   dependencies:
     debug "^4.1.1"
     debug "^4.1.1"
     env-paths "^2.2.0"
     env-paths "^2.2.0"
@@ -391,16 +391,18 @@
     global-agent "^3.0.0"
     global-agent "^3.0.0"
     global-tunnel-ng "^2.7.1"
     global-tunnel-ng "^2.7.1"
 
 
-"@electron/universal@1.0.5":
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.0.5.tgz#b812340e4ef21da2b3ee77b2b4d35c9b86defe37"
-  integrity sha512-zX9O6+jr2NMyAdSkwEUlyltiI4/EBLu2Ls/VD3pUQdi3cAYeYfdQnT2AJJ38HE4QxLccbU13LSpccw1IWlkyag==
+"@electron/universal@1.2.1":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339"
+  integrity sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ==
   dependencies:
   dependencies:
     "@malept/cross-spawn-promise" "^1.1.0"
     "@malept/cross-spawn-promise" "^1.1.0"
-    asar "^3.0.3"
+    asar "^3.1.0"
     debug "^4.3.1"
     debug "^4.3.1"
     dir-compare "^2.4.0"
     dir-compare "^2.4.0"
     fs-extra "^9.0.1"
     fs-extra "^9.0.1"
+    minimatch "^3.0.4"
+    plist "^3.0.4"
 
 
 "@grpc/grpc-js@^1.2.12":
 "@grpc/grpc-js@^1.2.12":
   version "1.4.2"
   version "1.4.2"
@@ -800,6 +802,11 @@
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
   integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
   integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
 
 
+"@tootallnate/once@2":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+  integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+
 "@tsconfig/node10@^1.0.7":
 "@tsconfig/node10@^1.0.7":
   version "1.0.8"
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
   resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
@@ -1032,10 +1039,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c"
   integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==
   integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==
 
 
-"@types/node@^14.6.2":
-  version "14.17.34"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.34.tgz#fe4b38b3f07617c0fa31ae923fca9249641038f0"
-  integrity sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg==
+"@types/node@^16.11.26":
+  version "16.18.3"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
+  integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
 
 
 "@types/plist@^3.0.1":
 "@types/plist@^3.0.1":
   version "3.0.2"
   version "3.0.2"
@@ -1137,6 +1144,13 @@
   dependencies:
   dependencies:
     "@types/yargs-parser" "*"
     "@types/yargs-parser" "*"
 
 
+"@types/yauzl@^2.9.1":
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
+  integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
+  dependencies:
+    "@types/node" "*"
+
 "@zilliz/milvus2-sdk-node@^2.1.3":
 "@zilliz/milvus2-sdk-node@^2.1.3":
   version "2.1.3"
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.1.3.tgz#f8f54e1385f67edc4485ba3724b8b20f45537303"
   resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.1.3.tgz#f8f54e1385f67edc4485ba3724b8b20f45537303"
@@ -1264,40 +1278,41 @@ anymatch@^3.0.3, anymatch@~3.1.2:
     normalize-path "^3.0.0"
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
     picomatch "^2.0.4"
 
 
-app-builder-bin@3.7.1:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.7.1.tgz#cb0825c5e12efc85b196ac3ed9c89f076c61040e"
-  integrity sha512-ql93vEUq6WsstGXD+SBLSIQw6SNnhbDEM0swzgugytMxLp3rT24Ag/jcC80ZHxiPRTdew1niuR7P3/FCrDqIjw==
+app-builder-bin@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0"
+  integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==
 
 
-app-builder-lib@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.14.5.tgz#a61a50b132b858e98fdc70b6b88994ae99b4f96d"
-  integrity sha512-k3VwKP4kpsnUaXoUkm1s4zaSHPHIMFnN4kPMU9yXaKmE1LfHHqBaEah5bXeTAX5V/BC41wFdg8CF5vOjvgy8Rg==
+app-builder-lib@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-23.6.0.tgz#03cade02838c077db99d86212d61c5fc1d6da1a8"
+  integrity sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA==
   dependencies:
   dependencies:
     "7zip-bin" "~5.1.1"
     "7zip-bin" "~5.1.1"
     "@develar/schema-utils" "~2.6.5"
     "@develar/schema-utils" "~2.6.5"
-    "@electron/universal" "1.0.5"
+    "@electron/universal" "1.2.1"
     "@malept/flatpak-bundler" "^0.4.0"
     "@malept/flatpak-bundler" "^0.4.0"
     async-exit-hook "^2.0.1"
     async-exit-hook "^2.0.1"
     bluebird-lst "^1.0.9"
     bluebird-lst "^1.0.9"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     chromium-pickle-js "^0.2.0"
     chromium-pickle-js "^0.2.0"
-    debug "^4.3.2"
-    ejs "^3.1.6"
-    electron-osx-sign "^0.5.0"
-    electron-publish "22.14.5"
+    debug "^4.3.4"
+    ejs "^3.1.7"
+    electron-osx-sign "^0.6.0"
+    electron-publish "23.6.0"
     form-data "^4.0.0"
     form-data "^4.0.0"
-    fs-extra "^10.0.0"
-    hosted-git-info "^4.0.2"
+    fs-extra "^10.1.0"
+    hosted-git-info "^4.1.0"
     is-ci "^3.0.0"
     is-ci "^3.0.0"
-    isbinaryfile "^4.0.8"
+    isbinaryfile "^4.0.10"
     js-yaml "^4.1.0"
     js-yaml "^4.1.0"
     lazy-val "^1.0.5"
     lazy-val "^1.0.5"
-    minimatch "^3.0.4"
+    minimatch "^3.1.2"
     read-config-file "6.2.0"
     read-config-file "6.2.0"
     sanitize-filename "^1.6.3"
     sanitize-filename "^1.6.3"
-    semver "^7.3.5"
+    semver "^7.3.7"
+    tar "^6.1.11"
     temp-file "^3.4.0"
     temp-file "^3.4.0"
 
 
 arg@^4.1.0:
 arg@^4.1.0:
@@ -1322,10 +1337,10 @@ array-flatten@1.1.1:
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
 
 
-asar@^3.0.3:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473"
-  integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==
+asar@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221"
+  integrity sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==
   dependencies:
   dependencies:
     chromium-pickle-js "^0.2.0"
     chromium-pickle-js "^0.2.0"
     commander "^5.0.0"
     commander "^5.0.0"
@@ -1349,10 +1364,10 @@ async-exit-hook@^2.0.1:
   resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3"
   resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3"
   integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==
   integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==
 
 
-async@0.9.x:
-  version "0.9.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
-  integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
+async@^3.2.3:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
+  integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
 
 
 asynckit@^0.4.0:
 asynckit@^0.4.0:
   version "0.4.0"
   version "0.4.0"
@@ -1590,29 +1605,31 @@ buffer@^5.1.0:
     base64-js "^1.3.1"
     base64-js "^1.3.1"
     ieee754 "^1.1.13"
     ieee754 "^1.1.13"
 
 
-builder-util-runtime@8.9.1:
-  version "8.9.1"
-  resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz#25f066b3fbc20b3e6236a9b956b1ebb0e33ff66a"
-  integrity sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==
+builder-util-runtime@9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz#2da7b34e78a64ad14ccd070d6eed4662d893bd60"
+  integrity sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==
   dependencies:
   dependencies:
-    debug "^4.3.2"
+    debug "^4.3.4"
     sax "^1.2.4"
     sax "^1.2.4"
 
 
-builder-util@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.14.5.tgz#42a18608d2a566c0846e91266464776c8bfb0cc9"
-  integrity sha512-zqIHDFJwmA7jV7SC9aI+33MWwT2mWoijH+Ol9IntNAwuuRXoS+7XeJwnhLBXOhcDBzXT4kDzHnRk4JKeaygEYA==
+builder-util@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-23.6.0.tgz#1880ec6da7da3fd6fa19b8bd71df7f39e8d17dd9"
+  integrity sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ==
   dependencies:
   dependencies:
     "7zip-bin" "~5.1.1"
     "7zip-bin" "~5.1.1"
     "@types/debug" "^4.1.6"
     "@types/debug" "^4.1.6"
     "@types/fs-extra" "^9.0.11"
     "@types/fs-extra" "^9.0.11"
-    app-builder-bin "3.7.1"
+    app-builder-bin "4.0.0"
     bluebird-lst "^1.0.9"
     bluebird-lst "^1.0.9"
-    builder-util-runtime "8.9.1"
+    builder-util-runtime "9.1.1"
     chalk "^4.1.1"
     chalk "^4.1.1"
     cross-spawn "^7.0.3"
     cross-spawn "^7.0.3"
-    debug "^4.3.2"
+    debug "^4.3.4"
     fs-extra "^10.0.0"
     fs-extra "^10.0.0"
+    http-proxy-agent "^5.0.0"
+    https-proxy-agent "^5.0.0"
     is-ci "^3.0.0"
     is-ci "^3.0.0"
     js-yaml "^4.1.0"
     js-yaml "^4.1.0"
     source-map-support "^0.5.19"
     source-map-support "^0.5.19"
@@ -1675,7 +1692,7 @@ caniuse-lite@^1.0.30001274:
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz#eb06818da481ef5096a3b3760f43e5382ed6b0ce"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz#eb06818da481ef5096a3b3760f43e5382ed6b0ce"
   integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==
   integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==
 
 
-chalk@*, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
+chalk@*, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
   version "4.1.2"
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
   integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
   integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -1683,7 +1700,7 @@ chalk@*, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
     ansi-styles "^4.1.0"
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
     supports-color "^7.1.0"
 
 
-chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.3.0:
   version "2.4.2"
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1712,6 +1729,11 @@ chokidar@^3.2.2:
   optionalDependencies:
   optionalDependencies:
     fsevents "~2.3.2"
     fsevents "~2.3.2"
 
 
+chownr@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+  integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+
 chromium-pickle-js@^0.2.0:
 chromium-pickle-js@^0.2.0:
   version "0.2.0"
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205"
   resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205"
@@ -1775,6 +1797,15 @@ cliui@^7.0.2:
     strip-ansi "^6.0.0"
     strip-ansi "^6.0.0"
     wrap-ansi "^7.0.0"
     wrap-ansi "^7.0.0"
 
 
+cliui@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
+  integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+  dependencies:
+    string-width "^4.2.0"
+    strip-ansi "^6.0.1"
+    wrap-ansi "^7.0.0"
+
 clone-response@^1.0.2:
 clone-response@^1.0.2:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
   resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
@@ -1870,16 +1901,6 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
 
-concat-stream@^1.6.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
-  integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
-  dependencies:
-    buffer-from "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
 config-chain@^1.1.11:
 config-chain@^1.1.11:
   version "1.1.13"
   version "1.1.13"
   resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
   resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
@@ -1944,11 +1965,6 @@ core-util-is@1.0.2:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
 
-core-util-is@~1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
-  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
-
 cors@^2.8.5, cors@~2.8.5:
 cors@^2.8.5, cors@~2.8.5:
   version "2.8.5"
   version "2.8.5"
   resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
   resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
@@ -2021,7 +2037,7 @@ data-urls@^2.0.0:
     whatwg-mimetype "^2.3.0"
     whatwg-mimetype "^2.3.0"
     whatwg-url "^8.0.0"
     whatwg-url "^8.0.0"
 
 
-debug@2.6.9, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.6.8:
   version "2.6.9"
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -2042,13 +2058,20 @@ debug@^3.2.6:
   dependencies:
   dependencies:
     ms "^2.1.1"
     ms "^2.1.1"
 
 
-debug@^4.3.1, debug@^4.3.2:
+debug@^4.3.1:
   version "4.3.3"
   version "4.3.3"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
   integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
   integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
   dependencies:
   dependencies:
     ms "2.1.2"
     ms "2.1.2"
 
 
+debug@^4.3.4:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+  dependencies:
+    ms "2.1.2"
+
 decimal.js@^10.2.1:
 decimal.js@^10.2.1:
   version "10.3.1"
   version "10.3.1"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
@@ -2143,24 +2166,24 @@ dir-compare@^2.4.0:
     commander "2.9.0"
     commander "2.9.0"
     minimatch "3.0.4"
     minimatch "3.0.4"
 
 
-dmg-builder@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.14.5.tgz#137c0b55e639badcc0b119eb060e6fa4ed61d948"
-  integrity sha512-1GvFGQE332bvPamcMwZDqWqfWfJTyyDLOsHMcGi0zs+Jh7JOn6/zuBkHJIWHdsj2QJbhzLVyd2/ZqttOKv7I8w==
+dmg-builder@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-23.6.0.tgz#d39d3871bce996f16c07d2cafe922d6ecbb2a948"
+  integrity sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA==
   dependencies:
   dependencies:
-    app-builder-lib "22.14.5"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    app-builder-lib "23.6.0"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     fs-extra "^10.0.0"
     fs-extra "^10.0.0"
     iconv-lite "^0.6.2"
     iconv-lite "^0.6.2"
     js-yaml "^4.1.0"
     js-yaml "^4.1.0"
   optionalDependencies:
   optionalDependencies:
-    dmg-license "^1.0.9"
+    dmg-license "^1.0.11"
 
 
-dmg-license@^1.0.9:
-  version "1.0.10"
-  resolved "https://registry.yarnpkg.com/dmg-license/-/dmg-license-1.0.10.tgz#89f52afae25d827fce8d818c13aff30af1c16bcc"
-  integrity sha512-SVeeyiOeinV5JCPHXMdKOgK1YVbak/4+8WL2rBnfqRYpA5FaeFaQnQWb25x628am1w70CbipGDv9S51biph63A==
+dmg-license@^1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/dmg-license/-/dmg-license-1.0.11.tgz#7b3bc3745d1b52be7506b4ee80cb61df6e4cd79a"
+  integrity sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==
   dependencies:
   dependencies:
     "@types/plist" "^3.0.1"
     "@types/plist" "^3.0.1"
     "@types/verror" "^1.10.3"
     "@types/verror" "^1.10.3"
@@ -2212,35 +2235,35 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
 
-ejs@^3.1.6:
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
-  integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
+ejs@^3.1.7:
+  version "3.1.8"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
+  integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==
   dependencies:
   dependencies:
-    jake "^10.6.1"
+    jake "^10.8.5"
 
 
-electron-builder@^22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.14.5.tgz#3a25547bd4fe3728d4704da80956a794c5c31496"
-  integrity sha512-N73hSbXFz6Mz5Z6h6C5ly6CB+dUN6k1LuCDJjI8VF47bMXv/QE0HE+Kkb0GPKqTqM7Hsk/yIYX+kHCfSkR5FGg==
+electron-builder@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-23.6.0.tgz#c79050cbdce90ed96c5feb67c34e9e0a21b5331b"
+  integrity sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw==
   dependencies:
   dependencies:
     "@types/yargs" "^17.0.1"
     "@types/yargs" "^17.0.1"
-    app-builder-lib "22.14.5"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    app-builder-lib "23.6.0"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     chalk "^4.1.1"
     chalk "^4.1.1"
-    dmg-builder "22.14.5"
+    dmg-builder "23.6.0"
     fs-extra "^10.0.0"
     fs-extra "^10.0.0"
     is-ci "^3.0.0"
     is-ci "^3.0.0"
     lazy-val "^1.0.5"
     lazy-val "^1.0.5"
     read-config-file "6.2.0"
     read-config-file "6.2.0"
-    update-notifier "^5.1.0"
-    yargs "^17.0.1"
+    simple-update-notifier "^1.0.7"
+    yargs "^17.5.1"
 
 
-electron-osx-sign@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.5.0.tgz#fc258c5e896859904bbe3d01da06902c04b51c3a"
-  integrity sha512-icoRLHzFz/qxzDh/N4Pi2z4yVHurlsCAYQvsCSG7fCedJ4UJXBS6PoQyGH71IfcqKupcKeK7HX/NkyfG+v6vlQ==
+electron-osx-sign@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz#9b69c191d471d9458ef5b1e4fdd52baa059f1bb8"
+  integrity sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==
   dependencies:
   dependencies:
     bluebird "^3.5.0"
     bluebird "^3.5.0"
     compare-version "^0.1.2"
     compare-version "^0.1.2"
@@ -2249,14 +2272,14 @@ electron-osx-sign@^0.5.0:
     minimist "^1.2.0"
     minimist "^1.2.0"
     plist "^3.0.1"
     plist "^3.0.1"
 
 
-electron-publish@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.14.5.tgz#34bcdce671f0e651330db20040d6919c77c94bd6"
-  integrity sha512-h+NANRdaA0PqGF15GKvorseWPzh1PXa/zx4I37//PIokW8eKIov8ky23foUSb55ZFWUHGpxQJux7y2NCfBtQeg==
+electron-publish@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-23.6.0.tgz#ac9b469e0b07752eb89357dd660e5fb10b3d1ce9"
+  integrity sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg==
   dependencies:
   dependencies:
     "@types/fs-extra" "^9.0.11"
     "@types/fs-extra" "^9.0.11"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     chalk "^4.1.1"
     chalk "^4.1.1"
     fs-extra "^10.0.0"
     fs-extra "^10.0.0"
     lazy-val "^1.0.5"
     lazy-val "^1.0.5"
@@ -2267,14 +2290,14 @@ electron-to-chromium@^1.3.886:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.892.tgz#0e3f5bb1de577e2e5a6dffd5a4b278c4a735cd39"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.892.tgz#0e3f5bb1de577e2e5a6dffd5a4b278c4a735cd39"
   integrity sha512-YDW4yIjdfMnbRoBjRZ/aNQYmT6JgQFLwmTSDRJMQdrY4MByEzppdXp3rnJ0g4LBWcsYTUvwKKClYN1ofZ0COOQ==
   integrity sha512-YDW4yIjdfMnbRoBjRZ/aNQYmT6JgQFLwmTSDRJMQdrY4MByEzppdXp3rnJ0g4LBWcsYTUvwKKClYN1ofZ0COOQ==
 
 
-electron@^16.0.2:
-  version "16.0.2"
-  resolved "https://registry.yarnpkg.com/electron/-/electron-16.0.2.tgz#6d0ae3152c410478dfffdbf80852a3840679b8c5"
-  integrity sha512-kT746yVMztrP4BbT3nrFNcUcfgFu2yelUw6TWBVTy0pju+fBISaqcvoiMrq+8U0vRpoXSu2MJYygOf4T0Det7g==
+electron@^21.0.1:
+  version "21.2.2"
+  resolved "https://registry.yarnpkg.com/electron/-/electron-21.2.2.tgz#e2f3dd014981df555d2cd1655590168b404edae4"
+  integrity sha512-Q0j1tzLTM5JRjSJVAfDSONZgdtuyruHR1pc1y2IbMYQz62pVJWVWAvcJXzpty5iRh2HKzW9+B9WVlmfWNFA8ag==
   dependencies:
   dependencies:
-    "@electron/get" "^1.13.0"
-    "@types/node" "^14.6.2"
-    extract-zip "^1.0.3"
+    "@electron/get" "^1.14.1"
+    "@types/node" "^16.11.26"
+    extract-zip "^2.0.1"
 
 
 emittery@^0.8.1:
 emittery@^0.8.1:
   version "0.8.1"
   version "0.8.1"
@@ -2461,15 +2484,16 @@ express@^4.17.1:
     utils-merge "1.0.1"
     utils-merge "1.0.1"
     vary "~1.1.2"
     vary "~1.1.2"
 
 
-extract-zip@^1.0.3:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
-  integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
+extract-zip@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
+  integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
   dependencies:
   dependencies:
-    concat-stream "^1.6.2"
-    debug "^2.6.9"
-    mkdirp "^0.5.4"
+    debug "^4.1.1"
+    get-stream "^5.1.0"
     yauzl "^2.10.0"
     yauzl "^2.10.0"
+  optionalDependencies:
+    "@types/yauzl" "^2.9.1"
 
 
 extsprintf@^1.2.0:
 extsprintf@^1.2.0:
   version "1.4.1"
   version "1.4.1"
@@ -2587,6 +2611,15 @@ fs-extra@^10.0.0:
     jsonfile "^6.0.1"
     jsonfile "^6.0.1"
     universalify "^2.0.0"
     universalify "^2.0.0"
 
 
+fs-extra@^10.1.0:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+  integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+  dependencies:
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^2.0.0"
+
 fs-extra@^8.1.0:
 fs-extra@^8.1.0:
   version "8.1.0"
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -2615,6 +2648,13 @@ fs-extra@~7.0.1:
     jsonfile "^4.0.0"
     jsonfile "^4.0.0"
     universalify "^0.1.0"
     universalify "^0.1.0"
 
 
+fs-minipass@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+  integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+  dependencies:
+    minipass "^3.0.0"
+
 fs.realpath@^1.0.0:
 fs.realpath@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2804,10 +2844,10 @@ helmet@^4.6.0:
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
   integrity sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==
   integrity sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==
 
 
-hosted-git-info@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961"
-  integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==
+hosted-git-info@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224"
+  integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==
   dependencies:
   dependencies:
     lru-cache "^6.0.0"
     lru-cache "^6.0.0"
 
 
@@ -2870,6 +2910,15 @@ http-proxy-agent@^4.0.1:
     agent-base "6"
     agent-base "6"
     debug "4"
     debug "4"
 
 
+http-proxy-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+  integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
+  dependencies:
+    "@tootallnate/once" "2"
+    agent-base "6"
+    debug "4"
+
 https-proxy-agent@^5.0.0:
 https-proxy-agent@^5.0.0:
   version "5.0.0"
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
@@ -2946,7 +2995,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     once "^1.3.0"
     wrappy "1"
     wrappy "1"
 
 
-inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.3:
   version "2.0.4"
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -3069,11 +3118,6 @@ is-yarn-global@^0.3.0:
   resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
   resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
   integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
   integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
 
 
-isarray@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
-  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
 isbinaryfile@^3.0.2:
 isbinaryfile@^3.0.2:
   version "3.0.3"
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80"
   resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80"
@@ -3081,10 +3125,10 @@ isbinaryfile@^3.0.2:
   dependencies:
   dependencies:
     buffer-alloc "^1.2.0"
     buffer-alloc "^1.2.0"
 
 
-isbinaryfile@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf"
-  integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==
+isbinaryfile@^4.0.10:
+  version "4.0.10"
+  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3"
+  integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
 
 
 isexe@^2.0.0:
 isexe@^2.0.0:
   version "2.0.0"
   version "2.0.0"
@@ -3143,13 +3187,13 @@ istanbul-reports@^3.0.2:
     html-escaper "^2.0.0"
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
     istanbul-lib-report "^3.0.0"
 
 
-jake@^10.6.1:
-  version "10.8.2"
-  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
-  integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
+jake@^10.8.5:
+  version "10.8.5"
+  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
+  integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
   dependencies:
   dependencies:
-    async "0.9.x"
-    chalk "^2.4.2"
+    async "^3.2.3"
+    chalk "^4.0.2"
     filelist "^1.0.1"
     filelist "^1.0.1"
     minimatch "^3.0.4"
     minimatch "^3.0.4"
 
 
@@ -3871,18 +3915,45 @@ minimatch@3.0.4, minimatch@^3.0.4:
   dependencies:
   dependencies:
     brace-expansion "^1.1.7"
     brace-expansion "^1.1.7"
 
 
+minimatch@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+  dependencies:
+    brace-expansion "^1.1.7"
+
 minimist@^1.2.0, minimist@^1.2.5:
 minimist@^1.2.0, minimist@^1.2.5:
   version "1.2.5"
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
 
-mkdirp@^0.5.3, mkdirp@^0.5.4:
+minipass@^3.0.0:
+  version "3.3.4"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae"
+  integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==
+  dependencies:
+    yallist "^4.0.0"
+
+minizlib@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+  integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+  dependencies:
+    minipass "^3.0.0"
+    yallist "^4.0.0"
+
+mkdirp@^0.5.3:
   version "0.5.5"
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
   integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
   integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
   dependencies:
   dependencies:
     minimist "^1.2.5"
     minimist "^1.2.5"
 
 
+mkdirp@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
 moment-timezone@^0.5.31:
 moment-timezone@^0.5.31:
   version "0.5.33"
   version "0.5.33"
   resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c"
   resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c"
@@ -4210,11 +4281,6 @@ pretty-format@^27.0.0, pretty-format@^27.3.1:
     ansi-styles "^5.0.0"
     ansi-styles "^5.0.0"
     react-is "^17.0.1"
     react-is "^17.0.1"
 
 
-process-nextick-args@~2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
-  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
 progress@^2.0.3:
 progress@^2.0.3:
   version "2.0.3"
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
   resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@@ -4362,19 +4428,6 @@ read-config-file@6.2.0:
     json5 "^2.2.0"
     json5 "^2.2.0"
     lazy-val "^1.0.4"
     lazy-val "^1.0.4"
 
 
-readable-stream@^2.2.2:
-  version "2.3.7"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
-  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.3"
-    isarray "~1.0.0"
-    process-nextick-args "~2.0.0"
-    safe-buffer "~5.1.1"
-    string_decoder "~1.1.1"
-    util-deprecate "~1.0.1"
-
 readable-stream@^3.6.0:
 readable-stream@^3.6.0:
   version "3.6.0"
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
@@ -4476,7 +4529,7 @@ roarr@^2.15.3:
     semver-compare "^1.0.0"
     semver-compare "^1.0.0"
     sprintf-js "^1.1.2"
     sprintf-js "^1.1.2"
 
 
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.2, safe-buffer@~5.1.1:
   version "5.1.2"
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -4522,7 +4575,7 @@ semver-diff@^3.1.1:
   dependencies:
   dependencies:
     semver "^6.3.0"
     semver "^6.3.0"
 
 
-semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0:
+semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@~7.3.0:
   version "7.3.5"
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@@ -4539,6 +4592,18 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
 
+semver@^7.3.7:
+  version "7.3.8"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+  dependencies:
+    lru-cache "^6.0.0"
+
+semver@~7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
+  integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
+
 send@0.17.1:
 send@0.17.1:
   version "0.17.1"
   version "0.17.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -4611,6 +4676,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3:
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
   integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
   integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
 
 
+simple-update-notifier@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc"
+  integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==
+  dependencies:
+    semver "~7.0.0"
+
 sisteransi@^1.0.5:
 sisteransi@^1.0.5:
   version "1.0.5"
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@@ -4732,7 +4804,7 @@ string-length@^4.0.1:
     char-regex "^1.0.2"
     char-regex "^1.0.2"
     strip-ansi "^6.0.0"
     strip-ansi "^6.0.0"
 
 
-string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2:
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
   version "4.2.3"
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -4748,13 +4820,6 @@ string_decoder@^1.1.1:
   dependencies:
   dependencies:
     safe-buffer "~5.2.0"
     safe-buffer "~5.2.0"
 
 
-string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
-  dependencies:
-    safe-buffer "~5.1.0"
-
 strip-ansi@^6.0.0, strip-ansi@^6.0.1:
 strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -4879,6 +4944,18 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
 
+tar@^6.1.11:
+  version "6.1.12"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6"
+  integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==
+  dependencies:
+    chownr "^2.0.0"
+    fs-minipass "^2.0.0"
+    minipass "^3.0.0"
+    minizlib "^2.1.1"
+    mkdirp "^1.0.3"
+    yallist "^4.0.0"
+
 temp-file@^3.4.0:
 temp-file@^3.4.0:
   version "3.4.0"
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7"
   resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7"
@@ -5100,11 +5177,6 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
   dependencies:
     is-typedarray "^1.0.0"
     is-typedarray "^1.0.0"
 
 
-typedarray@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-  integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-
 typescript@^4.4.4, typescript@~4.4.2:
 typescript@^4.4.4, typescript@~4.4.2:
   version "4.4.4"
   version "4.4.4"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
@@ -5176,7 +5248,7 @@ utf8-byte-length@^1.0.1:
   resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
   resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
   integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=
   integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=
 
 
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+util-deprecate@^1.0.1:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@@ -5369,6 +5441,11 @@ yargs-parser@20.x, yargs-parser@^20.2.2:
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
   integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
   integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
 
 
+yargs-parser@^21.1.1:
+  version "21.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+  integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
 yargs@^16.1.1, yargs@^16.2.0:
 yargs@^16.1.1, yargs@^16.2.0:
   version "16.2.0"
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
@@ -5382,18 +5459,18 @@ yargs@^16.1.1, yargs@^16.2.0:
     y18n "^5.0.5"
     y18n "^5.0.5"
     yargs-parser "^20.2.2"
     yargs-parser "^20.2.2"
 
 
-yargs@^17.0.1:
-  version "17.2.1"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
-  integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
+yargs@^17.5.1:
+  version "17.6.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"
+  integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==
   dependencies:
   dependencies:
-    cliui "^7.0.2"
+    cliui "^8.0.1"
     escalade "^3.1.1"
     escalade "^3.1.1"
     get-caller-file "^2.0.5"
     get-caller-file "^2.0.5"
     require-directory "^2.1.1"
     require-directory "^2.1.1"
-    string-width "^4.2.0"
+    string-width "^4.2.3"
     y18n "^5.0.5"
     y18n "^5.0.5"
-    yargs-parser "^20.2.2"
+    yargs-parser "^21.1.1"
 
 
 yauzl@^2.10.0:
 yauzl@^2.10.0:
   version "2.10.0"
   version "2.10.0"

Some files were not shown because too many files changed in this diff