Compare commits
194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d16c03851 | ||
|
|
7d30d398db | ||
|
|
8dc60190b3 | ||
|
|
9d4bbc97ac | ||
|
|
d938ca43ef | ||
|
|
b6aa0c13b0 | ||
|
|
62066817c6 | ||
|
|
9ef9b698a1 | ||
|
|
d510a14947 | ||
|
|
1fb91c891c | ||
|
|
a16b845940 | ||
|
|
4cbf9d0068 | ||
|
|
28cc8868fc | ||
|
|
9466254b17 | ||
|
|
5dd71d9985 | ||
|
|
e42712fa46 | ||
|
|
f508d76ee6 | ||
|
|
23c01912ea | ||
|
|
5e3518bdb1 | ||
|
|
7aadf008a2 | ||
|
|
095bb928a8 | ||
|
|
8799782a27 | ||
|
|
ab58170d27 | ||
|
|
1e2f102eac | ||
|
|
1e24f9bea1 | ||
|
|
8c75c354d0 | ||
|
|
a3b3a946cb | ||
|
|
ba3aaee726 | ||
|
|
5a52cc8eca | ||
|
|
74d6aba229 | ||
|
|
d423106e27 | ||
|
|
d256d4cb1b | ||
|
|
5c21a47557 | ||
|
|
6fd55a640f | ||
|
|
ee05ebfb33 | ||
|
|
9fd5cdcf8f | ||
|
|
95c56caa16 | ||
|
|
b47cf4f429 | ||
|
|
338deb2807 | ||
|
|
d6466d9a73 | ||
|
|
0a6395460f | ||
|
|
db4fb4f1a4 | ||
|
|
f0f81d7229 | ||
|
|
3da477ea0e | ||
|
|
93f13ad478 | ||
|
|
1b6fec9f52 | ||
|
|
6f2ebc6b70 | ||
|
|
1eb8ea593c | ||
|
|
e5f029356c | ||
|
|
3d6a5c0f39 | ||
|
|
86a0a28dfe | ||
|
|
a6533b4ed5 | ||
|
|
4f0166434a | ||
|
|
4bf16cccb9 | ||
|
|
aa139a693e | ||
|
|
5be30144b4 | ||
|
|
889a5c4011 | ||
|
|
ab31fcb8e7 | ||
|
|
37d68ac5d9 | ||
|
|
31e4457f39 | ||
|
|
7484ba098b | ||
|
|
93417fd355 | ||
|
|
6663567c90 | ||
|
|
d327f4fd29 | ||
|
|
0106f1a312 | ||
|
|
b8bbced7b7 | ||
|
|
b163530d67 | ||
|
|
2c8ff8ace9 | ||
|
|
4244f30dbd | ||
|
|
71df441572 | ||
|
|
1267c96de9 | ||
|
|
465f58092c | ||
|
|
7fa7f6b7ea | ||
|
|
11c45b7766 | ||
|
|
c29d639c0d | ||
|
|
ea8f1bc5f6 | ||
|
|
0ed2b8176f | ||
|
|
8fa19cac4c | ||
|
|
67a8840c61 | ||
|
|
b511031eea | ||
|
|
b70b1e835b | ||
|
|
ca239605fb | ||
|
|
762a99cc27 | ||
|
|
c719791d72 | ||
|
|
b50e1b644b | ||
|
|
190a4a9a62 | ||
|
|
a6518658cf | ||
|
|
19f14ef4d8 | ||
|
|
372997ec0f | ||
|
|
22090ad3da | ||
|
|
b4088b64ae | ||
|
|
1d0ecdeec8 | ||
|
|
795534a8dc | ||
|
|
a7843b1b8b | ||
|
|
8afb791fef | ||
|
|
89b4ea5ae7 | ||
|
|
38e0f2a0d6 | ||
|
|
91ae5ea3b0 | ||
|
|
29c9ed688e | ||
|
|
203b4a6ea1 | ||
|
|
64415afb8e | ||
|
|
e3d37f792b | ||
|
|
17d055b77b | ||
|
|
ce165d3ae7 | ||
|
|
7dc719e3e6 | ||
|
|
69a5b9ff65 | ||
|
|
24d70e3892 | ||
|
|
0b0dce3334 | ||
|
|
d11f74058e | ||
|
|
1752c2b6fa | ||
|
|
41d4386ca7 | ||
|
|
3cee7fff91 | ||
|
|
be67b54371 | ||
|
|
c50f224cfa | ||
|
|
2480fa8888 | ||
|
|
b9e5b76ec9 | ||
|
|
f4b988d24c | ||
|
|
0d9b0904b6 | ||
|
|
63b91a1064 | ||
|
|
2a59e8b4c4 | ||
|
|
68aaa7bfeb | ||
|
|
072849b577 | ||
|
|
b7a847e2a7 | ||
|
|
17fcd7f44c | ||
|
|
8a7a4f4286 | ||
|
|
1b51436770 | ||
|
|
cabfaae464 | ||
|
|
c4ee702da3 | ||
|
|
64159665ab | ||
|
|
719c9723e3 | ||
|
|
3ee14ed7ca | ||
|
|
f327390765 | ||
|
|
3aa422c127 | ||
|
|
92a5786974 | ||
|
|
8ab3f49690 | ||
|
|
2aabd45efa | ||
|
|
d62d9baedf | ||
|
|
796a576b8a | ||
|
|
d332dfb9db | ||
|
|
6feae672fe | ||
|
|
033979fd39 | ||
|
|
6051f8b1e6 | ||
|
|
7df10f97cf | ||
|
|
466c3bacd7 | ||
|
|
df4016f9f9 | ||
|
|
d5cd5b7b40 | ||
|
|
f9c3307426 | ||
|
|
a621288e5c | ||
|
|
48ba06580f | ||
|
|
0a47bcb096 | ||
|
|
60fed38eb5 | ||
|
|
b62a75b2e6 | ||
|
|
bb535ced21 | ||
|
|
ee7c75ed0c | ||
|
|
e3e231c011 | ||
|
|
fff462be1b | ||
|
|
d49df5b747 | ||
|
|
c1c181229d | ||
|
|
f0d73555b4 | ||
|
|
c21bf84f37 | ||
|
|
13e306b9d4 | ||
|
|
fe5f8922e9 | ||
|
|
65b217518f | ||
|
|
09bbb1d53d | ||
|
|
614863bf9d | ||
|
|
eba1fab8cc | ||
|
|
dc53fe51db | ||
|
|
2649a88731 | ||
|
|
4c79e17ce0 | ||
|
|
3ae9542666 | ||
|
|
9bdfaace16 | ||
|
|
a8244eb40f | ||
|
|
5a186ebe14 | ||
|
|
560329410e | ||
|
|
4473e1250e | ||
|
|
dac910fbf0 | ||
|
|
cb38228906 | ||
|
|
01479160d5 | ||
|
|
0433dcccdb | ||
|
|
1a2bb1b22c | ||
|
|
ad1f7c659c | ||
|
|
41c55d97c2 | ||
|
|
7fbf4d3530 | ||
|
|
2c7ae3c9f0 | ||
|
|
b68365c99b | ||
|
|
c8730bc587 | ||
|
|
1fa0d95cb0 | ||
|
|
2da217f3d0 | ||
|
|
4b70915182 | ||
|
|
a3f25ae2ad | ||
|
|
29807adc97 | ||
|
|
17c9bc5463 | ||
|
|
43ed27163e | ||
|
|
3eca3291c7 |
20
README.md
@@ -1,5 +1,7 @@
|
||||
# VanillaMeta
|
||||
|
||||
<img title="VanillaMeta Logo" src="design/vanillameta-logo.png"/><br/>
|
||||
|
||||
최신 엔터프라이즈용 비즈니스 인텔리전스 웹 애플리케이션입니다.
|
||||
|
||||
# 바닐라메타를 사용해야 하는 이유
|
||||
@@ -17,10 +19,17 @@
|
||||
|
||||
## 스크린샷
|
||||
|
||||
- 다양한 시각화 차트
|
||||
- 강력한 SQL 편집기
|
||||
- 코딩없이 차트 제작
|
||||
- 템플릿 추천
|
||||
- **다양한 시각화 차트**
|
||||
<kbd><img title="Chart" src="design/feature-01.png"/></kbd><br/>
|
||||
|
||||
- **강력한 SQL 편집기**
|
||||
<kbd><img title="Chart" src="design/feature-02.png"/></kbd><br/>
|
||||
|
||||
- **코딩없이 차트 제작**
|
||||
<kbd><img title="Chart" src="design/feature-03.png"/></kbd><br/>
|
||||
|
||||
- **템플릿 추천**
|
||||
<kbd><img title="Chart" src="design/feature-04.png"/></kbd><br/>
|
||||
|
||||
## 지원하는 데이터베이스
|
||||
|
||||
@@ -30,6 +39,7 @@
|
||||
- SQLServer
|
||||
- SQLite
|
||||
- Oracle
|
||||
- DB2
|
||||
- Amazon Redshift
|
||||
- Big Query
|
||||
- Cockroachdb
|
||||
- Snowflake
|
||||
|
||||
119
backend-api/.dockerignore
Normal file
@@ -0,0 +1,119 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir : __dirname,
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir : __dirname,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
@@ -22,4 +22,4 @@ module.exports = {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
||||
};
|
||||
2
backend-api/.gitignore
vendored
@@ -4,6 +4,8 @@ config.serverless.yml
|
||||
.env.prod
|
||||
.ormconfig.json
|
||||
vanillameta
|
||||
bigquery-key.json
|
||||
test-connect-info.json
|
||||
.
|
||||
# compiled output
|
||||
/dist
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get -qq update
|
||||
RUN apt-get -qq upgrade --yes
|
||||
RUN apt-get -qq install curl --yes
|
||||
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||
RUN apt-get -qq install nodejs --yes
|
||||
|
||||
RUN apt-get install chromium-browser --yes
|
||||
|
||||
FROM node:14-slim
|
||||
# ORACLE 설치
|
||||
RUN apt-get update && apt-get install -y libaio1 wget unzip
|
||||
WORKDIR /opt/oracle
|
||||
RUN wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip && \
|
||||
unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \
|
||||
cd /opt/oracle/instantclient* && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \
|
||||
echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# ENV NODE_ENV production
|
||||
# COPY --from=builder /app ./
|
||||
COPY . .
|
||||
COPY tsconfig.json .
|
||||
COPY tsconfig.build.json .
|
||||
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
# expose our default runtime port
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
CMD ["npm","run","start:prod"]
|
||||
30
backend-api/Dockerfile.dev
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM ubuntu:18.04
|
||||
|
||||
|
||||
|
||||
RUN apt-get -qq update
|
||||
RUN apt-get -qq upgrade --yes
|
||||
RUN apt-get -qq install curl --yes
|
||||
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||
RUN apt-get -qq install nodejs --yes
|
||||
|
||||
RUN apt-get install chromium-browser --yes
|
||||
|
||||
RUN useradd -ms /bin/bash frog
|
||||
USER frog
|
||||
WORKDIR /home/frog
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
COPY tsconfig.json .
|
||||
COPY tsconfig.build.json .
|
||||
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -3,44 +3,46 @@ services:
|
||||
main:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./Dockerfile
|
||||
dockerfile: ./Dockerfile.dev
|
||||
ports:
|
||||
- "4000:4000"
|
||||
networks:
|
||||
- vanillameta
|
||||
depends_on:
|
||||
- mysql
|
||||
# - mysql
|
||||
# - pg
|
||||
- mssql
|
||||
links:
|
||||
- "mysql:mysqldb"
|
||||
# - "mysql:mysqldb"
|
||||
# - "pg"
|
||||
- mssql
|
||||
env_file:
|
||||
- .env
|
||||
- .env.dev
|
||||
restart: always
|
||||
|
||||
mysql:
|
||||
container_name: vanillameta_mysql
|
||||
image: mysql
|
||||
ports:
|
||||
- "3306:3306"
|
||||
networks:
|
||||
- vanillameta
|
||||
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
MYSQL_CHARSET: utf8mb4
|
||||
MYSQL_DATABASE: ${DB_NAME}
|
||||
MYSQL_USER: ${DB_USERNAME}
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
TZ: Asia/Seoul
|
||||
restart: always
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
--default_authentication_plugin=mysql_native_password
|
||||
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
- ./db/conf.d:/etc/mysql/conf.d
|
||||
# mysql:
|
||||
# container_name: vanillameta_mysql
|
||||
# image: mysql
|
||||
# ports:
|
||||
# - "3306:3306"
|
||||
# networks:
|
||||
# - vanillameta
|
||||
#
|
||||
# environment:
|
||||
# MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
# MYSQL_CHARSET: utf8mb4
|
||||
# MYSQL_DATABASE: ${DB_NAME}
|
||||
# MYSQL_USER: ${DB_USERNAME}
|
||||
# MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
# TZ: Asia/Seoul
|
||||
# restart: always
|
||||
# command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
# --default_authentication_plugin=mysql_native_password
|
||||
#
|
||||
# volumes:
|
||||
# - mysql:/var/lib/mysql
|
||||
# - ./db/conf.d:/etc/mysql/conf.d
|
||||
|
||||
# mariadb:
|
||||
# container_name: vanillameta_mariadb
|
||||
@@ -104,25 +106,27 @@ services:
|
||||
# - ./db/conf.d:/etc/porstgressql/data
|
||||
|
||||
|
||||
#
|
||||
# mssql:
|
||||
# container_name: vanillameta_mssql
|
||||
# image: mcr.microsoft.com/azure-sql-edge
|
||||
# ports:
|
||||
# - "1433:1433"
|
||||
# networks:
|
||||
# - vanillameta
|
||||
# environment:
|
||||
# ACCEPT_EULA: "Y"
|
||||
# MSSQL_SA_PASSWORD: ${DB_PASSWORD}
|
||||
# MSSQL_AGENT_ENABLED: "true"
|
||||
# restart: always
|
||||
# command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
#
|
||||
# volumes:
|
||||
# - ./vanillameta_mssqldata:/var/opt/mssql
|
||||
#
|
||||
# - ./db/conf.d:/etc/mysql/conf.d
|
||||
|
||||
mssql:
|
||||
container_name: vanillameta_mssql
|
||||
image: mcr.microsoft.com/mssql/server:2019-latest
|
||||
user: frog
|
||||
ports:
|
||||
- "1433:1433"
|
||||
networks:
|
||||
- vanillameta
|
||||
environment:
|
||||
ACCEPT_EULA: "Y"
|
||||
MSSQL_SA_PASSWORD: 'Qkslffk@123123'
|
||||
|
||||
|
||||
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
- ./sqlvolume:/home/frog
|
||||
|
||||
|
||||
|
||||
volumes:
|
||||
mysql:
|
||||
|
||||
10
backend-api/install_oracle_driver_mac.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
BASEDIR=$(PWD)
|
||||
cd $HOME/Downloads
|
||||
curl -O https://download.oracle.com/otn_software/mac/instantclient/198000/instantclient-basic-macos.x64-19.8.0.0.0dbru.dmg
|
||||
hdiutil mount instantclient-basic-macos.x64-19.8.0.0.0dbru.dmg
|
||||
/Volumes/instantclient-basic-macos.x64-19.8.0.0.0dbru/install_ic.sh
|
||||
hdiutil unmount /Volumes/instantclient-basic-macos.x64-19.8.0.0.0dbru
|
||||
cd $BASEDIR
|
||||
cp ~/Downloads/instantclient_19_8/{libclntsh.dylib.19.1,libclntshcore.dylib.19.1,libnnz19.dylib,libociei.dylib} node_modules/oracledb/build/Release
|
||||
cd node_modules/oracledb/build/Release/ && ln -s libclntsh.dylib.19.1 libclntsh.dylib
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": ".",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".*spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "default",
|
||||
"type": "sqlite",
|
||||
"host" : "127.0.0.1",
|
||||
"post" : 5432,
|
||||
"username" : "username",
|
||||
"password" : "password",
|
||||
"database" : "db_01",
|
||||
"entities": [
|
||||
"dist/**/*.entity.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "test_db",
|
||||
"tpye":
|
||||
"database" : "vanillameta",
|
||||
},
|
||||
{
|
||||
"name": "db_02",
|
||||
"type": "mysql",
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"username": "vanillameta",
|
||||
"password": "pw",
|
||||
"database": "vanillameta",
|
||||
"autoLoadEntities": true
|
||||
|
||||
]
|
||||
520
backend-api/package-lock.json
generated
@@ -4951,14 +4951,6 @@
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"requires": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
@@ -5020,11 +5012,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
|
||||
},
|
||||
"assign-symbols": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
|
||||
@@ -5153,16 +5140,6 @@
|
||||
"type-is": "^1.6.16"
|
||||
}
|
||||
},
|
||||
"aws-sign2": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
|
||||
},
|
||||
"aws4": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
|
||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
@@ -5606,14 +5583,6 @@
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
||||
},
|
||||
"bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||
"requires": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"better-sqlite3": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.6.2.tgz",
|
||||
@@ -6341,11 +6310,6 @@
|
||||
"integrity": "sha512-DdUCktgMSM+1ndk9EFMZcavsGszV7zxV9O7MtOHniTa/iyAIwJCF0dFVBdU9SijJbfh29hC9bCs07wu8pjnGJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
|
||||
},
|
||||
"chainsaw": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
|
||||
@@ -6842,6 +6806,11 @@
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true
|
||||
},
|
||||
"complex.js": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
|
||||
"integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg=="
|
||||
},
|
||||
"component-bind": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
|
||||
@@ -7195,14 +7164,6 @@
|
||||
"integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==",
|
||||
"dev": true
|
||||
},
|
||||
"dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
|
||||
@@ -7227,6 +7188,11 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"decimal.js": {
|
||||
"version": "10.4.2",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz",
|
||||
"integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA=="
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
@@ -7810,15 +7776,6 @@
|
||||
"resolved": "https://registry.npmjs.org/dylib-node/-/dylib-node-1.0.10.tgz",
|
||||
"integrity": "sha512-bjsSQJgDz8Iqd0avdq1UmIgL46Ip+WfqJ6Y4AlNGBnZVlMzktQOrwuWYesqRaOTdtGjm8PijjATa8EZBzvgk4g=="
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
|
||||
"requires": {
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@@ -8083,11 +8040,6 @@
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
|
||||
},
|
||||
"es6-promisify": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz",
|
||||
@@ -8140,6 +8092,11 @@
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"escape-latex": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
|
||||
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
@@ -8708,15 +8665,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-diff": {
|
||||
"version": "1.2.0",
|
||||
@@ -8740,7 +8693,8 @@
|
||||
"fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
@@ -9092,11 +9046,6 @@
|
||||
"for-in": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin": {
|
||||
"version": "7.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz",
|
||||
@@ -9169,6 +9118,11 @@
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA=="
|
||||
},
|
||||
"fragment-cache": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
|
||||
@@ -9505,14 +9459,6 @@
|
||||
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz",
|
||||
"integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA=="
|
||||
},
|
||||
"getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
@@ -9826,38 +9772,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"har-schema": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
|
||||
},
|
||||
"har-validator": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
|
||||
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
|
||||
"requires": {
|
||||
"ajv": "^6.12.3",
|
||||
"har-schema": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
@@ -10090,16 +10004,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"jsprim": "^1.2.2",
|
||||
"sshpk": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"http2-wrapper": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
|
||||
@@ -10687,7 +10591,8 @@
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
|
||||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
|
||||
"dev": true
|
||||
},
|
||||
"is-unc-path": {
|
||||
"version": "1.0.0",
|
||||
@@ -10745,11 +10650,6 @@
|
||||
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
|
||||
"dev": true
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
|
||||
},
|
||||
"istanbul-lib-coverage": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
|
||||
@@ -10843,6 +10743,11 @@
|
||||
"integrity": "sha512-gZmQKe1QrfkkMjCn8Qv9cpyJFyogTYqkP5WCobX5RNaHsJzIV/6NvAnlnouOcwKr29QrxLGDGcqYuJ+ae98s1A==",
|
||||
"dev": true
|
||||
},
|
||||
"javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
|
||||
},
|
||||
"jest": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz",
|
||||
@@ -11444,11 +11349,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz",
|
||||
"integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g=="
|
||||
},
|
||||
"jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
@@ -11518,11 +11418,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"json-schema": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
@@ -11544,11 +11439,6 @@
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
@@ -11624,17 +11514,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
|
||||
"requires": {
|
||||
"assert-plus": "1.0.0",
|
||||
"extsprintf": "1.3.0",
|
||||
"json-schema": "0.4.0",
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
@@ -12056,125 +11935,6 @@
|
||||
"lodash.isnil": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"knex-snowflake-dialect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/knex-snowflake-dialect/-/knex-snowflake-dialect-1.0.1.tgz",
|
||||
"integrity": "sha512-MKXc2alsv9bt/TkiUdLD40Pp4CQJikbNztOJawvT2+tppXkvSywQL9agKE2jH8UqEZuUg47Gp1U7Thvg+yqx5g==",
|
||||
"requires": {
|
||||
"bluebird": "^3.7.2",
|
||||
"lodash": "^4.17.15",
|
||||
"snowflake-sdk": "1.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz",
|
||||
"integrity": "sha512-oDtZV740o3fr5oJtPLOsgH2hl2TRPscNXIx4VzzBwVlXVkv8RHm7XXqGAYg8t20+Gwu6LNDnx8HRMGqVGPZ8Vw==",
|
||||
"requires": {
|
||||
"extend": "~3.0.0",
|
||||
"semver": "~5.0.1"
|
||||
}
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz",
|
||||
"integrity": "sha512-uw4ra6Cv483Op/ebM0GBKKfxZlSmn6NgFRby5L3yGTlunLj53KQgndDlqy2WVFOwgvurocApYkSud0aO+mvrpQ=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||
"integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==",
|
||||
"requires": {
|
||||
"es6-promise": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz",
|
||||
"integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
|
||||
"requires": {
|
||||
"agent-base": "^4.3.0",
|
||||
"debug": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz",
|
||||
"integrity": "sha512-5OkOBiw69xqmxOFIXwXsiY1HlE+om8nNptg1ZIf95fzcnfgOv2fLm7pmmGbRJsjJIqPpW5Kwy4wpDBTz5wQlUw=="
|
||||
},
|
||||
"snowflake-sdk": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/snowflake-sdk/-/snowflake-sdk-1.6.0.tgz",
|
||||
"integrity": "sha512-5hjK1/swkYH5/+Q2ozcknhdKzTABviA9ncj3Go6rGVhNz2iXZDv+wC1dWlq3XYFKYhjPEd45SyhAZ20+yWgAbA==",
|
||||
"requires": {
|
||||
"agent-base": "^2.1.1",
|
||||
"asn1.js-rfc2560": "^5.0.0",
|
||||
"asn1.js-rfc5280": "^3.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"big-integer": "^1.6.43",
|
||||
"bignumber.js": "^2.4.0",
|
||||
"browser-request": "^0.3.3",
|
||||
"debug": "^3.2.6",
|
||||
"extend": "^3.0.2",
|
||||
"https-proxy-agent": "^3.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mkdirp": "^1.0.3",
|
||||
"mock-require": "^3.0.3",
|
||||
"moment": "^2.23.0",
|
||||
"moment-timezone": "^0.5.15",
|
||||
"ocsp": "^1.2.0",
|
||||
"open": "^7.3.1",
|
||||
"request": "^2.88.0",
|
||||
"requestretry": "^4.1.0",
|
||||
"simple-lru-cache": "^0.0.2",
|
||||
"uuid": "^3.3.2",
|
||||
"winston": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"kuler": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
|
||||
@@ -12561,6 +12321,37 @@
|
||||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"mathjs": {
|
||||
"version": "11.3.3",
|
||||
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.3.3.tgz",
|
||||
"integrity": "sha512-+NsgPRzvnczrw5hp7fNPMnfXCaBo2cs7c8Edoacbjcc2Z3js6jHf+Pz8FY6nb5udmBj0q4zl+a5PxbjWVgOQcA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.20.1",
|
||||
"complex.js": "^2.1.1",
|
||||
"decimal.js": "^10.4.2",
|
||||
"escape-latex": "^1.2.0",
|
||||
"fraction.js": "^4.2.0",
|
||||
"javascript-natural-sort": "^0.7.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"tiny-emitter": "^2.1.0",
|
||||
"typed-function": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz",
|
||||
"integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.10"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.10",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz",
|
||||
"integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -13295,11 +13086,6 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -13395,56 +13181,6 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"ocsp": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ocsp/-/ocsp-1.2.0.tgz",
|
||||
"integrity": "sha512-r4Q3oYKU+3b6iD4bn+5O2dQqctu8pFrJfWouUiKjiNXXjdr99lN/EaTVkFQevGlV/lKsomgtt/XRGB8xV8rq3Q==",
|
||||
"requires": {
|
||||
"asn1.js": "^4.8.0",
|
||||
"asn1.js-rfc2560": "^4.0.0",
|
||||
"asn1.js-rfc5280": "^2.0.0",
|
||||
"async": "^1.5.2",
|
||||
"simple-lru-cache": "0.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"asn1.js": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
|
||||
"integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
||||
"requires": {
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"asn1.js-rfc2560": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js-rfc2560/-/asn1.js-rfc2560-4.0.6.tgz",
|
||||
"integrity": "sha512-ysf48ni+f/efNPilq4+ApbifUPcSW/xbDeQAh055I+grr2gXgNRQqHew7kkO70WSMQ2tEOURVwsK+dJqUNjIIg==",
|
||||
"requires": {
|
||||
"asn1.js-rfc5280": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"asn1.js-rfc5280": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js-rfc5280/-/asn1.js-rfc5280-2.0.1.tgz",
|
||||
"integrity": "sha512-1e2ypnvTbYD/GdxWK77tdLBahvo1fZUHlQJqAVUuZWdYj0rdjGcf2CWYUtbsyRYpYUMwMWLZFUtLxog8ZXTrcg==",
|
||||
"requires": {
|
||||
"asn1.js": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"odbc": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/odbc/-/odbc-2.4.6.tgz",
|
||||
@@ -14099,11 +13835,6 @@
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"dev": true
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
|
||||
},
|
||||
"pg": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
|
||||
@@ -14959,74 +14690,6 @@
|
||||
"integrity": "sha512-sL26E4+8Kec7bwpRjHlQvbNZcpnGroT3PA7ywsgH6GjzxAg4IGNlNalLoRC/JmTed7cMhyDbi44pWw1kMhDxlw==",
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
||||
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"requires": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestretry": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/requestretry/-/requestretry-4.1.2.tgz",
|
||||
"integrity": "sha512-N1WAp+8eOy8NfsVBChcSxNCKvPY1azOpliQ4Sby4WDe0HFEhdKywlNZeROMBQ+BI3Jpc0eNOT1KVFGREawtahA==",
|
||||
"requires": {
|
||||
"extend": "^3.0.2",
|
||||
"lodash": "^4.17.15",
|
||||
"when": "^3.7.7"
|
||||
}
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -15271,6 +14934,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"seedrandom": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
||||
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
|
||||
},
|
||||
"seek-bzip": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
|
||||
@@ -16419,22 +16087,6 @@
|
||||
"tar": "^6.1.11"
|
||||
}
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
|
||||
"integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
|
||||
"requires": {
|
||||
"asn1": "~0.2.3",
|
||||
"assert-plus": "^1.0.0",
|
||||
"bcrypt-pbkdf": "^1.0.0",
|
||||
"dashdash": "^1.12.0",
|
||||
"ecc-jsbn": "~0.1.1",
|
||||
"getpass": "^0.1.1",
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.0.2",
|
||||
"tweetnacl": "~0.14.0"
|
||||
}
|
||||
},
|
||||
"ssri": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
|
||||
@@ -17432,6 +17084,11 @@
|
||||
"next-tick": "1"
|
||||
}
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
@@ -17709,11 +17366,6 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
|
||||
},
|
||||
"type": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
|
||||
@@ -17750,6 +17402,11 @@
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"typed-function": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz",
|
||||
"integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg=="
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
@@ -18033,6 +17690,7 @@
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
@@ -18225,23 +17883,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
|
||||
"requires": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vm-browserify": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||
@@ -18342,11 +17983,6 @@
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"when": {
|
||||
"version": "3.7.8",
|
||||
"resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz",
|
||||
"integrity": "sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw=="
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"knex-bigquery": "^2.0.3",
|
||||
"knex-db2": "^1.0.0",
|
||||
"knex-schema-inspector": "^2.0.4",
|
||||
"knex-snowflake-dialect": "^1.0.1",
|
||||
"mathjs": "^11.3.3",
|
||||
"mustache": "^4.2.0",
|
||||
"mysql2": "^2.3.3",
|
||||
"nest-winston": "^1.7.0",
|
||||
@@ -107,7 +107,8 @@
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testTimeout": 20000,
|
||||
"rootDir": ".",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
|
||||
@@ -7,6 +7,9 @@ import { Database } from '../database/entities/database.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ResponseStatus } from '../common/enum/response-status.enum';
|
||||
import { SnowflakeDialect } from './knex-dialects/snowflake';
|
||||
|
||||
const { BigQueryClient } = require('knex-bigquery');
|
||||
|
||||
const knexConnections = new Map<number, Knex>();
|
||||
|
||||
@@ -44,12 +47,17 @@ export class ConnectionService {
|
||||
/**
|
||||
* Knex 객체 가져오기 - 만약 없으면 가져오기
|
||||
* @param id
|
||||
* @param databaseInfo
|
||||
*/
|
||||
async getKnex(id: number): Promise<Knex> {
|
||||
if (!this.hasKnex(id)) {
|
||||
const one = await this.databaseRepository.findOne({ where: { id: id } });
|
||||
one.connectionConfig = JSON.parse(one.connectionConfig);
|
||||
const knexConfig = one.connectionConfig;
|
||||
if (knexConfig['client'] == 'bigquery') {
|
||||
knexConfig['client'] = BigQueryClient;
|
||||
} else if (knexConfig['client'] == 'snowflake') {
|
||||
knexConfig['client'] = SnowflakeDialect;
|
||||
}
|
||||
this.addKnex(id, one.connectionConfig as Knex.Config);
|
||||
}
|
||||
return knexConnections.get(id);
|
||||
@@ -60,12 +68,30 @@ export class ConnectionService {
|
||||
* @param createDatabaseDto
|
||||
*/
|
||||
async testConnection(createDatabaseDto: CreateDatabaseDto) {
|
||||
let engine: any = createDatabaseDto.engine;
|
||||
switch (createDatabaseDto.engine) {
|
||||
case 'bigquery':
|
||||
engine = BigQueryClient;
|
||||
break;
|
||||
case 'snowflake':
|
||||
engine = SnowflakeDialect;
|
||||
break;
|
||||
}
|
||||
|
||||
if (createDatabaseDto.engine === 'cockroachdb') {
|
||||
const connectioninfo = createDatabaseDto.connectionConfig;
|
||||
const cockroach_url = `postgresql://${connectioninfo['user']}:${connectioninfo['password']}@${connectioninfo['host']}:${connectioninfo['port']}/${connectioninfo['database']}?sslmode=verify-full&options=--cluster%3Dvanillameta-cockroach-3010`;
|
||||
connectioninfo['connectionString'] = cockroach_url;
|
||||
}
|
||||
|
||||
const connectionConfig = {
|
||||
client: createDatabaseDto.engine,
|
||||
client: engine,
|
||||
connection: createDatabaseDto.connectionConfig,
|
||||
useNullAsDefault: true,
|
||||
};
|
||||
createDatabaseDto.connectionConfig = JSON.stringify(connectionConfig);
|
||||
|
||||
// createDatabaseDto.connectionConfig = JSON.stringify(connectionConfig);
|
||||
// console.log(createDatabaseDto)
|
||||
let _knex: Knex;
|
||||
let returnObj = {};
|
||||
try {
|
||||
@@ -76,8 +102,10 @@ export class ConnectionService {
|
||||
return { status: ResponseStatus.ERROR, message: 'knex not connected' };
|
||||
}
|
||||
|
||||
const testQuery = createDatabaseDto.engine == 'oracledb' ? 'SELECT 1 FROM DUAL' : 'SELECT 1';
|
||||
|
||||
try {
|
||||
await _knex.raw('SELECT 1');
|
||||
await _knex.raw(testQuery);
|
||||
returnObj = { status: ResponseStatus.SUCCESS, data: { message: 'success' } };
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
@@ -98,108 +126,120 @@ export class ConnectionService {
|
||||
|
||||
let datas = [];
|
||||
const fields = [];
|
||||
const resultObj = { status: ResponseStatus.SUCCESS, message: 'success', datas: [], fields: [] };
|
||||
|
||||
const resultObj = { status: null, message: null, datas: [], fields: [] };
|
||||
try {
|
||||
const queryRes = await knex.raw(queryExecuteDto.query);
|
||||
|
||||
switch (knex.client.config.client) {
|
||||
case 'mysql2':
|
||||
if (queryRes && queryRes[0].length > 0) {
|
||||
datas = queryRes[0];
|
||||
const tempFields = queryRes[1];
|
||||
tempFields.map(field => {
|
||||
const fieldInfo = {
|
||||
columnName: field.name,
|
||||
columnType: FieldTypeUtil.mysqlFieldType(field.columnType),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
|
||||
});
|
||||
// bigquery, snowflake
|
||||
if (typeof knex.client.config.client === 'function') {
|
||||
switch (knex.client.config.client.name) {
|
||||
case 'SnowflakeDialect':
|
||||
if (queryRes && queryRes.rows && queryRes.rows.length > 0) {
|
||||
datas = queryRes.rows;
|
||||
const tempFields = Object.keys(queryRes.rows[0]);
|
||||
tempFields.map(field => {
|
||||
const length = [];
|
||||
const maxCnt = queryRes.rows.length > 100 ? 100 : queryRes.rows.length;
|
||||
for (let i = 0; i < maxCnt; i++) {
|
||||
length.push(queryRes.rows[i][field]);
|
||||
}
|
||||
const fieldInfo = {
|
||||
columnName: field,
|
||||
columnType: FieldTypeUtil.FieldType(length),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'BigQueryClient':
|
||||
if (queryRes && queryRes.length > 0) {
|
||||
datas = queryRes;
|
||||
const tempFields = Object.keys(queryRes[0]);
|
||||
|
||||
case 'sqlite':
|
||||
|
||||
if (queryRes && queryRes.length > 0) {
|
||||
|
||||
datas = queryRes;
|
||||
const tempFields = Object.keys(queryRes[0]);
|
||||
tempFields.map(field => {
|
||||
const length = [];
|
||||
for(let i = 0 ; i < 100; i ++){
|
||||
length.push(queryRes[i][field])
|
||||
}
|
||||
const fieldInfo = {
|
||||
columnName: field,
|
||||
columnType: FieldTypeUtil.FieldType(length),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
tempFields.map(field => {
|
||||
const length = [];
|
||||
const maxCnt = queryRes.length > 100 ? 100 : queryRes.length;
|
||||
for (let i = 0; i < maxCnt; i++) {
|
||||
length.push(queryRes[i][field]);
|
||||
}
|
||||
const fieldInfo = {
|
||||
columnName: field,
|
||||
columnType: FieldTypeUtil.FieldType(length),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pg':
|
||||
if (queryRes && queryRes.rows.length > 0) {
|
||||
datas = queryRes.rows;
|
||||
const tempFields = queryRes.fields;
|
||||
tempFields.map(field => {
|
||||
const length = [];
|
||||
for(let i = 0 ; i < 100; i ++){
|
||||
length.push(queryRes.rows[i][field.name])
|
||||
}
|
||||
const fieldInfo = {
|
||||
columnName: field.name,
|
||||
columnType: FieldTypeUtil.FieldType(length),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
switch (knex.client.config.client) {
|
||||
case 'mysql2':
|
||||
if (queryRes && queryRes[0].length > 0) {
|
||||
datas = queryRes[0];
|
||||
const tempFields = queryRes[1];
|
||||
tempFields.map(field => {
|
||||
const fieldInfo = {
|
||||
columnName: field.name,
|
||||
columnType: FieldTypeUtil.mysqlFieldType(field.columnType),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case 'Db2Dialect':
|
||||
if (queryRes && queryRes.length > 0) {
|
||||
|
||||
datas = queryRes;
|
||||
|
||||
// for (let i = 0; i < Object.keys(queryRes[0]).length; i++) {
|
||||
// console.log(Object.keys[i]);
|
||||
// }
|
||||
const tempFields = queryRes;
|
||||
tempFields.map(field => {
|
||||
const fieldInfo = {
|
||||
columnName: field.name,
|
||||
columnLength: field.length,
|
||||
columnType: FieldTypeUtil.mysqlFieldType(field.type),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
case 'cockroachdb':
|
||||
case 'pg':
|
||||
if (queryRes && queryRes.rows && queryRes.rows.length > 0) {
|
||||
datas = queryRes.rows;
|
||||
const tempFields = queryRes.fields;
|
||||
tempFields.map(field => {
|
||||
const length = [];
|
||||
const maxCnt = queryRes.rows.length > 100 ? 100 : queryRes.rows.length;
|
||||
for (let i = 0; i < maxCnt; i++) {
|
||||
length.push(queryRes.rows[i][field.name]);
|
||||
}
|
||||
const fieldInfo = {
|
||||
columnName: field.name,
|
||||
columnType: FieldTypeUtil.FieldType(length),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'mssql':
|
||||
if (queryRes && queryRes.length > 0) {
|
||||
datas = queryRes;
|
||||
const tempFields = Object.keys(queryRes[0]);
|
||||
tempFields.map(field => {
|
||||
const fieldInfo = {
|
||||
columnName: field,
|
||||
columnType: FieldTypeUtil.mysqlFieldType(field),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
}
|
||||
break;
|
||||
// case 'sqlite3':
|
||||
// case 'mssql':
|
||||
// case 'oracledb':
|
||||
default:
|
||||
if (queryRes && queryRes.length > 0) {
|
||||
datas = queryRes;
|
||||
const tempFields = Object.keys(queryRes[0]);
|
||||
|
||||
tempFields.map(field => {
|
||||
const length = [];
|
||||
const maxCnt = queryRes.length > 100 ? 100 : queryRes.length;
|
||||
for (let i = 0; i < maxCnt; i++) {
|
||||
length.push(queryRes[i][field]);
|
||||
}
|
||||
const fieldInfo = {
|
||||
columnName: field,
|
||||
columnType: FieldTypeUtil.FieldType(length),
|
||||
};
|
||||
fields.push(fieldInfo);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
resultObj.status = ResponseStatus.SUCCESS;
|
||||
resultObj.message = 'success';
|
||||
resultObj.datas = datas;
|
||||
resultObj.fields = fields;
|
||||
} catch (e) {
|
||||
resultObj.status = ResponseStatus.ERROR;
|
||||
resultObj.message = e.sqlMessage;
|
||||
if (e.sqlMessage) resultObj.message = e.sqlMessage;
|
||||
else if (e.message) resultObj.message = e.message; // bigquery
|
||||
console.log(e);
|
||||
console.log(e.sqlMessage);
|
||||
}
|
||||
|
||||
return resultObj;
|
||||
|
||||
160
backend-api/src/connection/knex-dialects/ibm-db/index.js
Normal file
@@ -0,0 +1,160 @@
|
||||
// import { Knex, knex } from 'knex';
|
||||
// import { Database } from 'ibm_db';
|
||||
// import Client = knex.Client;
|
||||
|
||||
// const Promise = require('bluebird');
|
||||
// const Client = require('knex/lib/client');
|
||||
//
|
||||
// class DB2Client extends Client {
|
||||
// constructor(config) {
|
||||
// super(config);
|
||||
// }
|
||||
//
|
||||
// get dialect() {
|
||||
// return 'ibm_db';
|
||||
// }
|
||||
//
|
||||
// get driverName() {
|
||||
// return 'ibm_db';
|
||||
// }
|
||||
//
|
||||
// get canCancelQuery() {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// _driver() {
|
||||
// return Promise.promisifyAll(require(this.driverName));
|
||||
// }
|
||||
//
|
||||
// // transaction() {
|
||||
// // return new Transaction(this, ...arguments);
|
||||
// // }
|
||||
//
|
||||
// wrapIdentifierImpl(value) {
|
||||
// // override default wrapper ("). we don't want to use it since
|
||||
// // it makes identifiers case-sensitive in DB2
|
||||
// return value;
|
||||
// }
|
||||
//
|
||||
// // printDebug(message) {
|
||||
// // if (process.env.DEBUG === 1) {
|
||||
// // this.logger.log(message);
|
||||
// // }
|
||||
// // }
|
||||
//
|
||||
// // Get a raw connection, called by the pool manager whenever a new
|
||||
// // connection needs to be added to the pool.
|
||||
// acquireRawConnection() {
|
||||
// // this.printDebug('acquiring raw connection.');
|
||||
// const connectionConfig = this.config.connection;
|
||||
// return new Promise((resolve, reject) => {
|
||||
// this.driver.open(this._getConnectionString(connectionConfig), (err, connection) => {
|
||||
// if (err) {
|
||||
// return reject(err);
|
||||
// }
|
||||
//
|
||||
// return resolve(connection);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// // Used to explicitly close a connection, called internally by the pool manager
|
||||
// // when a connection times out or the pool is shutdown.
|
||||
// destroyRawConnection(connection) {
|
||||
// // this.printDebug('destroying raw connection');
|
||||
//
|
||||
// return connection.closeAsync();
|
||||
// }
|
||||
//
|
||||
// validateConnection(connection) {
|
||||
// return Promise.resolve(connection.connected);
|
||||
// }
|
||||
//
|
||||
// _stream(connection, obj, stream, options) {
|
||||
// this._stream(connection, obj, stream, options);
|
||||
// throw new Error('Not yet implemented');
|
||||
// }
|
||||
//
|
||||
// _getConnectionString(connectionConfig = {}) {
|
||||
// const connectionStringParams = connectionConfig.connectionStringParams || {};
|
||||
// const connectionStringExtension = Object.keys(connectionStringParams).reduce((result, key) => {
|
||||
// const value = connectionStringParams[key];
|
||||
// return `${result}${key}=${value};`;
|
||||
// }, '');
|
||||
//
|
||||
// const connectionString = `${
|
||||
// `DRIVER=${connectionConfig.driver};SYSTEM=${connectionConfig.host};HOSTNAME=${connectionConfig.host};` +
|
||||
// `PORT=${connectionConfig.port};DATABASE=${connectionConfig.database};` +
|
||||
// `UID=${connectionConfig.user};PWD=${connectionConfig.password};`
|
||||
// }${connectionStringExtension}`;
|
||||
//
|
||||
// return connectionString;
|
||||
// }
|
||||
//
|
||||
// // Runs the query on the specified connection, providing the bindings
|
||||
// // and any other necessary prep work.
|
||||
// _query(connection, obj) {
|
||||
// // TODO: verify correctness
|
||||
// if (!obj || typeof obj === 'string') obj = { sql: obj };
|
||||
//
|
||||
// const method = (obj.method !== 'raw' ? obj.method : obj.sql.split(' ')[0]).toLowerCase();
|
||||
//
|
||||
// obj.sqlMethod = method;
|
||||
//
|
||||
// // Different functions are used since query() doesn't return # of rows affected,
|
||||
// // which is needed for queries that modify the database
|
||||
// if (method === 'select' || method === 'first' || method === 'pluck') {
|
||||
// return connection.queryAsync(obj.sql, obj.bindings).then(rows => {
|
||||
// obj.response = {
|
||||
// rows,
|
||||
// rowCount: rows.length,
|
||||
// };
|
||||
//
|
||||
// return obj;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return connection
|
||||
// .prepareAsync(obj.sql)
|
||||
// .then(statement => statement.executeNonQueryAsync(obj.bindings))
|
||||
// .then(numRowsAffected => {
|
||||
// obj.response = {
|
||||
// rowCount: numRowsAffected,
|
||||
// };
|
||||
//
|
||||
// return obj;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// // Process / normalize the response as returned from the query
|
||||
// processResponse(obj, runner) {
|
||||
// // TODO: verify correctness
|
||||
//
|
||||
// if (obj === null) return null;
|
||||
//
|
||||
// const resp = obj.response;
|
||||
// const method = obj.sqlMethod;
|
||||
// const { rows } = resp;
|
||||
//
|
||||
// if (obj.output) return obj.output.call(runner, resp);
|
||||
//
|
||||
// switch (method) {
|
||||
// case 'select':
|
||||
// case 'pluck':
|
||||
// case 'first': {
|
||||
// if (method === 'pluck') return rows.map(obj.pluck);
|
||||
// return method === 'first' ? rows[0] : rows;
|
||||
// }
|
||||
// case 'insert':
|
||||
// case 'del':
|
||||
// case 'delete':
|
||||
// case 'update':
|
||||
// case 'counter':
|
||||
// return resp.rowCount;
|
||||
// default:
|
||||
// return resp;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// module.exports = { DB2Client };
|
||||
@@ -0,0 +1,17 @@
|
||||
// import Transaction from 'knex/lib/execution/transaction';
|
||||
//
|
||||
// class TransactionDb2 extends Transaction {
|
||||
// begin(conn) {
|
||||
// return conn.beginTransactionAsync().then(this._resolver).catch(this._rejecter);
|
||||
// }
|
||||
//
|
||||
// commit(conn, value) {
|
||||
// this._completed = true;
|
||||
// return conn
|
||||
// .commitTransactionAsync()
|
||||
// .then(() => this._resolver(value))
|
||||
// .catch(this._rejecter);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// module.exports = TransactionDb2;
|
||||
84
backend-api/src/connection/knex-dialects/knex-ibm-db.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
// // import Client from "knex/lib/client";
|
||||
//
|
||||
// import { Knex, knex } from 'knex';
|
||||
// import { Database } from 'ibm_db';
|
||||
// import Client = knex.Client;
|
||||
//
|
||||
// export class IbmDbClient extends Client {
|
||||
// constructor(config) {
|
||||
// super(config);
|
||||
// this.dialect = 'ibm_db';
|
||||
// this.driverName = 'ibm_db';
|
||||
// this.canCancelQuery = true;
|
||||
// }
|
||||
//
|
||||
// _driver() {
|
||||
// return new Database();
|
||||
// }
|
||||
//
|
||||
// acquireRawConnection() {
|
||||
// return Promise.resolve({
|
||||
// driver: this.driver,
|
||||
// job: null,
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// // validateConnection(connection) {
|
||||
// // return (
|
||||
// // connection &&
|
||||
// // !connection._fatalError &&
|
||||
// // !connection._protocolError &&
|
||||
// // !connection._closing &&
|
||||
// // !connection.stream.destroyed
|
||||
// // );
|
||||
// // }
|
||||
//
|
||||
// destroyRawConnection(connection) {
|
||||
// return this.cancelJob(connection);
|
||||
// }
|
||||
//
|
||||
// wrapIdentifier(value) {
|
||||
// return value !== '*' ? `\`${value}\`` : '*';
|
||||
// }
|
||||
//
|
||||
// cancelJob(connection) {
|
||||
// if (connection.job === null) {
|
||||
// return Promise.resolve();
|
||||
// }
|
||||
// const cancelJobRequest = connection.job.cancel();
|
||||
// connection.job = null;
|
||||
// return cancelJobRequest;
|
||||
// }
|
||||
//
|
||||
// _query(connection, obj) {
|
||||
// const queryConfig = {
|
||||
// ...obj.options,
|
||||
// query: obj.sql,
|
||||
// params: obj.bindings,
|
||||
// };
|
||||
//
|
||||
// return this.createJob(connection, queryConfig)
|
||||
// .then(connection => this.getJobResults(connection, obj))
|
||||
// .catch(err => {
|
||||
// this.cancelJob(connection);
|
||||
// throw err;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// createJob(connection, queryConfig) {
|
||||
// return Promise.resolve(
|
||||
// connection.driver.createQueryJob(queryConfig).then(res => {
|
||||
// connection.job = res[0];
|
||||
// return connection;
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// getJobResults(connection, obj) {
|
||||
// return connection.job.getQueryResults({ autoPaginate: false }).then(res => {
|
||||
// obj.response = res[0];
|
||||
// connection.job = null;
|
||||
// return obj;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
238
backend-api/src/connection/knex-dialects/snowflake/index.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
// import * as Promise from 'bluebird';
|
||||
import { Knex, knex } from 'knex';
|
||||
import { defer, fromPairs, isArray, toPairs } from 'lodash';
|
||||
import * as ColumnBuilder from 'knex/lib/schema/columnbuilder';
|
||||
import * as ColumnCompiler_MySQL from 'knex/lib/dialects/mysql/schema/mysql-columncompiler';
|
||||
import * as Transaction from 'knex/lib/execution/transaction';
|
||||
import { promisify } from 'util';
|
||||
|
||||
export class SnowflakeDialect extends knex.Client {
|
||||
constructor(
|
||||
config = {
|
||||
dialect: 'snowflake',
|
||||
driverName: 'snowflake-sdk',
|
||||
} as any,
|
||||
) {
|
||||
if (config.connection) {
|
||||
if (config.connection.user && !config.connection.username) {
|
||||
config.connection.username = config.connection.user;
|
||||
}
|
||||
if (config.connection.host) {
|
||||
const [account, region] = config.connection.host.split('.');
|
||||
if (!config.connection.account) {
|
||||
config.connection.account = account;
|
||||
}
|
||||
if (!config.connection.region) {
|
||||
config.connection.region = region;
|
||||
}
|
||||
}
|
||||
}
|
||||
super(config);
|
||||
}
|
||||
|
||||
transaction(container: any, config: any, outerTx: any): Knex.Transaction {
|
||||
const transax = new Transaction(this, container, config, outerTx);
|
||||
transax.savepoint = (conn: any) => {
|
||||
// @ts-ignore
|
||||
transax.trxClient.logger('Snowflake does not support savepoints.');
|
||||
};
|
||||
|
||||
transax.release = (conn: any, value: any) => {
|
||||
// @ts-ignore
|
||||
transax.trxClient.logger('Snowflake does not support savepoints.');
|
||||
};
|
||||
|
||||
transax.rollbackTo = (conn: any, error: any) => {
|
||||
// @ts-ignore
|
||||
this.trxClient.logger('Snowflake does not support savepoints.');
|
||||
};
|
||||
return transax;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
// queryCompiler(builder: any, formatter: any) {
|
||||
// return new QueryCompiler(this, builder, formatter);
|
||||
// }
|
||||
|
||||
columnBuilder(tableBuilder: any, type: any, args: any) {
|
||||
// ColumnBuilder methods are created at runtime, so that it does not play well with TypeScript.
|
||||
// So instead of extending ColumnBuilder, we override methods at runtime here
|
||||
const columnBuilder = new ColumnBuilder(this, tableBuilder, type, args);
|
||||
columnBuilder.primary = (constraintName?: string | undefined): Knex.ColumnBuilder => {
|
||||
// @ts-ignore
|
||||
columnBuilder.notNullable();
|
||||
return columnBuilder;
|
||||
};
|
||||
columnBuilder.index = (indexName?: string | undefined): Knex.ColumnBuilder => {
|
||||
// @ts-ignore
|
||||
columnBuilder.client.logger.warn('Snowflake does not support the creation of indexes.');
|
||||
return columnBuilder;
|
||||
};
|
||||
|
||||
return columnBuilder;
|
||||
}
|
||||
|
||||
columnCompiler(tableCompiler: any, columnBuilder: any) {
|
||||
// ColumnCompiler methods are created at runtime, so that it does not play well with TypeScript.
|
||||
// So instead of extending ColumnCompiler, we override methods at runtime here
|
||||
const columnCompiler = new ColumnCompiler_MySQL(
|
||||
this,
|
||||
tableCompiler.tableBuilder,
|
||||
columnBuilder,
|
||||
);
|
||||
columnCompiler.increments = 'int not null autoincrement primary key';
|
||||
columnCompiler.bigincrements = 'bigint not null autoincrement primary key';
|
||||
|
||||
columnCompiler.mediumint = (colName: string) => 'integer';
|
||||
columnCompiler.decimal = (colName: string, precision?: number, scale?: number) => {
|
||||
if (precision) {
|
||||
return ColumnCompiler_MySQL.prototype.decimal(colName, precision, scale);
|
||||
}
|
||||
return 'decimal';
|
||||
};
|
||||
columnCompiler.double = (colName: string, precision?: number, scale?: number) => {
|
||||
if (precision) {
|
||||
return ColumnCompiler_MySQL.prototype.decimal(colName, precision, scale);
|
||||
}
|
||||
return 'double';
|
||||
};
|
||||
columnCompiler.enu = (colName: string, values: string[]) => 'varchar';
|
||||
columnCompiler.json = columnCompiler.jsonb = (colName: string) => 'variant';
|
||||
return columnCompiler;
|
||||
}
|
||||
|
||||
// tableCompiler(tableBuilder: any) {
|
||||
// return new TableCompiler(this, tableBuilder);
|
||||
// }
|
||||
//
|
||||
// schemaCompiler(builder: any) {
|
||||
// return new SchemaCompiler(this, builder);
|
||||
// }
|
||||
|
||||
_driver() {
|
||||
const Snowflake = require('snowflake-sdk');
|
||||
return Snowflake;
|
||||
}
|
||||
|
||||
// Get a raw connection, called by the `pool` whenever a new
|
||||
// connection needs to be added to the pool.
|
||||
acquireRawConnection() {
|
||||
return new Promise((resolver, rejecter) => {
|
||||
// @ts-ignore
|
||||
const connection = this.driver.createConnection(this.connectionSettings);
|
||||
connection.on('error', err => {
|
||||
connection.__knex__disposed = err;
|
||||
});
|
||||
connection.connect(err => {
|
||||
if (err) {
|
||||
// if connection is rejected, remove listener that was registered above...
|
||||
connection.removeAllListeners();
|
||||
return rejecter(err);
|
||||
}
|
||||
resolver(connection);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Used to explicitly close a connection, called internally by the pool
|
||||
// when a connection times out or the pool is shutdown.
|
||||
async destroyRawConnection(connection): Promise<void> {
|
||||
try {
|
||||
const end = promisify(cb => connection.end(cb));
|
||||
await end();
|
||||
} catch (err) {
|
||||
connection.__knex__disposed = err;
|
||||
} finally {
|
||||
// see discussion https://github.com/knex/knex/pull/3483
|
||||
defer(() => connection.removeAllListeners());
|
||||
}
|
||||
}
|
||||
|
||||
async validateConnection(connection: any): Promise<boolean> {
|
||||
if (connection) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Runs the query on the specified connection, providing the bindings
|
||||
// and any other necessary prep work.
|
||||
_query(connection: any, obj: any) {
|
||||
if (!obj || typeof obj === 'string') obj = { sql: obj };
|
||||
return new Promise((resolver: any, rejecter: any) => {
|
||||
if (!obj.sql) {
|
||||
resolver();
|
||||
return;
|
||||
}
|
||||
|
||||
const queryOptions = {
|
||||
sqlText: obj.sql,
|
||||
binds: obj.bindings,
|
||||
complete(err: any, statement: any, rows: any) {
|
||||
if (err) return rejecter(err);
|
||||
obj.response = { rows, statement };
|
||||
resolver(obj);
|
||||
},
|
||||
...obj.options,
|
||||
};
|
||||
connection.execute(queryOptions);
|
||||
});
|
||||
}
|
||||
|
||||
// Ensures the response is returned in the same format as other clients.
|
||||
processResponse(obj: any, runner: any) {
|
||||
const resp = obj.response;
|
||||
if (obj.output) return obj.output.call(runner, resp);
|
||||
if (obj.method === 'raw') return resp;
|
||||
if (obj.method === 'select') {
|
||||
// if (obj.method === 'first') return resp.rows[0];
|
||||
// if (obj.method === 'pluck') return map(resp.rows, obj.pluck);
|
||||
return resp.rows;
|
||||
}
|
||||
if (obj.method === 'insert' || obj.method === 'update' || obj.method === 'delete') {
|
||||
if (resp.rows) {
|
||||
const method = obj.method === 'insert' ? 'inserte' : obj.method;
|
||||
return resp.rows.reduce((count, row) => count + row[`number of rows ${method}d`], 0);
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
if (resp.statement && resp.rows) {
|
||||
return resp.rows;
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
postProcessResponse(result, queryContext) {
|
||||
if (this.config.postProcessResponse) {
|
||||
return this.config.postProcessResponse(result, queryContext);
|
||||
}
|
||||
// Snowflake returns column names in uppercase, convert to lowercase
|
||||
// (to conform with knex, e.g. schema migrations)
|
||||
const lowercaseAttrs = (row: any) => {
|
||||
return fromPairs(toPairs(row).map(([key, value]) => [key.toLowerCase(), value]));
|
||||
};
|
||||
if (result.rows) {
|
||||
return {
|
||||
...result,
|
||||
rows: result.rows.map(lowercaseAttrs),
|
||||
};
|
||||
} else if (isArray(result)) {
|
||||
return result.map(lowercaseAttrs);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
customWrapIdentifier(value, origImpl, queryContext) {
|
||||
if (this.config.wrapIdentifier) {
|
||||
return this.config.wrapIdentifier(value, origImpl, queryContext);
|
||||
} else if (!value.startsWith('"')) {
|
||||
return origImpl(value.toUpperCase());
|
||||
}
|
||||
return origImpl;
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(SnowflakeDialect.prototype, {
|
||||
// The "dialect", for reference elsewhere.
|
||||
driverName: 'snowflake-sdk',
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// @ts-ignore
|
||||
import * as QueryCompiler_MySQL from 'knex/lib/dialects/mysql/query/mysql-querycompiler';
|
||||
|
||||
export class QueryCompiler extends QueryCompiler_MySQL {
|
||||
constructor(client: any, builder: any, formatter: any) {
|
||||
super(client, builder, formatter);
|
||||
}
|
||||
|
||||
forUpdate() {
|
||||
// @ts-ignore
|
||||
this.client.logger.warn('table lock is not supported by snowflake dialect');
|
||||
return '';
|
||||
}
|
||||
|
||||
forShare() {
|
||||
// @ts-ignore
|
||||
this.client.logger.warn('lock for share is not supported by snowflake dialect');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// @ts-ignore
|
||||
import * as SchemaCompiler_MySQL from "knex/lib/dialects/mysql/schema/mysql-compiler";
|
||||
|
||||
export class SchemaCompiler extends SchemaCompiler_MySQL {
|
||||
constructor(client: any, builder: any) {
|
||||
super(client, builder);
|
||||
}
|
||||
|
||||
// Check whether a table exists on the query.
|
||||
hasTable(tableName: string) {
|
||||
const [ schema, table ] = tableName.includes(".") ? tableName.split(".") : [undefined, tableName];
|
||||
let sql = 'select * from information_schema.tables where table_name = ?';
|
||||
const bindings = [table.toUpperCase()];
|
||||
|
||||
if (schema) {
|
||||
sql += ' and table_schema = ?';
|
||||
bindings.push(schema.toUpperCase());
|
||||
} else {
|
||||
sql += ' and table_schema = current_schema()';
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this.pushQuery({
|
||||
sql,
|
||||
bindings,
|
||||
output: (resp) => resp.rows.length > 0
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// @ts-ignore
|
||||
import * as TableCompiler_MySQL from "knex/lib/dialects/mysql/schema/mysql-tablecompiler";
|
||||
|
||||
export class TableCompiler extends TableCompiler_MySQL {
|
||||
constructor(client: any, builder: any) {
|
||||
super(client, builder);
|
||||
}
|
||||
|
||||
index(columns, indexName, indexType) {
|
||||
// @ts-ignore
|
||||
this.client.logger.warn('Snowflake does not support the creation of indexes.');
|
||||
};
|
||||
|
||||
dropIndex(columns, indexName) {
|
||||
// @ts-ignore
|
||||
this.client.logger.warn('Snowflake does not support the deletion of indexes.');
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./SchemaCompiler";
|
||||
export * from "./TableCompiler";
|
||||
1
backend-api/src/connection/knex-dialects/snowflake/snowflake-sdk.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module "snowflake-sdk";
|
||||
@@ -5,7 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Dashboard } from './entities/dashboard.entity';
|
||||
import { DashboardWidget } from './dashboard-widget/entities/dashboard-widget.entity';
|
||||
import { DashboardWidgetService } from './dashboard-widget/dashboard-widget.service';
|
||||
import { Widget } from 'src/widget/entities/widget.entity';
|
||||
import { Widget } from '../widget/entities/widget.entity';
|
||||
import { Component } from '../component/entities/component.entity';
|
||||
|
||||
@Module({
|
||||
|
||||
@@ -42,6 +42,7 @@ export class DashboardService {
|
||||
const find_all = await this.dashboardRepository.find({
|
||||
order: {
|
||||
updatedAt: 'desc',
|
||||
title: 'asc',
|
||||
},
|
||||
});
|
||||
find_all.forEach(el => {
|
||||
|
||||
@@ -24,12 +24,24 @@ export class DatabaseController {
|
||||
@Get('/data')
|
||||
async findData(
|
||||
@Query('datasetType') datasetType: DatasetType,
|
||||
@Query('datasetId') datasetId: number,
|
||||
@Query('databaseId') databaseId: number,
|
||||
@Query('datasetId') datasetId?: number,
|
||||
@Query('tableName') tableName?: string,
|
||||
) {
|
||||
const res = await this.databaseService.findData(datasetType, datasetId);
|
||||
const res = await this.databaseService.findData(datasetType, databaseId, datasetId, tableName);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터베이스 연결정보 단순조회
|
||||
* @param id
|
||||
*/
|
||||
@Get('/info/:id')
|
||||
async findOneInfo(@Param('id') id: string) {
|
||||
const databaseInfo = await this.databaseService.findOneInfo(+id);
|
||||
return databaseInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터베이스 생성 ( 데이터소스 생성)
|
||||
* @param createDatabaseDto
|
||||
|
||||
@@ -10,8 +10,8 @@ import { ResponseStatus } from '../common/enum/response-status.enum';
|
||||
import { DatasetType } from '../common/enum/dataset-type.enum';
|
||||
import { TableQuery } from '../widget/tabel-query/entity/table-query.entity';
|
||||
import { QueryExecuteDto } from './dto/query-execute.dto';
|
||||
import {DatabaseType} from "./entities/database_type.entity";
|
||||
import {YesNo} from "../common/enum/yn.enum";
|
||||
import { DatabaseType } from './entities/database_type.entity';
|
||||
import { YesNo } from '../common/enum/yn.enum';
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseService {
|
||||
@@ -26,8 +26,8 @@ export class DatabaseService {
|
||||
/**
|
||||
* database type 목록 조회
|
||||
*/
|
||||
async findTypeList(){
|
||||
const result = await this.databaseTypeRepository.find({where:{useYn:YesNo.YES}});
|
||||
async findTypeList() {
|
||||
const result = await this.databaseTypeRepository.find({ where: { useYn: YesNo.YES } });
|
||||
return { status: ResponseStatus.SUCCESS, data: result };
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@ export class DatabaseService {
|
||||
connection: databaseDto.connectionConfig,
|
||||
useNullAsDefault: true,
|
||||
};
|
||||
if (connectionConfig.client === 'cockroachdb') {
|
||||
const connectioninfo = connectionConfig.connection;
|
||||
const cockroach_url = `postgresql://${connectioninfo['user']}:${connectioninfo['password']}@${connectioninfo['host']}:${connectioninfo['port']}/${connectioninfo['database']}?sslmode=verify-full&options=--cluster%3Dvanillameta-cockroach-3010`;
|
||||
connectioninfo['connectionString'] = cockroach_url;
|
||||
}
|
||||
|
||||
databaseDto.connectionConfig = JSON.stringify(connectionConfig);
|
||||
databaseDto.timezone = 'Asia/Seoul';
|
||||
|
||||
@@ -72,24 +78,76 @@ export class DatabaseService {
|
||||
databaseInfo.connectionConfig = JSON.parse(databaseInfo.connectionConfig).connection;
|
||||
|
||||
// table 정보 조회
|
||||
const tablesInfo = await this.connectionService.executeQuery({ id: +id, query: 'show tables' });
|
||||
let selectTableQuery;
|
||||
switch (databaseInfo.engine) {
|
||||
case 'mysql2':
|
||||
selectTableQuery = 'show tables';
|
||||
break;
|
||||
case 'pg':
|
||||
selectTableQuery = `SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' and table_schema not in ('information_schema', 'pg_catalog', 'pg_internal')`;
|
||||
break;
|
||||
case 'sqlite3':
|
||||
selectTableQuery = `SELECT tbl_name FROM sqlite_master WHERE type = 'table'`;
|
||||
break;
|
||||
case 'mssql':
|
||||
selectTableQuery = 'SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES';
|
||||
break;
|
||||
case 'bigquery':
|
||||
selectTableQuery = `select table_id from ${databaseInfo.connectionConfig['schema']}.__TABLES__`;
|
||||
break;
|
||||
case 'oracledb':
|
||||
selectTableQuery = 'SELECT table_name FROM user_tables ORDER BY table_name';
|
||||
break;
|
||||
case 'snowflake':
|
||||
selectTableQuery = `select table_name from information_schema.tables where table_type = 'BASE TABLE'`;
|
||||
break;
|
||||
case 'cockroachdb':
|
||||
selectTableQuery = `SELECT TABLE_NAME FROM information_schema.tables WHERE table_type = 'BASE TABLE'`;
|
||||
break;
|
||||
default:
|
||||
selectTableQuery = 'show tables';
|
||||
break;
|
||||
}
|
||||
|
||||
const tablesInfo = await this.connectionService.executeQuery({
|
||||
id: +id,
|
||||
query: selectTableQuery,
|
||||
});
|
||||
const tables = [];
|
||||
if (tablesInfo && tablesInfo.datas.length > 0) {
|
||||
tablesInfo.datas.map(tableObj => {
|
||||
tables.push({ id: Object.values(tableObj)[0], tableName: Object.values(tableObj)[0], databaseId: id, datasetType:DatasetType.TABLE });
|
||||
tables.push({
|
||||
id: Object.values(tableObj)[0],
|
||||
tableName: Object.values(tableObj)[0],
|
||||
databaseId: id,
|
||||
datasetType: DatasetType.TABLE,
|
||||
});
|
||||
});
|
||||
} else if (tablesInfo && tablesInfo.status === ResponseStatus.ERROR) {
|
||||
return tablesInfo;
|
||||
}
|
||||
|
||||
// dataset 정보 조회
|
||||
const tempDatasets = await this.datasetRepository.find({ where: { databaseId: id } });
|
||||
const datasets = [];
|
||||
tempDatasets.map(item => {
|
||||
datasets.push(Object.assign({datasetType:DatasetType.DATASET}, item));
|
||||
})
|
||||
datasets.push(Object.assign({ datasetType: DatasetType.DATASET }, item));
|
||||
});
|
||||
|
||||
return { status: ResponseStatus.SUCCESS, data: { databaseInfo, tables, datasets } };
|
||||
}
|
||||
|
||||
/**
|
||||
* database 정보 단순 조회
|
||||
* @param id
|
||||
*/
|
||||
async findOneInfo(id: number): Promise<any> {
|
||||
// 연동 db 정보
|
||||
const databaseInfo = await this.databaseRepository.findOne({ where: { id } });
|
||||
databaseInfo.connectionConfig = JSON.parse(databaseInfo.connectionConfig).connection;
|
||||
return { status: ResponseStatus.SUCCESS, data: { databaseInfo } };
|
||||
}
|
||||
|
||||
/**
|
||||
* db config 정보 단순 조회
|
||||
* @param id
|
||||
@@ -109,13 +167,26 @@ export class DatabaseService {
|
||||
message: `조건에 맞는 데이터베이스를 찾지 못했습니다. id:${id}`,
|
||||
};
|
||||
|
||||
if (updateDatabaseDto.engine === 'cockroachdb') {
|
||||
const connectioninfo = updateDatabaseDto.connectionConfig;
|
||||
const cockroach_url = `postgresql://${connectioninfo['user']}:${connectioninfo['password']}@${connectioninfo['host']}:${connectioninfo['port']}/${connectioninfo['database']}?sslmode=verify-full&options=--cluster%3Dvanillameta-cockroach-3010`;
|
||||
connectioninfo['connectionString'] = cockroach_url;
|
||||
}
|
||||
|
||||
const connectionConfig = {
|
||||
client: one.engine,
|
||||
connection: Object(one.connectionConfig).connection,
|
||||
connection: updateDatabaseDto.connectionConfig,
|
||||
useNullAsDefault: true,
|
||||
};
|
||||
updateDatabaseDto.connectionConfig = JSON.stringify(connectionConfig);
|
||||
|
||||
// const connectionConfig = {
|
||||
// client: one.engine,
|
||||
// connection: Object(one.connectionConfig).connection,
|
||||
// useNullAsDefault: true,
|
||||
// };
|
||||
// updateDatabaseDto.connectionConfig = JSON.stringify(connectionConfig);
|
||||
|
||||
const saveResult = await this.databaseRepository.update(
|
||||
{ id },
|
||||
{ name: updateDatabaseDto.name, connectionConfig: updateDatabaseDto.connectionConfig },
|
||||
@@ -135,21 +206,57 @@ export class DatabaseService {
|
||||
status: ResponseStatus.ERROR,
|
||||
message: `조건에 맞는 데이터베이스를 찾지 못했습니다. id:${id}`,
|
||||
};
|
||||
|
||||
// 연관된 widget 제거
|
||||
const deletedWidget = await this.datasetRepository.query(
|
||||
`delete from widget
|
||||
where ((datasetType = 'DATASET' and datasetId in (select id from dataset where databaseId = ?)) or
|
||||
(datasetType = 'TABLE' and datasetId in (select id from table_query where databaseId = ?)))`,
|
||||
[id, id],
|
||||
);
|
||||
// table query 삭제
|
||||
await this.tableQueryRepository.delete({ databaseId: id });
|
||||
// dataset 삭제
|
||||
const deletedDataset = await this.datasetRepository.delete({ databaseId: id });
|
||||
// database 삭제
|
||||
await this.databaseRepository.remove(one);
|
||||
return { status: ResponseStatus.SUCCESS, data: { message: `${id} 삭제 완료` } };
|
||||
|
||||
return {
|
||||
status: ResponseStatus.SUCCESS,
|
||||
data: {
|
||||
message: `${deletedWidget.affected}개의 widget, ${deletedDataset.affected}개의 dataset, databse [${one.name}] 삭제 완료`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터 조회
|
||||
* @param datasetType
|
||||
* @param databaseId
|
||||
* @param datasetId
|
||||
* @param tableName
|
||||
*/
|
||||
async findData(datasetType: DatasetType, datasetId: number) {
|
||||
if (!datasetType || !datasetId)
|
||||
async findData(
|
||||
datasetType: DatasetType,
|
||||
databaseId: number,
|
||||
datasetId?: number,
|
||||
tableName?: string,
|
||||
) {
|
||||
if (datasetType === DatasetType.DATASET && datasetId === undefined) {
|
||||
return {
|
||||
status: ResponseStatus.ERROR,
|
||||
message: 'datasetType, datasetId는 필수 입력 param 입니다',
|
||||
message: 'DATASET의 경우, datasetId가 필수 입력 사항입니다.',
|
||||
};
|
||||
} else if (
|
||||
datasetType === DatasetType.TABLE &&
|
||||
tableName === undefined &&
|
||||
datasetId === undefined
|
||||
) {
|
||||
return {
|
||||
status: ResponseStatus.ERROR,
|
||||
message: 'TABLE의 경우, tableName이나 datasetId 둘 중 하나는 입력해야합니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const queryExecuteDto = new QueryExecuteDto();
|
||||
if (datasetType === DatasetType.DATASET) {
|
||||
@@ -157,14 +264,35 @@ export class DatabaseService {
|
||||
queryExecuteDto.id = datasetItem.databaseId;
|
||||
queryExecuteDto.query = datasetItem.query;
|
||||
} else if (datasetType === DatasetType.TABLE) {
|
||||
const datasetItem = await this.tableQueryRepository.findOne({ where: { id: datasetId } });
|
||||
queryExecuteDto.id = datasetItem.databaseId;
|
||||
queryExecuteDto.query = datasetItem.query;
|
||||
if (datasetId != undefined) {
|
||||
const datasetItem = await this.tableQueryRepository.findOne({ where: { id: datasetId } });
|
||||
queryExecuteDto.id = datasetItem.databaseId;
|
||||
queryExecuteDto.query = datasetItem.query;
|
||||
} else {
|
||||
const databaseOne = await this.databaseRepository.findOne({ where: { id: databaseId } });
|
||||
let selectQuery;
|
||||
switch (databaseOne.type) {
|
||||
case 'bigquery':
|
||||
const schemaName = JSON.parse(databaseOne.connectionConfig).connection.schema;
|
||||
selectQuery = `SELECT * FROM ${schemaName}.${tableName}`;
|
||||
break;
|
||||
case 'oracle':
|
||||
selectQuery = `SELECT * FROM "${tableName}"`;
|
||||
break;
|
||||
default:
|
||||
selectQuery = `SELECT * FROM ${tableName}`;
|
||||
break;
|
||||
}
|
||||
queryExecuteDto.id = databaseId;
|
||||
queryExecuteDto.query = selectQuery;
|
||||
}
|
||||
}
|
||||
const queryResult = await this.connectionService.executeQuery(queryExecuteDto);
|
||||
return {
|
||||
status: queryResult.status,
|
||||
data: { datas: queryResult.datas, fields: queryResult.fields },
|
||||
};
|
||||
if (queryResult.status === 'ERROR') return queryResult;
|
||||
else
|
||||
return {
|
||||
status: queryResult.status,
|
||||
data: { datas: queryResult.datas, fields: queryResult.fields },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,14 @@ export class CreateDatabaseDto {
|
||||
})
|
||||
engine: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
example: 'mysql',
|
||||
description: '데이터베이스 구분',
|
||||
})
|
||||
type: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
|
||||
@@ -16,9 +16,12 @@ export class Database extends BaseEntity {
|
||||
@Column({ type: 'text', comment: '속성' })
|
||||
connectionConfig: string; // 기타 속성 json으로 .. host, schema, filePath...
|
||||
|
||||
@Column({ length: 100, comment: '데이터베이스 구분' })
|
||||
@Column({ length: 100, comment: '데이터베이스 엔진' })
|
||||
engine: string;
|
||||
|
||||
@Column({ length: 100, comment: '데이터베이스 구분' })
|
||||
type: string;
|
||||
|
||||
@Column({ length: 100, comment: '타임존', nullable: true })
|
||||
timezone: string;
|
||||
|
||||
@@ -27,6 +30,7 @@ export class Database extends BaseEntity {
|
||||
description: string,
|
||||
details: string,
|
||||
engine: string,
|
||||
type: string,
|
||||
timezone: string,
|
||||
): Database {
|
||||
const obj = new Database();
|
||||
@@ -34,12 +38,20 @@ export class Database extends BaseEntity {
|
||||
obj.description = description;
|
||||
obj.connectionConfig = details;
|
||||
obj.engine = engine;
|
||||
obj.type = type;
|
||||
obj.timezone = timezone;
|
||||
return obj;
|
||||
}
|
||||
|
||||
static toDto(dto: CreateDatabaseDto): Database {
|
||||
return Database.of(dto.name, dto.description, dto.connectionConfig, dto.engine, dto.timezone);
|
||||
return Database.of(
|
||||
dto.name,
|
||||
dto.description,
|
||||
dto.connectionConfig,
|
||||
dto.engine,
|
||||
dto.type,
|
||||
dto.timezone,
|
||||
);
|
||||
}
|
||||
|
||||
getFullDescription(): string {
|
||||
|
||||
@@ -5,9 +5,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Dataset } from './entities/dataset.entity';
|
||||
import { ConnectionService } from '../connection/connection.service';
|
||||
import { Database } from '../database/entities/database.entity';
|
||||
import { Widget } from '../widget/entities/widget.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Dataset, Database])],
|
||||
imports: [TypeOrmModule.forFeature([Dataset, Database, Widget])],
|
||||
controllers: [DatasetController],
|
||||
providers: [DatasetService, ConnectionService],
|
||||
})
|
||||
|
||||
@@ -6,12 +6,16 @@ import { Repository } from 'typeorm';
|
||||
import { Dataset } from './entities/dataset.entity';
|
||||
import { ConnectionService } from '../connection/connection.service';
|
||||
import { ResponseStatus } from '../common/enum/response-status.enum';
|
||||
import { Widget } from '../widget/entities/widget.entity';
|
||||
import { DatasetType } from '../common/enum/dataset-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class DatasetService {
|
||||
constructor(
|
||||
@InjectRepository(Dataset)
|
||||
private datasetRepository: Repository<Dataset>,
|
||||
@InjectRepository(Widget)
|
||||
private widgetRepository: Repository<Widget>,
|
||||
private readonly connectionService: ConnectionService,
|
||||
) {}
|
||||
|
||||
@@ -51,8 +55,9 @@ export class DatasetService {
|
||||
let returnObj: any;
|
||||
const dataObj = await this.datasetRepository.findOne({ where: { id: id } });
|
||||
|
||||
if (!dataObj) returnObj = {status: ResponseStatus.ERROR, message: `id ${id}의 값이 존재하지 않습니다.`};
|
||||
else returnObj = {status:ResponseStatus.SUCCESS, data: dataObj};
|
||||
if (!dataObj)
|
||||
returnObj = { status: ResponseStatus.ERROR, message: `id ${id}의 값이 존재하지 않습니다.` };
|
||||
else returnObj = { status: ResponseStatus.SUCCESS, data: dataObj };
|
||||
return returnObj;
|
||||
}
|
||||
|
||||
@@ -89,6 +94,10 @@ export class DatasetService {
|
||||
return 'Not exist dataset';
|
||||
} else {
|
||||
await this.datasetRepository.delete(find_dataset.id);
|
||||
await this.widgetRepository.delete({
|
||||
datasetType: DatasetType.DATASET,
|
||||
datasetId: find_dataset.id,
|
||||
});
|
||||
}
|
||||
return `This action removes a #${id} dataset`;
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// import {Connection} from "typeorm";
|
||||
// import {Seeder, Factory} from 'typeorm-seeding';
|
||||
// import {Component} from "../component/entities/component.entity";
|
||||
//
|
||||
//
|
||||
// export class CreateInitialData implements Seeder {
|
||||
// public async run(factory: Factory, connection: Connection): Promise<any> {
|
||||
// await connection
|
||||
// .createQueryBuilder()
|
||||
// .insert()
|
||||
// .into(Component)
|
||||
// .values([
|
||||
// {
|
||||
// type: 'CHART_LINE',
|
||||
// title: '선형 차트',
|
||||
// category: 'LINE',
|
||||
// icon: 'icon/line-chart.png',
|
||||
// description: 'Line Chart',
|
||||
// option: `{
|
||||
// "title": "test",
|
||||
// "xField": "",
|
||||
// "series": [
|
||||
// {
|
||||
// "field": "",
|
||||
// "color": "#5470c6",
|
||||
// "aggregation": "",
|
||||
// },
|
||||
// ],
|
||||
// "legendPosition": "left",
|
||||
// }`,
|
||||
// },
|
||||
// {
|
||||
// type: 'CHART_AREA',
|
||||
// title: '영역형 차트',
|
||||
// category: 'AREA',
|
||||
// icon: 'icon/area-chart.png',
|
||||
// description: 'Area Chart',
|
||||
// option: `{
|
||||
// "title": "",
|
||||
// "xField": "",
|
||||
// "series": [
|
||||
// {
|
||||
// "field": "",
|
||||
// "color": "#5470c6",
|
||||
// "aggregation": "",
|
||||
// },
|
||||
// ],
|
||||
// "legendPosition": "left",
|
||||
// }`,
|
||||
// }
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
@@ -225,7 +225,6 @@ export class TemplateService {
|
||||
const result = templateComponentInfoList.sort(
|
||||
(a, b) => b.totalRecommendScore - a.totalRecommendScore,
|
||||
);
|
||||
|
||||
return { status: ResponseStatus.SUCCESS, data: result };
|
||||
}
|
||||
|
||||
@@ -289,7 +288,16 @@ export class TemplateService {
|
||||
// if (templateInfo.layout.length > i && template) templateInfo.layout[i].i = item.id;
|
||||
});
|
||||
|
||||
templateInfo.widgets = widgetList;
|
||||
const isWidgetList = widgetList.filter(
|
||||
widgetItem =>
|
||||
templateInfo.layout.findIndex(templateItem => templateItem.i === widgetItem.id) > -1,
|
||||
);
|
||||
const isntWidgetList = widgetList.filter(
|
||||
widgetItem =>
|
||||
templateInfo.layout.findIndex(templateItem => templateItem.i === widgetItem.id) === -1,
|
||||
);
|
||||
|
||||
templateInfo.widgets = isWidgetList.concat(isntWidgetList);
|
||||
|
||||
return { status: ResponseStatus.SUCCESS, data: templateInfo };
|
||||
}
|
||||
@@ -825,16 +833,23 @@ export class TemplateService {
|
||||
}
|
||||
}
|
||||
|
||||
// template에서 넘친 widget 목록 가져오기
|
||||
// // template에서 넘친 widget 목록 가져오기
|
||||
const leastWidgetList = differntWidgetList.filter(item => item.category != 'MAPPED');
|
||||
// templateInfo.layout에 새로운 layout object 넣어주기(template에서 넘친 widget 목록)
|
||||
leastWidgetList.forEach((leastWidget, index) => {
|
||||
const layout = new DashboardLayout();
|
||||
layout.x = 0;
|
||||
layout.y =
|
||||
templateItemList[templateItemList.length - 1].y +
|
||||
templateItemList[templateItemList.length - 1].h;
|
||||
layout.w = 5;
|
||||
if (index % 2 === 0) {
|
||||
layout.x = 0;
|
||||
layout.y =
|
||||
templateItemList[templateItemList.length - 1].y +
|
||||
templateItemList[templateItemList.length - 1].h;
|
||||
} else {
|
||||
layout.x = 6;
|
||||
layout.y =
|
||||
templateItemList[templateItemList.length - 2].y +
|
||||
templateItemList[templateItemList.length - 2].h;
|
||||
}
|
||||
layout.w = 6;
|
||||
layout.h = 5;
|
||||
layout.i = leastWidget.id;
|
||||
templateItemList.push(layout);
|
||||
|
||||
88
backend-api/src/utils/aggregation.util.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as math from 'mathjs';
|
||||
|
||||
export const WIDGET_AGGREGATION = {
|
||||
SUM: 'sum',
|
||||
AVG: 'avg',
|
||||
MAX: 'max',
|
||||
MIN: 'min',
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type
|
||||
* @param data
|
||||
* @param field
|
||||
*/
|
||||
export const getAggregationData = (type, data, field) => {
|
||||
let result = 0;
|
||||
let fits = 0;
|
||||
let dataList = [];
|
||||
if (data.length > 0 && (type === WIDGET_AGGREGATION.MIN || type === WIDGET_AGGREGATION.MAX)) {
|
||||
result = Number(data[0][field]);
|
||||
} else if (data.length > 0 && type === WIDGET_AGGREGATION.SUM) {
|
||||
dataList = data.map(row => row[field]);
|
||||
fits = decimalFits(dataList);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case WIDGET_AGGREGATION.SUM:
|
||||
data.forEach(item => {
|
||||
// console.log('item ', item[field]);
|
||||
if (item[field] != undefined && item[field] != null) {
|
||||
result += Number(item[field]);
|
||||
}
|
||||
});
|
||||
result = Number(result.toFixed(fits));
|
||||
break;
|
||||
case WIDGET_AGGREGATION.AVG:
|
||||
// result = math.mean(dataList);
|
||||
data.forEach(item => {
|
||||
// console.log('item ', item[field]);
|
||||
if (item[field] != undefined && item[field] != null) {
|
||||
result = math.add(result, math.bignumber(item[field]));
|
||||
}
|
||||
});
|
||||
result = math.divide(result, math.bignumber(data.length));
|
||||
// // result = Number(result.toFixed(fits));
|
||||
// // result = Math.round((result / data.length) * 1000000) / 1000000;
|
||||
// result = result / data.length;
|
||||
// result = Number(result);
|
||||
// result = Math.round(result * 1000000) / 1000000;
|
||||
result = Number(result.toFixed(6));
|
||||
break;
|
||||
case WIDGET_AGGREGATION.MAX:
|
||||
data.forEach(item => {
|
||||
// console.log('item ', item[field]);
|
||||
if (item[field] != undefined && item[field] != null) {
|
||||
result = Math.max(result, item[field]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case WIDGET_AGGREGATION.MIN:
|
||||
data.forEach(item => {
|
||||
// console.log('item ', item[field]);
|
||||
if (item[field] != undefined && item[field] != null) {
|
||||
result = Math.min(result, item[field]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
function decimalFits(arr) {
|
||||
//소수점 자리수가 가장많은 수 return
|
||||
var decimalN = 0;
|
||||
for (var j = 0; j < arr.length; j++) {
|
||||
var n = arr[j];
|
||||
if (!Number.isInteger(n)) {
|
||||
//소수
|
||||
var d = String(n).split('.')[1].length; //문자열 소수점 다음 개수
|
||||
if (decimalN < d) decimalN = d;
|
||||
}
|
||||
}
|
||||
return decimalN;
|
||||
}
|
||||
var arr = [0.1, 0.12, 0.123];
|
||||
decimalFits(arr);
|
||||
@@ -2,12 +2,15 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { TableQuery } from './entity/table-query.entity';
|
||||
import { Database } from '../../database/entities/database.entity';
|
||||
|
||||
@Injectable()
|
||||
export class TableQueryService {
|
||||
constructor(
|
||||
@InjectRepository(TableQuery)
|
||||
private tableQueryRepository: Repository<TableQuery>,
|
||||
@InjectRepository(Database)
|
||||
private databaseRepository: Repository<Database>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -16,12 +19,33 @@ export class TableQueryService {
|
||||
* @param tableName
|
||||
*/
|
||||
async create(databaseId: number, tableName: string) {
|
||||
const selectQuery = await this.makeSelectAllQuery(databaseId, tableName);
|
||||
|
||||
return await this.tableQueryRepository.save({
|
||||
databaseId,
|
||||
query: `SELECT * FROM ${tableName}`,
|
||||
query: selectQuery,
|
||||
});
|
||||
}
|
||||
|
||||
async makeSelectAllQuery(databaseId: number, tableName: string) {
|
||||
const databaseOne = await this.databaseRepository.findOne({ where: { id: databaseId } });
|
||||
let selectQuery;
|
||||
switch (databaseOne.type) {
|
||||
case 'bigquery':
|
||||
const schemaName = JSON.parse(databaseOne.connectionConfig).connection.schema;
|
||||
selectQuery = `SELECT * FROM ${schemaName}.${tableName}`;
|
||||
break;
|
||||
case 'oracle':
|
||||
selectQuery = `SELECT * FROM "${tableName}"`;
|
||||
break;
|
||||
default:
|
||||
selectQuery = `SELECT * FROM ${tableName}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return selectQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* tableQuery 삭제
|
||||
* @param id
|
||||
|
||||
@@ -6,9 +6,10 @@ import { Widget } from './entities/widget.entity';
|
||||
import { Component } from '../component/entities/component.entity';
|
||||
import { TableQueryService } from './tabel-query/table-query.service';
|
||||
import { TableQuery } from './tabel-query/entity/table-query.entity';
|
||||
import { Database } from '../database/entities/database.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Widget, Component, TableQuery])],
|
||||
imports: [TypeOrmModule.forFeature([Widget, Component, TableQuery, Database])],
|
||||
controllers: [WidgetController],
|
||||
providers: [WidgetService, TableQueryService],
|
||||
})
|
||||
|
||||
@@ -63,6 +63,7 @@ export class WidgetService {
|
||||
'component.description as componentDescription',
|
||||
])
|
||||
.orderBy('widget.updatedAt', 'DESC')
|
||||
.addOrderBy('widget.title')
|
||||
.getRawMany();
|
||||
|
||||
find_all.forEach(el => {
|
||||
|
||||
@@ -1,5 +1,57 @@
|
||||
describe('MySQL 연동 확인', () => {
|
||||
it('test', () => {
|
||||
return expect(1).toBe(1);
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { getTestMysqlModule } from '../util/get-test-mysql.module';
|
||||
import { Database } from '../../src/database/entities/database.entity';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { Dataset } from '@google-cloud/bigquery';
|
||||
import { TableQuery } from '../../src/widget/tabel-query/entity/table-query.entity';
|
||||
import { DatabaseType } from '../../src/database/entities/database_type.entity';
|
||||
import { ConnectionService } from '../../src/connection/connection.service';
|
||||
import * as TestConnectionInfo from '../../test-connect-info.json';
|
||||
import { ResponseStatus } from '../../src/common/enum/response-status.enum';
|
||||
|
||||
describe('QTT-001: 외부 API 연동', () => {
|
||||
let connectService: ConnectionService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env.dev',
|
||||
}),
|
||||
getTestMysqlModule(),
|
||||
TypeOrmModule.forFeature([Database, Dataset, TableQuery, DatabaseType]),
|
||||
],
|
||||
providers: [ConnectionService],
|
||||
}).compile();
|
||||
|
||||
connectService = module.get<ConnectionService>(ConnectionService);
|
||||
}, 10000);
|
||||
const dbName = [
|
||||
['QTT-001-01', 'mysql'],
|
||||
['QTT-001-02', 'maria'],
|
||||
['QTT-001-03', 'pg'],
|
||||
['QTT-001-04', 'oracle'],
|
||||
['QTT-001-05', 'cockroach'],
|
||||
['QTT-001-06', 'redshift'],
|
||||
['QTT-001-07', 'bigquery'],
|
||||
['QTT-001-08', 'sqlite'],
|
||||
['QTT-001-09', 'mssql'],
|
||||
['QTT-001-10', 'snowflake'],
|
||||
];
|
||||
|
||||
it.each(dbName)('%s : %s', async (name, engine) => {
|
||||
const result = await connectService.testConnection(Object.create(TestConnectionInfo[engine]));
|
||||
return expect(result['status']).toStrictEqual(ResponseStatus.SUCCESS);
|
||||
});
|
||||
|
||||
// for (let i = 0; i < dbName.length; i++) {
|
||||
// it(`QTT-001-${String(i + 1).padStart(2, '0')}`, async () => {
|
||||
// const result = await connectService.testConnection(
|
||||
// Object.create(TestConnectionInfo[dbName[i]]),
|
||||
// );
|
||||
// return expect(result['status']).toStrictEqual(ResponseStatus.SUCCESS);
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
84
backend-api/test/QTT-002/QTT-002-01.spec.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { getTestMysqlModule } from '../util/get-test-mysql.module';
|
||||
import { Database } from '../../src/database/entities/database.entity';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TableQuery } from '../../src/widget/tabel-query/entity/table-query.entity';
|
||||
import { WidgetService } from '../../src/widget/widget.service';
|
||||
import * as widgetTestoption from './widgetTestOption.json';
|
||||
import { Widget } from '../../src/widget/entities/widget.entity';
|
||||
import { Component } from '../../src/component/entities/component.entity';
|
||||
import { TableQueryService } from '../../src/widget/tabel-query/table-query.service';
|
||||
import { Connection, DataSource, getConnection, getRepository } from 'typeorm';
|
||||
import { ResponseStatus } from '../../src/common/enum/response-status.enum';
|
||||
import { DatasetType } from '../../src/common/enum/dataset-type.enum';
|
||||
|
||||
describe('QTT-002 : 위젯 생성', () => {
|
||||
let widgetService: WidgetService;
|
||||
let tableQueryService: TableQueryService;
|
||||
let widgetRepository: Widget;
|
||||
let connection: Connection;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: `.env.dev`,
|
||||
}),
|
||||
getTestMysqlModule(),
|
||||
TypeOrmModule.forFeature([Widget, Component, TableQuery, Database]),
|
||||
],
|
||||
providers: [WidgetService, TableQueryService, Widget],
|
||||
}).compile();
|
||||
widgetService = module.get<WidgetService>(WidgetService);
|
||||
tableQueryService = module.get<TableQueryService>(TableQueryService);
|
||||
widgetRepository = module.get<Widget>(Widget);
|
||||
connection = module.get<Connection>(Connection);
|
||||
}, 10000);
|
||||
|
||||
it('QTT-002-01: widget 생성 확인', async () => {
|
||||
// const result = await widgetService.create(Object.create(widgetTestoption['default']))
|
||||
|
||||
const testData = widgetTestoption['default'];
|
||||
|
||||
const tableQueryList = [];
|
||||
|
||||
const tableData = testData.filter(item => item.datasetType === DatasetType.TABLE);
|
||||
|
||||
for (const item of tableData) {
|
||||
const selectQuery = await tableQueryService.makeSelectAllQuery(
|
||||
Number(item.databaseId),
|
||||
item.tableName,
|
||||
);
|
||||
tableQueryList.push({ id: item.datasetId, databaseId: item.databaseId, query: selectQuery });
|
||||
}
|
||||
|
||||
await connection.query('truncate table table_query');
|
||||
|
||||
const tableQueryResult = await connection
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(TableQuery)
|
||||
.values(tableQueryList)
|
||||
.execute();
|
||||
|
||||
testData.forEach(el => {
|
||||
el.option = JSON.stringify(el.option);
|
||||
});
|
||||
|
||||
const widgetResult = await connection
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(Widget)
|
||||
.values(testData)
|
||||
.execute();
|
||||
|
||||
return expect(widgetResult.raw.affectedRows).toEqual(100);
|
||||
});
|
||||
|
||||
it('QTT-002-02: widget 목록 확인', async () => {
|
||||
const result = await widgetService.findAll();
|
||||
return expect(result.status).toEqual(ResponseStatus.SUCCESS);
|
||||
});
|
||||
});
|
||||
3730
backend-api/test/QTT-002/widgetTestOption.json
Normal file
101
backend-api/test/QTT-003/QTT-003-01.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { getTestMysqlModule } from '../util/get-test-mysql.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { Widget } from '../../src/widget/entities/widget.entity';
|
||||
import { Component } from '../../src/component/entities/component.entity';
|
||||
import { DashboardService } from '../../src/dashboard/dashboard.service';
|
||||
import { Dashboard } from '../../src/dashboard/entities/dashboard.entity';
|
||||
import { DashboardWidget } from '../../src/dashboard/dashboard-widget/entities/dashboard-widget.entity';
|
||||
import { DashboardWidgetService } from '../../src/dashboard/dashboard-widget/dashboard-widget.service';
|
||||
import { ComponentService } from '../../src/component/component.service';
|
||||
import { WidgetService } from '../../src/widget/widget.service';
|
||||
import { TableQueryService } from '../../src/widget/tabel-query/table-query.service';
|
||||
import { TableQuery } from '../../src/widget/tabel-query/entity/table-query.entity';
|
||||
import { Database } from '../../src/database/entities/database.entity';
|
||||
import { TemplateService } from '../../src/template/template.service';
|
||||
import { CreateDashboardDto } from '../../src/dashboard/dto/create-dashboard.dto';
|
||||
import { Template } from '../../src/template/entities/template.entity';
|
||||
import { TemplateItem } from '../../src/template/entities/template-item.entity';
|
||||
import { ResponseStatus } from '../../src/common/enum/response-status.enum';
|
||||
|
||||
describe('QTT-003: 시각화 종류', () => {
|
||||
let dashboardService: DashboardService;
|
||||
let widgetService: WidgetService;
|
||||
let componentService: ComponentService;
|
||||
let templateService: TemplateService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env.dev',
|
||||
}),
|
||||
getTestMysqlModule(),
|
||||
TypeOrmModule.forFeature([
|
||||
Dashboard,
|
||||
DashboardWidget,
|
||||
Widget,
|
||||
Component,
|
||||
TableQuery,
|
||||
Database,
|
||||
Template,
|
||||
TemplateItem,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
DashboardService,
|
||||
DashboardWidgetService,
|
||||
WidgetService,
|
||||
TableQueryService,
|
||||
ComponentService,
|
||||
TemplateService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
dashboardService = module.get<DashboardService>(DashboardService);
|
||||
widgetService = module.get<WidgetService>(WidgetService);
|
||||
componentService = module.get<ComponentService>(ComponentService);
|
||||
templateService = module.get<TemplateService>(TemplateService);
|
||||
}, 10000);
|
||||
|
||||
const barChartList = [3, 4, 16, 17, 20, 25];
|
||||
const lineChartList = [1, 2, 14, 15, 26];
|
||||
const pieChartList = [6, 7, 9, 11, 22, 31, 33, 41, 42, 52, 53];
|
||||
const etcChartList = [
|
||||
5, 8, 10, 19, 21, 23, 24, 27, 28, 32, 34, 35, 36, 37, 38, 39, 40, 43, 44, 45, 46, 47, 48, 49,
|
||||
50, 51,
|
||||
];
|
||||
const etcComponentList = [12, 13];
|
||||
|
||||
const testData = [
|
||||
['01: varchart 위젯 목록 확인', 'QTT-003-01 바차트 계열 시각화', barChartList],
|
||||
['02: linechart 위젯 목록 확인', 'QTT-003-02 라인차트 계열 시각화', lineChartList],
|
||||
['03: piechart 위젯 목록 확인', 'QTT-003-03 파이차트 계열 시각화', pieChartList],
|
||||
['04: etcchart 위젯 목록 확인', 'QTT-003-04 기타 차트 계열 시각화 확인', etcChartList],
|
||||
['05: etcComponent 위젯 목록 확인', 'QTT-003-05 기타 시각화 확인', etcComponentList],
|
||||
];
|
||||
|
||||
it.each(testData)(
|
||||
'QTT-003-%s',
|
||||
async (name: string, dashboardTitle: string, componentList: number[]) => {
|
||||
let findWidgetInfo = await widgetService.findAll();
|
||||
let widgetIdList = [];
|
||||
for (let i = 0; i < componentList.length; i++) {
|
||||
const tempWidgetObj = findWidgetInfo.data.find(
|
||||
item => item.componentId === componentList[i],
|
||||
);
|
||||
widgetIdList.push(tempWidgetObj.id);
|
||||
}
|
||||
|
||||
const layoutResult = await templateService.getTemplateDashboardLayout(widgetIdList, 8);
|
||||
const createDashboardDto: CreateDashboardDto = new CreateDashboardDto();
|
||||
createDashboardDto.title = dashboardTitle;
|
||||
createDashboardDto.layout = layoutResult.data.layout;
|
||||
const createDashboardResult = await dashboardService.create(createDashboardDto);
|
||||
console.log('::::::::생성된 대시보드 id :: ', createDashboardResult.data.id);
|
||||
return expect(createDashboardResult.status).toEqual(ResponseStatus.SUCCESS);
|
||||
},
|
||||
);
|
||||
});
|
||||
33
backend-api/test/QTT-004/QTT-004.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as dummyJson from './QTT-004_dummyData.json';
|
||||
import * as resultJson from './QTT-004_resultData.json';
|
||||
import { getAggregationData, WIDGET_AGGREGATION } from '../../src/utils/aggregation.util';
|
||||
|
||||
describe('QTT-004: 데이터 집산', () => {
|
||||
const dummyData = dummyJson.dummyData;
|
||||
const expectData = resultJson.expectData;
|
||||
const fieldList = Object.keys(expectData);
|
||||
|
||||
it.each(fieldList)('QTT-004-01 (MAX): %s', fieldName => {
|
||||
expect(getAggregationData(WIDGET_AGGREGATION.MAX, dummyData, fieldName)).toBe(
|
||||
expectData[fieldName][WIDGET_AGGREGATION.MAX],
|
||||
);
|
||||
});
|
||||
|
||||
it.each(fieldList)('QTT-004-02 (MIN) : %s ', fieldName => {
|
||||
expect(getAggregationData(WIDGET_AGGREGATION.MIN, dummyData, fieldName)).toBe(
|
||||
expectData[fieldName][WIDGET_AGGREGATION.MIN],
|
||||
);
|
||||
});
|
||||
|
||||
it.each(fieldList)('QTT-004-03 (SUM) : %s ', fieldName => {
|
||||
expect(getAggregationData(WIDGET_AGGREGATION.SUM, dummyData, fieldName)).toBe(
|
||||
expectData[fieldName][WIDGET_AGGREGATION.SUM],
|
||||
);
|
||||
});
|
||||
|
||||
it.each(fieldList)('QTT-004-04 (AVG) : %s ', fieldName => {
|
||||
expect(getAggregationData(WIDGET_AGGREGATION.AVG, dummyData, fieldName)).toBe(
|
||||
expectData[fieldName][WIDGET_AGGREGATION.AVG],
|
||||
);
|
||||
});
|
||||
});
|
||||
81604
backend-api/test/QTT-004/QTT-004_dummyData.json
Normal file
101
backend-api/test/QTT-004/QTT-004_resultData.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{"expectData":{
|
||||
"column0" : {"max":998.948,"min":1.116,"sum":407490.778,"avg":509.363473},
|
||||
"column1" : {"max":999.563,"min":0.385,"sum":409306.359,"avg":511.632949},
|
||||
"column2" : {"max":999.129,"min":1.168,"sum":389591.141,"avg":486.988926},
|
||||
"column3" : {"max":999.579,"min":0.424,"sum":419521.521,"avg":524.401901},
|
||||
"column4" : {"max":998.982,"min":2.311,"sum":393989.918,"avg":492.487398},
|
||||
"column5" : {"max":999.168,"min":0.23,"sum":391632.037,"avg":489.540046},
|
||||
"column6" : {"max":998.186,"min":1.201,"sum":393227.412,"avg":491.534265},
|
||||
"column7" : {"max":996.089,"min":0.031,"sum":413118.872,"avg":516.39859},
|
||||
"column8" : {"max":998.908,"min":0.04,"sum":400969.224,"avg":501.21153},
|
||||
"column9" : {"max":998.946,"min":0.191,"sum":414858.579,"avg":518.573224},
|
||||
"column10" : {"max":996.196,"min":0.987,"sum":404863.735,"avg":506.079669},
|
||||
"column11" : {"max":997.99,"min":0.345,"sum":401228.814,"avg":501.536018},
|
||||
"column12" : {"max":996.723,"min":4.326,"sum":399080.727,"avg":498.850909},
|
||||
"column13" : {"max":998.933,"min":0.558,"sum":380537.694,"avg":475.672118},
|
||||
"column14" : {"max":998.27,"min":0.087,"sum":395527.684,"avg":494.409605},
|
||||
"column15" : {"max":997.903,"min":0.894,"sum":405428.704,"avg":506.78588},
|
||||
"column16" : {"max":998.262,"min":1.628,"sum":399424.256,"avg":499.28032},
|
||||
"column17" : {"max":999.92,"min":0.14,"sum":405688.163,"avg":507.110204},
|
||||
"column18" : {"max":998.518,"min":0.968,"sum":409870.981,"avg":512.338726},
|
||||
"column19" : {"max":998.286,"min":0.618,"sum":394402.714,"avg":493.003393},
|
||||
"column20" : {"max":997.566,"min":0.01,"sum":407179.516,"avg":508.974395},
|
||||
"column21" : {"max":995.865,"min":0.536,"sum":410940.916,"avg":513.676145},
|
||||
"column22" : {"max":999.821,"min":2.186,"sum":415033.819,"avg":518.792274},
|
||||
"column23" : {"max":999.181,"min":0.272,"sum":409699.541,"avg":512.124426},
|
||||
"column24" : {"max":999.891,"min":3.872,"sum":406412.265,"avg":508.015331},
|
||||
"column25" : {"max":999.7,"min":0.568,"sum":392242.444,"avg":490.303055},
|
||||
"column26" : {"max":999.766,"min":3.807,"sum":400251.747,"avg":500.314684},
|
||||
"column27" : {"max":999.095,"min":0.21,"sum":398043.371,"avg":497.554214},
|
||||
"column28" : {"max":995.157,"min":1.177,"sum":404005.741,"avg":505.007176},
|
||||
"column29" : {"max":999.487,"min":1.012,"sum":394403.681,"avg":493.004601},
|
||||
"column30" : {"max":998.318,"min":1.629,"sum":409910.569,"avg":512.388211},
|
||||
"column31" : {"max":998.941,"min":2.153,"sum":396167.899,"avg":495.209874},
|
||||
"column32" : {"max":998.474,"min":0.083,"sum":401932.824,"avg":502.41603},
|
||||
"column33" : {"max":999.36,"min":0.308,"sum":385863.073,"avg":482.328841},
|
||||
"column34" : {"max":999.965,"min":0.383,"sum":399426.485,"avg":499.283106},
|
||||
"column35" : {"max":999.941,"min":0.828,"sum":406550.077,"avg":508.187596},
|
||||
"column36" : {"max":998.56,"min":0.534,"sum":393857.778,"avg":492.322223},
|
||||
"column37" : {"max":998.115,"min":0.827,"sum":397831.11,"avg":497.288888},
|
||||
"column38" : {"max":997.716,"min":0.154,"sum":405915.964,"avg":507.394955},
|
||||
"column39" : {"max":998.42,"min":0.062,"sum":409514.766,"avg":511.893458},
|
||||
"column40" : {"max":999.734,"min":4.852,"sum":398318.867,"avg":497.898584},
|
||||
"column41" : {"max":999.534,"min":2.031,"sum":407142.789,"avg":508.928486},
|
||||
"column42" : {"max":999.872,"min":0.701,"sum":412393.392,"avg":515.49174},
|
||||
"column43" : {"max":999.79,"min":1.502,"sum":403758.409,"avg":504.698011},
|
||||
"column44" : {"max":998.014,"min":0.54,"sum":395707.726,"avg":494.634658},
|
||||
"column45" : {"max":996.389,"min":0.287,"sum":389924.446,"avg":487.405558},
|
||||
"column46" : {"max":999.777,"min":0.555,"sum":408607.805,"avg":510.759756},
|
||||
"column47" : {"max":999.675,"min":1.107,"sum":402007.437,"avg":502.509296},
|
||||
"column48" : {"max":997.018,"min":0.993,"sum":406525.938,"avg":508.157423},
|
||||
"column49" : {"max":999.724,"min":0.109,"sum":391503.017,"avg":489.378771},
|
||||
"column50" : {"max":999.828,"min":0.321,"sum":408112.412,"avg":510.140515},
|
||||
"column51" : {"max":999.838,"min":0.21,"sum":393734.577,"avg":492.168221},
|
||||
"column52" : {"max":999.687,"min":0.401,"sum":393900.081,"avg":492.375101},
|
||||
"column53" : {"max":999.364,"min":2.952,"sum":396889.218,"avg":496.111523},
|
||||
"column54" : {"max":999.338,"min":0.216,"sum":384038.187,"avg":480.047734},
|
||||
"column55" : {"max":998.388,"min":0.765,"sum":405974.245,"avg":507.467806},
|
||||
"column56" : {"max":999.166,"min":1.002,"sum":404003.733,"avg":505.004666},
|
||||
"column57" : {"max":999.392,"min":0.194,"sum":399748.594,"avg":499.685743},
|
||||
"column58" : {"max":998.034,"min":1,"sum":408202.382,"avg":510.252978},
|
||||
"column59" : {"max":995.775,"min":3.286,"sum":405000.362,"avg":506.250453},
|
||||
"column60" : {"max":999.323,"min":0.457,"sum":408638.668,"avg":510.798335},
|
||||
"column61" : {"max":997.392,"min":0.66,"sum":399419.991,"avg":499.274989},
|
||||
"column62" : {"max":999.664,"min":1.244,"sum":396122.368,"avg":495.15296},
|
||||
"column63" : {"max":999.831,"min":0.679,"sum":402507.184,"avg":503.13398},
|
||||
"column64" : {"max":999.674,"min":2.016,"sum":384550.265,"avg":480.687831},
|
||||
"column65" : {"max":999.83,"min":0.413,"sum":397623.809,"avg":497.029761},
|
||||
"column66" : {"max":997.354,"min":5.288,"sum":407969.697,"avg":509.962121},
|
||||
"column67" : {"max":999.658,"min":4.658,"sum":388819.876,"avg":486.024845},
|
||||
"column68" : {"max":997.769,"min":0.287,"sum":407090.625,"avg":508.863281},
|
||||
"column69" : {"max":992.847,"min":1.278,"sum":387942.239,"avg":484.927799},
|
||||
"column70" : {"max":999.816,"min":2.226,"sum":399643.955,"avg":499.554944},
|
||||
"column71" : {"max":998.834,"min":0.253,"sum":392842.066,"avg":491.052583},
|
||||
"column72" : {"max":999.686,"min":0.929,"sum":403421.019,"avg":504.276274},
|
||||
"column73" : {"max":998.759,"min":0.579,"sum":391177.73,"avg":488.972163},
|
||||
"column74" : {"max":999.886,"min":1.924,"sum":414671.857,"avg":518.339821},
|
||||
"column75" : {"max":999.963,"min":0.338,"sum":389449.138,"avg":486.811423},
|
||||
"column76" : {"max":999.567,"min":0.052,"sum":385304.626,"avg":481.630783},
|
||||
"column77" : {"max":998.847,"min":0.277,"sum":394790.478,"avg":493.488098},
|
||||
"column78" : {"max":999.334,"min":0.498,"sum":401637.896,"avg":502.04737},
|
||||
"column79" : {"max":999.635,"min":0.293,"sum":419438.882,"avg":524.298603},
|
||||
"column80" : {"max":998.16,"min":0.219,"sum":394466.47,"avg":493.083088},
|
||||
"column81" : {"max":999.658,"min":1.232,"sum":410821.612,"avg":513.527015},
|
||||
"column82" : {"max":999.718,"min":1.963,"sum":415056.321,"avg":518.820401},
|
||||
"column83" : {"max":996.498,"min":0.481,"sum":394685.495,"avg":493.356869},
|
||||
"column84" : {"max":997.869,"min":0.535,"sum":397989.088,"avg":497.48636},
|
||||
"column85" : {"max":998.859,"min":1.578,"sum":410967.12,"avg":513.7089},
|
||||
"column86" : {"max":994.368,"min":0.811,"sum":401226.531,"avg":501.533164},
|
||||
"column87" : {"max":993.338,"min":1.62,"sum":393735.842,"avg":492.169803},
|
||||
"column88" : {"max":997.111,"min":2.852,"sum":405012.091,"avg":506.265114},
|
||||
"column89" : {"max":998.836,"min":0.167,"sum":406977.765,"avg":508.722206},
|
||||
"column90" : {"max":998.146,"min":2.363,"sum":409079.234,"avg":511.349043},
|
||||
"column91" : {"max":999.407,"min":8.614,"sum":405605.575,"avg":507.006969},
|
||||
"column92" : {"max":999.129,"min":0.725,"sum":405691.33,"avg":507.114163},
|
||||
"column93" : {"max":998.867,"min":2.123,"sum":395407.757,"avg":494.259696},
|
||||
"column94" : {"max":998.492,"min":0.331,"sum":414532.736,"avg":518.16592},
|
||||
"column95" : {"max":998.944,"min":1.618,"sum":412989.83,"avg":516.237288},
|
||||
"column96" : {"max":999.886,"min":0.543,"sum":399757.752,"avg":499.69719},
|
||||
"column97" : {"max":994.608,"min":1.023,"sum":401151.274,"avg":501.439093},
|
||||
"column98" : {"max":999.864,"min":2.846,"sum":405335.743,"avg":506.669679},
|
||||
"column99" : {"max":996.95,"min":0.061,"sum":397142,"avg":496.4275}}}
|
||||
47
backend-api/test/QTT-005/QTT-005-01.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TemplateService } from '../../src/template/template.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Template } from '../../src/template/entities/template.entity';
|
||||
import { TemplateItem } from '../../src/template/entities/template-item.entity';
|
||||
import { Widget } from '../../src/widget/entities/widget.entity';
|
||||
import { Component } from '../../src/component/entities/component.entity';
|
||||
import { getTestMysqlModule } from '../util/get-test-mysql.module';
|
||||
import { TemplateModule } from '../../src/template/template.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
describe('QTT-005: 대시보드 템플릿', () => {
|
||||
let templateService: TemplateService;
|
||||
|
||||
let templateList;
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
TemplateModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env.dev',
|
||||
}),
|
||||
getTestMysqlModule(),
|
||||
TypeOrmModule.forFeature([Template, TemplateItem, Widget, Component]),
|
||||
],
|
||||
providers: [TemplateService],
|
||||
}).compile();
|
||||
|
||||
templateService = module.get<TemplateService>(TemplateService);
|
||||
}, 100000);
|
||||
|
||||
it('QTT-005-01 : 템플릿 종류 10개 확인', async () => {
|
||||
const templateResult = await templateService.findAll();
|
||||
templateList = templateResult.data;
|
||||
|
||||
return expect(templateList.length).toEqual(10);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const templateDetail = await templateService.findOne(templateList[0].id);
|
||||
console.log(
|
||||
':::::::::::::::::::::::::::::template detail:::::::::::::::::::::::::::::\n',
|
||||
templateDetail.data,
|
||||
);
|
||||
});
|
||||
});
|
||||
123
backend-api/test/QTT-006/QTT-006.spec.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TemplateService } from '../../src/template/template.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Template } from '../../src/template/entities/template.entity';
|
||||
import { TemplateItem } from '../../src/template/entities/template-item.entity';
|
||||
import { Widget } from '../../src/widget/entities/widget.entity';
|
||||
import { Component } from '../../src/component/entities/component.entity';
|
||||
import { getTestMysqlModule } from '../util/get-test-mysql.module';
|
||||
import { TemplateModule } from '../../src/template/template.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { DashboardService } from '../../src/dashboard/dashboard.service';
|
||||
import { DashboardModule } from '../../src/dashboard/dashboard.module';
|
||||
import { CreateDashboardDto } from '../../dist/dashboard/dto/create-dashboard.dto';
|
||||
import { Dashboard } from '../../src/dashboard/entities/dashboard.entity';
|
||||
import { DashboardWidget } from '../../src/dashboard/dashboard-widget/entities/dashboard-widget.entity';
|
||||
import { DashboardWidgetService } from '../../src/dashboard/dashboard-widget/dashboard-widget.service';
|
||||
import { ResponseStatus } from '../../src/common/enum/response-status.enum';
|
||||
import { WidgetService } from '../../src/widget/widget.service';
|
||||
import { TableQuery } from '../../src/widget/tabel-query/entity/table-query.entity';
|
||||
import { Database } from '../../src/database/entities/database.entity';
|
||||
import { TableQueryService } from '../../src/widget/tabel-query/table-query.service';
|
||||
|
||||
describe('QTT-006 : 대시보드 템플릿 추천', () => {
|
||||
let templateService: TemplateService;
|
||||
let dashboardService: DashboardService;
|
||||
let widgetService: WidgetService;
|
||||
let templateList01, templateList02;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
TemplateModule,
|
||||
DashboardModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env.dev',
|
||||
}),
|
||||
getTestMysqlModule(),
|
||||
TypeOrmModule.forFeature([
|
||||
Template,
|
||||
TemplateItem,
|
||||
Widget,
|
||||
Component,
|
||||
Dashboard,
|
||||
DashboardWidget,
|
||||
Database,
|
||||
TableQuery,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
TemplateService,
|
||||
DashboardService,
|
||||
DashboardWidgetService,
|
||||
WidgetService,
|
||||
TableQueryService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
templateService = module.get<TemplateService>(TemplateService);
|
||||
dashboardService = module.get<DashboardService>(DashboardService);
|
||||
widgetService = module.get<WidgetService>(WidgetService);
|
||||
}, 100000);
|
||||
|
||||
it('QTT-006-01 : 서로 다른 타입의 위젯 목록', async () => {
|
||||
const componentList = [15, 12, 41, 13, 38];
|
||||
|
||||
let findWidgetInfo = await widgetService.findAll();
|
||||
let widgetIdList = [];
|
||||
for (let i = 0; i < componentList.length; i++) {
|
||||
const tempWidgetObj = findWidgetInfo.data.find(item => item.componentId === componentList[i]);
|
||||
widgetIdList.push(tempWidgetObj.id);
|
||||
}
|
||||
|
||||
const templateResult = await templateService.findRecommendTemplates(widgetIdList);
|
||||
templateList01 = templateResult.data;
|
||||
|
||||
return expect(templateList01.length).toEqual(10);
|
||||
});
|
||||
|
||||
it('QTT-006-02 : 바차트 타입의 위젯 목록', async () => {
|
||||
const componentList = [3, 4, 16, 17, 20, 25];
|
||||
let findWidgetInfo = await widgetService.findAll();
|
||||
let widgetIdList = [];
|
||||
for (let i = 0; i < componentList.length; i++) {
|
||||
const tempWidgetObj = findWidgetInfo.data.find(item => item.componentId === componentList[i]);
|
||||
widgetIdList.push(tempWidgetObj.id);
|
||||
}
|
||||
|
||||
const templateResult = await templateService.findRecommendTemplates(widgetIdList);
|
||||
templateList02 = templateResult.data;
|
||||
|
||||
// QTT-006-01의 결과와 다름을 확인
|
||||
return expect(templateList02).not.toEqual(templateList01);
|
||||
});
|
||||
|
||||
it('QTT-006-03 : 알고리즘 범위를 벗어난 위젯 목록', async () => {
|
||||
const componentList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let findWidgetInfo = await widgetService.findAll();
|
||||
let widgetIdList = [];
|
||||
for (let i = 0; i < componentList.length; i++) {
|
||||
const tempWidgetObj = findWidgetInfo.data.find(item => item.componentId === componentList[i]);
|
||||
widgetIdList.push(tempWidgetObj.id);
|
||||
}
|
||||
|
||||
const templateResult = await templateService.findRecommendTemplates(widgetIdList);
|
||||
const templateList = templateResult.data;
|
||||
const layoutResult = await templateService.getTemplateDashboardLayout(
|
||||
widgetIdList,
|
||||
templateList[0].id,
|
||||
);
|
||||
|
||||
const createDashboardDto: CreateDashboardDto = new CreateDashboardDto();
|
||||
createDashboardDto.title = 'QTT-006-03 dashboard';
|
||||
createDashboardDto.layout = layoutResult.data.layout;
|
||||
const createDashboardResult = await dashboardService.create(createDashboardDto);
|
||||
|
||||
console.log(
|
||||
'::::::::::::::대시보드 위젯 배치 확인::::::::::::::\n',
|
||||
createDashboardResult.data,
|
||||
);
|
||||
return expect(createDashboardResult.status).toEqual(ResponseStatus.SUCCESS);
|
||||
});
|
||||
});
|
||||
25
backend-api/test/util/get-test-mysql.module.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { DynamicModule } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* 테스트 MySQL 가져오기
|
||||
*
|
||||
* @returns {DynamicModule}
|
||||
*/
|
||||
export function getTestMysqlModule(): DynamicModule {
|
||||
const entityUrl = path.join(__dirname, '..', '..', '/src/**/*.entity{.ts,.js}');
|
||||
return TypeOrmModule.forRoot({
|
||||
type: 'mysql',
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT) || 3306,
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
autoLoadEntities: true,
|
||||
entities: [entityUrl],
|
||||
synchronize: false,
|
||||
logging: process.env.NODE_ENV == 'dev',
|
||||
retryAttempts: 1,
|
||||
});
|
||||
}
|
||||
BIN
design/feature-01.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
design/feature-02.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
design/feature-03.png
Normal file
|
After Width: | Height: | Size: 304 KiB |
BIN
design/feature-04.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
design/vanillameta-logo.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
@@ -1 +1,5 @@
|
||||
REACT_APP_API_URL='http://localhost:4000'
|
||||
REACT_APP_MODE=prod
|
||||
REACT_APP_API_URL='http://localhost:4000'
|
||||
REACT_APP_ID=vanillameta
|
||||
REACT_APP_PWD=vanillameta
|
||||
REACT_APP_TOKEN=TOKEN1VB2GS3SWJYDS
|
||||
@@ -1 +1,5 @@
|
||||
REACT_APP_API_URL='https://dev-api.vanillameta.net/v1'
|
||||
REACT_APP_MODE=dev
|
||||
REACT_APP_API_URL='https://vanillameta-api.vanillabrain.com'
|
||||
REACT_APP_ID=vanillameta
|
||||
REACT_APP_PWD=vanillameta
|
||||
REACT_APP_TOKEN=TOKEN1VB2GS3SWJYDS
|
||||
@@ -1 +1,5 @@
|
||||
REACT_APP_API_URL='http://localhost:4000'
|
||||
REACT_APP_MODE=local
|
||||
REACT_APP_API_URL='http://localhost:4000'
|
||||
REACT_APP_ID=vanillameta
|
||||
REACT_APP_PWD=vanillameta
|
||||
REACT_APP_TOKEN=TOKEN1VB2GS3SWJYDS
|
||||
@@ -2,7 +2,8 @@
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint","prettier"],
|
||||
|
||||
20
frontend-web/jest.config.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "jsx", "ts", "tsx", "json"],
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx)?$": "babel-jest",
|
||||
"^.+\\.(ts|tsx)?$": "ts-jest"
|
||||
},
|
||||
"testEnvironment": "jsdom",
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"src"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"@/(.*)": "<rootDir>/src/$1"
|
||||
},
|
||||
"testMatch": [
|
||||
"<rootDir>/**/*.test.(js|jsx|ts|tsx)",
|
||||
"<rootDir>/(tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx))"
|
||||
],
|
||||
"transformIgnorePatterns": ["<rootDir>/node_modules/"]
|
||||
}
|
||||
577
frontend-web/package-lock.json
generated
@@ -2256,6 +2256,21 @@
|
||||
"jest-mock": "^27.5.1"
|
||||
}
|
||||
},
|
||||
"@jest/expect-utils": {
|
||||
"version": "29.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.2.2.tgz",
|
||||
"integrity": "sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg==",
|
||||
"requires": {
|
||||
"jest-get-type": "^29.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"jest-get-type": {
|
||||
"version": "29.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz",
|
||||
"integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@jest/fake-timers": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz",
|
||||
@@ -8466,14 +8481,43 @@
|
||||
}
|
||||
},
|
||||
"@types/jest": {
|
||||
"version": "28.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.6.tgz",
|
||||
"integrity": "sha512-0RbGAFMfcBJKOmqRazM8L98uokwuwD5F8rHrv/ZMbrZBwVOWZUyPG6VFNscjYr/vjM3Vu4fRrCPbOs42AfemaQ==",
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.1.tgz",
|
||||
"integrity": "sha512-nKixEdnGDqFOZkMTF74avFNr3yRqB1ZJ6sRZv5/28D5x2oLN14KApv7F9mfDT/vUic0L3tRCsh3XWpWjtJisUQ==",
|
||||
"requires": {
|
||||
"jest-matcher-utils": "^28.0.0",
|
||||
"pretty-format": "^28.0.0"
|
||||
"expect": "^29.0.0",
|
||||
"pretty-format": "^29.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/schemas": {
|
||||
"version": "29.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz",
|
||||
"integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==",
|
||||
"requires": {
|
||||
"@sinclair/typebox": "^0.24.1"
|
||||
}
|
||||
},
|
||||
"@jest/types": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz",
|
||||
"integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==",
|
||||
"requires": {
|
||||
"@jest/schemas": "^29.0.0",
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^17.0.8",
|
||||
"chalk": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "17.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
|
||||
"integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@@ -8505,9 +8549,21 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "28.1.1",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz",
|
||||
"integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw=="
|
||||
"version": "29.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.2.0.tgz",
|
||||
"integrity": "sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw=="
|
||||
},
|
||||
"expect": {
|
||||
"version": "29.2.2",
|
||||
"resolved": "https://registry.npmjs.org/expect/-/expect-29.2.2.tgz",
|
||||
"integrity": "sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw==",
|
||||
"requires": {
|
||||
"@jest/expect-utils": "^29.2.2",
|
||||
"jest-get-type": "^29.2.0",
|
||||
"jest-matcher-utils": "^29.2.2",
|
||||
"jest-message-util": "^29.2.1",
|
||||
"jest-util": "^29.2.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
@@ -8515,39 +8571,67 @@
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz",
|
||||
"integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==",
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.2.1.tgz",
|
||||
"integrity": "sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^28.1.1",
|
||||
"jest-get-type": "^28.0.2",
|
||||
"pretty-format": "^28.1.3"
|
||||
"diff-sequences": "^29.2.0",
|
||||
"jest-get-type": "^29.2.0",
|
||||
"pretty-format": "^29.2.1"
|
||||
}
|
||||
},
|
||||
"jest-get-type": {
|
||||
"version": "28.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz",
|
||||
"integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA=="
|
||||
"version": "29.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.2.0.tgz",
|
||||
"integrity": "sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA=="
|
||||
},
|
||||
"jest-matcher-utils": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz",
|
||||
"integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==",
|
||||
"version": "29.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz",
|
||||
"integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^28.1.3",
|
||||
"jest-get-type": "^28.0.2",
|
||||
"pretty-format": "^28.1.3"
|
||||
"jest-diff": "^29.2.1",
|
||||
"jest-get-type": "^29.2.0",
|
||||
"pretty-format": "^29.2.1"
|
||||
}
|
||||
},
|
||||
"jest-message-util": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz",
|
||||
"integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@jest/types": "^29.2.1",
|
||||
"@types/stack-utils": "^2.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"micromatch": "^4.0.4",
|
||||
"pretty-format": "^29.2.1",
|
||||
"slash": "^3.0.0",
|
||||
"stack-utils": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"jest-util": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz",
|
||||
"integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==",
|
||||
"requires": {
|
||||
"@jest/types": "^29.2.1",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"ci-info": "^3.2.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"picomatch": "^2.2.3"
|
||||
}
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "28.1.3",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz",
|
||||
"integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==",
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz",
|
||||
"integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==",
|
||||
"requires": {
|
||||
"@jest/schemas": "^28.1.3",
|
||||
"ansi-regex": "^5.0.1",
|
||||
"@jest/schemas": "^29.0.0",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^18.0.0"
|
||||
},
|
||||
@@ -9863,24 +9947,90 @@
|
||||
"integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA=="
|
||||
},
|
||||
"babel-jest": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
|
||||
"integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==",
|
||||
"version": "29.2.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.2.2.tgz",
|
||||
"integrity": "sha512-kkq2QSDIuvpgfoac3WZ1OOcHsQQDU5xYk2Ql7tLdJ8BVAYbefEXal+NfS45Y5LVZA7cxC8KYcQMObpCt1J025w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/transform": "^27.5.1",
|
||||
"@jest/types": "^27.5.1",
|
||||
"@jest/transform": "^29.2.2",
|
||||
"@types/babel__core": "^7.1.14",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"babel-preset-jest": "^27.5.1",
|
||||
"babel-preset-jest": "^29.2.0",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"slash": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/schemas": {
|
||||
"version": "29.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz",
|
||||
"integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinclair/typebox": "^0.24.1"
|
||||
}
|
||||
},
|
||||
"@jest/transform": {
|
||||
"version": "29.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.2.2.tgz",
|
||||
"integrity": "sha512-aPe6rrletyuEIt2axxgdtxljmzH8O/nrov4byy6pDw9S8inIrTV+2PnjyP/oFHMSynzGxJ2s6OHowBNMXp/Jzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/core": "^7.11.6",
|
||||
"@jest/types": "^29.2.1",
|
||||
"@jridgewell/trace-mapping": "^0.3.15",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"chalk": "^4.0.0",
|
||||
"convert-source-map": "^1.4.0",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"jest-haste-map": "^29.2.1",
|
||||
"jest-regex-util": "^29.2.0",
|
||||
"jest-util": "^29.2.1",
|
||||
"micromatch": "^4.0.4",
|
||||
"pirates": "^4.0.4",
|
||||
"slash": "^3.0.0",
|
||||
"write-file-atomic": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"@jest/types": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz",
|
||||
"integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/schemas": "^29.0.0",
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^17.0.8",
|
||||
"chalk": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.17",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
|
||||
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "17.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
|
||||
"integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
@@ -9889,6 +10039,7 @@
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
@@ -9898,6 +10049,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
@@ -9905,20 +10057,96 @@
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"jest-haste-map": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.2.1.tgz",
|
||||
"integrity": "sha512-wF460rAFmYc6ARcCFNw4MbGYQjYkvjovb9GBT+W10Um8q5nHq98jD6fHZMDMO3tA56S8XnmNkM8GcA8diSZfnA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/types": "^29.2.1",
|
||||
"@types/graceful-fs": "^4.1.3",
|
||||
"@types/node": "*",
|
||||
"anymatch": "^3.0.3",
|
||||
"fb-watchman": "^2.0.0",
|
||||
"fsevents": "^2.3.2",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"jest-regex-util": "^29.2.0",
|
||||
"jest-util": "^29.2.1",
|
||||
"jest-worker": "^29.2.1",
|
||||
"micromatch": "^4.0.4",
|
||||
"walker": "^1.0.8"
|
||||
}
|
||||
},
|
||||
"jest-regex-util": {
|
||||
"version": "29.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.2.0.tgz",
|
||||
"integrity": "sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==",
|
||||
"dev": true
|
||||
},
|
||||
"jest-util": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz",
|
||||
"integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/types": "^29.2.1",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"ci-info": "^3.2.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"picomatch": "^2.2.3"
|
||||
}
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.2.1.tgz",
|
||||
"integrity": "sha512-ROHTZ+oj7sBrgtv46zZ84uWky71AoYi0vEV9CdEtc1FQunsoAGe5HbQmW76nI5QWdvECVPrSi1MCVUmizSavMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"jest-util": "^29.2.1",
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"write-file-atomic": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
|
||||
"integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"imurmurhash": "^0.1.4",
|
||||
"signal-exit": "^3.0.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10007,13 +10235,14 @@
|
||||
}
|
||||
},
|
||||
"babel-plugin-jest-hoist": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz",
|
||||
"integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==",
|
||||
"version": "29.2.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz",
|
||||
"integrity": "sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.3.3",
|
||||
"@babel/types": "^7.3.3",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/babel__core": "^7.1.14",
|
||||
"@types/babel__traverse": "^7.0.6"
|
||||
}
|
||||
},
|
||||
@@ -10125,11 +10354,12 @@
|
||||
}
|
||||
},
|
||||
"babel-preset-jest": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz",
|
||||
"integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==",
|
||||
"version": "29.2.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz",
|
||||
"integrity": "sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-plugin-jest-hoist": "^27.5.1",
|
||||
"babel-plugin-jest-hoist": "^29.2.0",
|
||||
"babel-preset-current-node-syntax": "^1.0.0"
|
||||
}
|
||||
},
|
||||
@@ -10587,6 +10817,15 @@
|
||||
"update-browserslist-db": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"bs-logger": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
|
||||
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-json-stable-stringify": "2.x"
|
||||
}
|
||||
},
|
||||
"bser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
|
||||
@@ -11228,6 +11467,11 @@
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
|
||||
},
|
||||
"complex.js": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
|
||||
"integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg=="
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
@@ -12897,6 +13141,11 @@
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"escape-latex": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
|
||||
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
@@ -15987,6 +16236,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript-natural-sort": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
|
||||
},
|
||||
"jest": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz",
|
||||
@@ -16181,6 +16435,41 @@
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"babel-jest": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
|
||||
"integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==",
|
||||
"requires": {
|
||||
"@jest/transform": "^27.5.1",
|
||||
"@jest/types": "^27.5.1",
|
||||
"@types/babel__core": "^7.1.14",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"babel-preset-jest": "^27.5.1",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"slash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-jest-hoist": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz",
|
||||
"integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.3.3",
|
||||
"@babel/types": "^7.3.3",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/babel__traverse": "^7.0.6"
|
||||
}
|
||||
},
|
||||
"babel-preset-jest": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz",
|
||||
"integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==",
|
||||
"requires": {
|
||||
"babel-plugin-jest-hoist": "^27.5.1",
|
||||
"babel-preset-current-node-syntax": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -17810,6 +18099,37 @@
|
||||
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
||||
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
|
||||
},
|
||||
"mathjs": {
|
||||
"version": "11.3.3",
|
||||
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.3.3.tgz",
|
||||
"integrity": "sha512-+NsgPRzvnczrw5hp7fNPMnfXCaBo2cs7c8Edoacbjcc2Z3js6jHf+Pz8FY6nb5udmBj0q4zl+a5PxbjWVgOQcA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.20.1",
|
||||
"complex.js": "^2.1.1",
|
||||
"decimal.js": "^10.4.2",
|
||||
"escape-latex": "^1.2.0",
|
||||
"fraction.js": "^4.2.0",
|
||||
"javascript-natural-sort": "^0.7.1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"tiny-emitter": "^2.1.0",
|
||||
"typed-function": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.1.tgz",
|
||||
"integrity": "sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.10"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.10",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz",
|
||||
"integrity": "sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -20734,6 +21054,41 @@
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"babel-jest": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
|
||||
"integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==",
|
||||
"requires": {
|
||||
"@jest/transform": "^27.5.1",
|
||||
"@jest/types": "^27.5.1",
|
||||
"@types/babel__core": "^7.1.14",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"babel-preset-jest": "^27.5.1",
|
||||
"chalk": "^4.0.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"slash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-jest-hoist": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz",
|
||||
"integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.3.3",
|
||||
"@babel/types": "^7.3.3",
|
||||
"@types/babel__core": "^7.0.0",
|
||||
"@types/babel__traverse": "^7.0.6"
|
||||
}
|
||||
},
|
||||
"babel-preset-jest": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz",
|
||||
"integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==",
|
||||
"requires": {
|
||||
"babel-plugin-jest-hoist": "^27.5.1",
|
||||
"babel-preset-current-node-syntax": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -21842,6 +22197,11 @@
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"seedrandom": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
|
||||
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
@@ -23416,6 +23776,11 @@
|
||||
"setimmediate": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||
},
|
||||
"tiny-glob": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
|
||||
@@ -23554,6 +23919,125 @@
|
||||
"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "29.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz",
|
||||
"integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bs-logger": "0.x",
|
||||
"fast-json-stable-stringify": "2.x",
|
||||
"jest-util": "^29.0.0",
|
||||
"json5": "^2.2.1",
|
||||
"lodash.memoize": "4.x",
|
||||
"make-error": "1.x",
|
||||
"semver": "7.x",
|
||||
"yargs-parser": "^21.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jest/schemas": {
|
||||
"version": "29.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz",
|
||||
"integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@sinclair/typebox": "^0.24.1"
|
||||
}
|
||||
},
|
||||
"@jest/types": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz",
|
||||
"integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/schemas": "^29.0.0",
|
||||
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||
"@types/istanbul-reports": "^3.0.0",
|
||||
"@types/node": "*",
|
||||
"@types/yargs": "^17.0.8",
|
||||
"chalk": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "17.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
|
||||
"integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"jest-util": {
|
||||
"version": "29.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz",
|
||||
"integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jest/types": "^29.2.1",
|
||||
"@types/node": "*",
|
||||
"chalk": "^4.0.0",
|
||||
"ci-info": "^3.2.0",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"picomatch": "^2.2.3"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
@@ -23703,6 +24187,11 @@
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"typed-function": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz",
|
||||
"integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg=="
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"echarts": "^5.3.3",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"mathjs": "^11.3.3",
|
||||
"react": "^18.2.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-alert": "^7.0.3",
|
||||
@@ -42,12 +43,12 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"start": "env-cmd -f .env.local craco start",
|
||||
"start:dev": "env-cmd -f .env.development craco start",
|
||||
"start:local": "env-cmd -f .env.local craco start",
|
||||
"build": "craco build",
|
||||
"build:dev": "env-cmd -f .env.development craco build",
|
||||
"test": "craco test",
|
||||
"test": "jest",
|
||||
"eject": "craco eject",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook"
|
||||
@@ -95,9 +96,11 @@
|
||||
"@storybook/preset-create-react-app": "^4.1.2",
|
||||
"@storybook/react": "^6.5.9",
|
||||
"@storybook/testing-library": "0.0.13",
|
||||
"@types/jest": "^29.2.1",
|
||||
"@types/numeral": "^2.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"babel-jest": "^29.2.2",
|
||||
"babel-loader": "^8.2.5",
|
||||
"babel-plugin-named-exports-order": "0.0.2",
|
||||
"craco-alias": "^3.0.1",
|
||||
@@ -116,6 +119,7 @@
|
||||
"prop-types": "^15.8.1",
|
||||
"react-error-overlay": "^6.0.11",
|
||||
"serve": "^14.0.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.7.4",
|
||||
"webpack": "^5.73.0"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 55 KiB |
9
frontend-web/public/static/images/logo/postgis-logo.svg
Normal file
|
After Width: | Height: | Size: 246 KiB |
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
import Layout from './layouts/Layout';
|
||||
import Router from './router';
|
||||
import 'tui-grid/dist/tui-grid.css';
|
||||
import Grid from 'tui-grid';
|
||||
@@ -59,9 +58,7 @@ function App() {
|
||||
return (
|
||||
<>
|
||||
<CssBaseline />
|
||||
<Layout>
|
||||
<Router />
|
||||
</Layout>
|
||||
<Router />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { del, get, post, put } from '@/helpers/apiHelper';
|
||||
export const URL_DATABASE = '/database';
|
||||
|
||||
const selectDatabaseList = (): Promise<any> => get(URL_DATABASE);
|
||||
const selectDatabaseInfo = (id: string, data = null): Promise<any> => get(URL_DATABASE + '/info/' + id, data);
|
||||
const selectDatabase = (id: string, data = null): Promise<any> => get(URL_DATABASE + '/' + id, data);
|
||||
const testConnection = (data: unknown): Promise<any> => post(URL_DATABASE + '/test', data);
|
||||
const executeQuery = (data: unknown): Promise<any> => post(URL_DATABASE + '/execute', data);
|
||||
@@ -14,6 +15,7 @@ const selectData = (data: unknown): Promise<any> => get(URL_DATABASE + '/data',
|
||||
|
||||
const DatabaseService = {
|
||||
selectDatabaseList,
|
||||
selectDatabaseInfo,
|
||||
selectDatabase,
|
||||
selectDatabaseTypeList,
|
||||
createDatabase,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 13a.996.996 0 0 1-.707-.293l-5-5a1 1 0 0 1 0-1.414l5-5a1 1 0 1 1 1.414 1.414L5.414 7l4.294 4.294A1 1 0 0 1 9 13z" fill="#fff"/>
|
||||
<path d="M9 13a.996.996 0 0 1-.707-.293l-5-5a1 1 0 0 1 0-1.414l5-5a1 1 0 1 1 1.414 1.414L5.414 7l4.294 4.294A1 1 0 0 1 9 13z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 247 B After Width: | Height: | Size: 236 B |
BIN
frontend-web/src/assets/images/visual-bg.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
@@ -49,7 +49,7 @@ function BoardList(props) {
|
||||
<Stack
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
sx={{ paddingLeft: '20px', paddingRight: '217px', marginBottom: '11px', marginTop: '36px' }}
|
||||
sx={{ paddingLeft: '20px', paddingRight: '206px', marginBottom: '11px', marginTop: '36px' }}
|
||||
>
|
||||
<GTSpan>이름</GTSpan>
|
||||
{matches && <GTSpan>수정일</GTSpan>}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Box, CardContent, Grid, Typography } from '@mui/material';
|
||||
import { Box, CardContent, Typography } from '@mui/material';
|
||||
import { CardWrapper } from '@/components/list/CardListWrapper';
|
||||
|
||||
function ImgCardList(props) {
|
||||
const { data, minWidth, selectedType, setSelectedType } = props;
|
||||
const { data, selectedType, handleTypeClick } = props;
|
||||
const srcUrl = '/static/images/';
|
||||
|
||||
const handleClick = item => {
|
||||
console.log('database : ', item);
|
||||
setSelectedType(item);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="ul"
|
||||
@@ -28,7 +23,7 @@ function ImgCardList(props) {
|
||||
const selected = selectedType && selectedType.id === item.id;
|
||||
return (
|
||||
<Box component="li" key={item.id}>
|
||||
<CardWrapper sx={{ p: 0 }} selected={selected} onClick={() => handleClick(item)}>
|
||||
<CardWrapper sx={{ p: 0 }} selected={selected} onClick={() => handleTypeClick(item)}>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: 'flex',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, CardContent, Grid, Stack, Typography } from '@mui/material';
|
||||
import { Box, CardContent, Typography } from '@mui/material';
|
||||
import { CardWrapper } from '@/components/list/CardListWrapper';
|
||||
|
||||
function LargeImgCardList(props) {
|
||||
@@ -24,49 +24,52 @@ function LargeImgCardList(props) {
|
||||
m: '0',
|
||||
}}
|
||||
>
|
||||
{data.map(item => {
|
||||
const selected = selectedType && selectedType.id === item.id;
|
||||
return (
|
||||
<Box component="li" key={item.id}>
|
||||
<CardWrapper sx={{ width: 169, p: 0 }} selected={selected} onClick={() => handleClick(item)}>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: 169,
|
||||
height: 166,
|
||||
pt: '24px',
|
||||
px: '15px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="img"
|
||||
src={srcUrl + item.icon}
|
||||
sx={{ width: 48, height: 48, objectFit: 'contain', mb: '12px', border: 0 }}
|
||||
/>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
component="span"
|
||||
sx={{ textAlign: 'center', lineHeight: '1.3', fontWeight: 'bold' }}
|
||||
{data
|
||||
.filter(item => item.seq !== null)
|
||||
.sort((a, b) => a.seq - b.seq)
|
||||
.map(item => {
|
||||
const selected = selectedType && selectedType.id === item.id;
|
||||
return (
|
||||
<Box component="li" key={item.id}>
|
||||
<CardWrapper sx={{ width: 169, p: 0 }} selected={selected} onClick={() => handleClick(item)}>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: 169,
|
||||
height: 166,
|
||||
pt: '24px',
|
||||
px: '15px',
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</Typography>
|
||||
{item.description ? (
|
||||
<Box
|
||||
component="img"
|
||||
src={srcUrl + item.icon}
|
||||
sx={{ width: 48, height: 48, objectFit: 'contain', mb: '12px', border: 0 }}
|
||||
/>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ mt: '4px', textAlign: 'center', lineHeight: '1.3', color: theme => theme.palette.grey.A700 }}
|
||||
variant="subtitle2"
|
||||
component="span"
|
||||
sx={{ textAlign: 'center', lineHeight: '1.3', fontWeight: 'bold' }}
|
||||
>
|
||||
{item.description}
|
||||
{item.title}
|
||||
</Typography>
|
||||
) : (
|
||||
' '
|
||||
)}
|
||||
</CardContent>
|
||||
</CardWrapper>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{item.description ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ mt: '4px', textAlign: 'center', lineHeight: '1.3', color: theme => theme.palette.grey.A700 }}
|
||||
>
|
||||
{item.description}
|
||||
</Typography>
|
||||
) : (
|
||||
' '
|
||||
)}
|
||||
</CardContent>
|
||||
</CardWrapper>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ function PageTitleBox(props) {
|
||||
minWidth: '900px',
|
||||
paddingLeft: '25px',
|
||||
paddingRight: '25px',
|
||||
width: '1920px',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import Button from '@mui/material/Button';
|
||||
import { Divider } from '@mui/material';
|
||||
import { Alert, Divider, Snackbar } from '@mui/material';
|
||||
|
||||
interface IProps {
|
||||
message: string | JSX.Element;
|
||||
@@ -22,11 +22,23 @@ interface IProps {
|
||||
|
||||
const buttonStyle = { minWidth: 80, height: 36, padding: '0 10px', fontSize: '13px', fontWeight: 'bold' };
|
||||
|
||||
const AlertDialog = ({ close, message, options }: IProps) => {
|
||||
export const SnackbarTemplate = props => {
|
||||
const { close, message, options } = props;
|
||||
console.log(props);
|
||||
return (
|
||||
<Snackbar open autoHideDuration={6000} onClose={close}>
|
||||
<Alert onClose={close} severity={options.type} sx={{ width: '100%' }}>
|
||||
{message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
};
|
||||
|
||||
const AlertTemplate = ({ close, message, options }: IProps) => {
|
||||
const hasTitle = options.title && options.title.toString().trim() !== '';
|
||||
return (
|
||||
<Dialog
|
||||
open
|
||||
open={true}
|
||||
onClose={close}
|
||||
keepMounted
|
||||
aria-labelledby="alert-dialog-slide-title"
|
||||
@@ -69,6 +81,7 @@ const AlertDialog = ({ close, message, options }: IProps) => {
|
||||
action.onClick();
|
||||
close();
|
||||
}}
|
||||
autoFocus={true}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={buttonStyle}
|
||||
@@ -77,7 +90,7 @@ const AlertDialog = ({ close, message, options }: IProps) => {
|
||||
{action.copy}
|
||||
</Button>
|
||||
))}
|
||||
<Button variant="contained" color="primary" sx={buttonStyle} onClick={close}>
|
||||
<Button variant="contained" color="primary" sx={buttonStyle} onClick={close} autoFocus={true}>
|
||||
{options.closeCopy || '확인'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@@ -85,4 +98,4 @@ const AlertDialog = ({ close, message, options }: IProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AlertDialog;
|
||||
export default AlertTemplate;
|
||||
|
||||
@@ -38,6 +38,9 @@ export const CardWrapper = ({ children, selected, onClick, sx = null }) => {
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
alignItems: 'center',
|
||||
'&:hover': {
|
||||
backgroundColor: '#ebfbff',
|
||||
},
|
||||
...sx,
|
||||
}}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Card, CardActionArea, CardActions, CardContent, Grid, Typography } from '@mui/material';
|
||||
import { CardActions, CardContent, Grid, Typography } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import CardListWrapper, { CardWrapper } from '@/components/list/CardListWrapper';
|
||||
import { any } from 'prop-types';
|
||||
@@ -31,7 +31,7 @@ export const DatabaseCardList = props => {
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
pl: '10px',
|
||||
width: disabledIcons ? '100%' : '40%',
|
||||
width: disabledIcons ? '100%' : '70%',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
|
||||
24
frontend-web/src/contexts/AlertContext.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { createContext } from 'react';
|
||||
import AlertTemplate, { SnackbarTemplate } from '@/components/alert';
|
||||
import { positions, Provider, transitions } from 'react-alert';
|
||||
|
||||
export const SnackbarContext = createContext(null);
|
||||
|
||||
export const AlertProvider = ({ children }) => {
|
||||
const defaultOp = {
|
||||
position: positions.BOTTOM_CENTER,
|
||||
offset: '30px',
|
||||
transition: transitions.SCALE,
|
||||
};
|
||||
const snackbarOp = {
|
||||
position: positions.BOTTOM_LEFT,
|
||||
timeout: 6000,
|
||||
};
|
||||
return (
|
||||
<Provider template={AlertTemplate} {...defaultOp}>
|
||||
<Provider template={SnackbarTemplate} context={SnackbarContext} {...snackbarOp}>
|
||||
{children}
|
||||
</Provider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
37
frontend-web/src/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createContext, useContext, useState } from 'react';
|
||||
|
||||
const AuthContext = createContext(null);
|
||||
|
||||
export const AuthProvider = ({ children }) => {
|
||||
const [token, setToken] = useState(null);
|
||||
const storage = window.sessionStorage;
|
||||
|
||||
const handleLogin = async (id, pwd) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (id === process.env.REACT_APP_ID && pwd === process.env.REACT_APP_PWD) {
|
||||
setToken(process.env.REACT_APP_TOKEN);
|
||||
storage.setItem('loggedUserId', id);
|
||||
storage.setItem('loggedUserPwd', pwd);
|
||||
setTimeout(() => resolve(process.env.REACT_APP_TOKEN), 1000);
|
||||
} else {
|
||||
reject(new Error('ID 또는 비밀번호가 일치하지 않습니다.'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
setToken(null);
|
||||
};
|
||||
|
||||
const value = {
|
||||
token,
|
||||
onLogin: handleLogin,
|
||||
onLogout: handleLogout,
|
||||
};
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
return useContext(AuthContext);
|
||||
};
|
||||
9244
frontend-web/src/data/QTT-004_dummyData.json
Normal file
6
frontend-web/src/global.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare module '*.jpg' {
|
||||
export default '' as string;
|
||||
}
|
||||
declare module '*.png' {
|
||||
export default '' as string;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ const API_URL = process.env.REACT_APP_API_URL;
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: API_URL,
|
||||
timeout: 10000,
|
||||
timeout: 100000,
|
||||
// withCredentials: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,35 +1,27 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import theme from './theme/theme';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { positions, Provider as AlertProvider, transitions } from 'react-alert';
|
||||
import AlertTemplate from './components/alert';
|
||||
|
||||
import theme from './theme/theme';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
import { LayoutProvider } from '@/contexts/LayoutContext';
|
||||
import { LoadingProvider } from '@/contexts/LoadingContext';
|
||||
|
||||
// alert optional configuration
|
||||
const options = {
|
||||
// you can also just use 'bottom center'
|
||||
position: positions.BOTTOM_CENTER,
|
||||
offset: '30px',
|
||||
// you can also just use 'scale'
|
||||
transition: transitions.SCALE,
|
||||
};
|
||||
import { AuthProvider } from '@/contexts/AuthContext';
|
||||
import { AlertProvider } from '@/contexts/AlertContext';
|
||||
import './index.css';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<LayoutProvider>
|
||||
<LoadingProvider>
|
||||
<BrowserRouter>
|
||||
<ThemeProvider theme={theme}>
|
||||
<AlertProvider template={AlertTemplate} {...options}>
|
||||
<App />
|
||||
</AlertProvider>
|
||||
</ThemeProvider>
|
||||
<AuthProvider>
|
||||
<ThemeProvider theme={theme}>
|
||||
<AlertProvider>
|
||||
<App />
|
||||
</AlertProvider>
|
||||
</ThemeProvider>
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</LoadingProvider>
|
||||
</LayoutProvider>,
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import React from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { Link, Stack, Typography } from '@mui/material';
|
||||
|
||||
const Footer = props => {
|
||||
const { height } = props;
|
||||
|
||||
return (
|
||||
<Stack sx={{ height: height, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Typography
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '16px',
|
||||
fontFamily: 'Pretendard',
|
||||
fontSize: '13px',
|
||||
fontWeight: 'normal',
|
||||
fontStretch: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 'normal',
|
||||
letterSpacing: 'normal',
|
||||
textAlign: 'center',
|
||||
color: '#767676',
|
||||
}}
|
||||
<Link
|
||||
color="inherit"
|
||||
href="https://vanillabrain.com/"
|
||||
sx={{ fontSize: '13px', color: '#767676', textDecoration: 'none' }}
|
||||
>
|
||||
@ Vanilla Meta 2022
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '16px',
|
||||
fontFamily: 'Pretendard',
|
||||
fontSize: '13px',
|
||||
fontWeight: 'normal',
|
||||
fontStretch: 'normal',
|
||||
fontStyle: 'normal',
|
||||
lineHeight: 'normal',
|
||||
letterSpacing: 'normal',
|
||||
textAlign: 'center',
|
||||
color: '#767676',
|
||||
}}
|
||||
>
|
||||
@ Vanilla Meta 2022
|
||||
</Typography>
|
||||
</Link>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Box, Link } from '@mui/material';
|
||||
import { Box } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { ReactComponent as IconLogo } from '@/assets/images/logo.svg';
|
||||
|
||||
function Logo(props) {
|
||||
const { sx = { width: 105, height: 50 } } = props;
|
||||
return (
|
||||
<Box sx={sx}>
|
||||
<Link href="/">
|
||||
<RouterLink to="/">
|
||||
<IconLogo style={{ width: '100%', height: '100%' }} />
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import { Box, Stack } from '@mui/material';
|
||||
import Header from './Header/Header';
|
||||
import Footer from './Footer/Footer';
|
||||
import { LayoutContext } from '@/contexts/LayoutContext';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
function Layout(props) {
|
||||
const Layout = props => {
|
||||
const { children } = props;
|
||||
const headerHeight = 65;
|
||||
const footerHeight = 50;
|
||||
|
||||
@@ -33,11 +35,11 @@ function Layout(props) {
|
||||
minHeight: `calc(100% - ${footerHeight}px)`,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
{children || <Outlet />}
|
||||
</Stack>
|
||||
<Footer height={footerHeight} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
@@ -17,23 +17,30 @@ import WidgetService from '@/api/widgetService';
|
||||
import { STATUS } from '@/constant';
|
||||
import { useAlert } from 'react-alert';
|
||||
import CloseButton from '@/components/button/CloseButton';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
|
||||
function AddWidgetPopup({ label, useWidgetIds = [], widgetOpen = false, widgetSelect = null }) {
|
||||
const [open, setOpen] = useState(widgetOpen);
|
||||
const [selectedIds, setSelectedIds] = useState([]);
|
||||
const [loadedWidgetData, setLoadedWidgetData] = useState([]);
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
const alert = useAlert();
|
||||
const getItems = () => {
|
||||
WidgetService.selectWidgetList().then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
const widgetList = response.data.data.filter(item => {
|
||||
return !useWidgetIds.find(useItem => useItem == item.id);
|
||||
});
|
||||
setLoadedWidgetData(widgetList);
|
||||
} else {
|
||||
console.log('조회실패!!!');
|
||||
}
|
||||
});
|
||||
showLoading();
|
||||
WidgetService.selectWidgetList()
|
||||
.then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
const widgetList = response.data.data.filter(item => {
|
||||
return !useWidgetIds.find(useItem => useItem == item.id);
|
||||
});
|
||||
setLoadedWidgetData(widgetList);
|
||||
} else {
|
||||
console.log('조회실패!!!');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const descriptionElementRef = useRef<HTMLElement>(null);
|
||||
@@ -93,7 +100,7 @@ function AddWidgetPopup({ label, useWidgetIds = [], widgetOpen = false, widgetSe
|
||||
}
|
||||
setOpen(false);
|
||||
} else {
|
||||
alert.info('위젯을 선택하세요.');
|
||||
alert.info('위젯을 선택해주세요.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -229,7 +236,7 @@ function AddWidgetPopup({ label, useWidgetIds = [], widgetOpen = false, widgetSe
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<span style={{ width: '4px' }}></span>
|
||||
<span style={{ width: '4px' }} />
|
||||
<Button
|
||||
onClick={() => handleSelect()}
|
||||
sx={{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
@@ -32,6 +32,7 @@ import { ReactComponent as TemplateIcon08 } from '@/assets/images/template/templ
|
||||
import { ReactComponent as TemplateIcon09 } from '@/assets/images/template/template09.svg';
|
||||
import { ReactComponent as TemplateIcon10 } from '@/assets/images/template/template10.svg';
|
||||
import { ReactComponent as CheckIcon } from '@/assets/images/icon/ic-check.svg';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
|
||||
const getTemplateIcon = id => {
|
||||
let icon = null;
|
||||
@@ -79,6 +80,7 @@ export const WidgetList = ({
|
||||
const alert = useAlert();
|
||||
const [loadedWidgetData, setLoadedWidgetData] = useState([]);
|
||||
const [selectedIds, setSelectedIds] = useState(selectedWidgetIds);
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
|
||||
useEffect(() => {
|
||||
getItems();
|
||||
@@ -93,13 +95,18 @@ export const WidgetList = ({
|
||||
}, [selectedIds]);
|
||||
|
||||
const getItems = () => {
|
||||
WidgetService.selectWidgetList().then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setLoadedWidgetData(response.data.data);
|
||||
} else {
|
||||
console.log('조회 실패!!!!');
|
||||
}
|
||||
});
|
||||
showLoading();
|
||||
WidgetService.selectWidgetList()
|
||||
.then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setLoadedWidgetData(response.data.data);
|
||||
} else {
|
||||
console.log('조회 실패!!!!');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const handleClick = item => {
|
||||
@@ -140,7 +147,7 @@ export const WidgetList = ({
|
||||
handleWidgetConfirm(widgets);
|
||||
}
|
||||
} else {
|
||||
alert.info('위젯을 선택하세요.');
|
||||
alert.info('위젯을 선택해주세요.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -216,7 +223,7 @@ export const WidgetList = ({
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<span style={{ width: '4px' }}></span>
|
||||
<span style={{ width: '4px' }} />
|
||||
<Button
|
||||
onClick={() => handleConfirmClick()}
|
||||
sx={{
|
||||
@@ -275,7 +282,7 @@ export const TemplateList = ({ handleWidgetConfirm = null, handleWidgetCancel =
|
||||
handleWidgetConfirm(selectedItem);
|
||||
}
|
||||
} else {
|
||||
alert.info('템플릿을 선택하세요.');
|
||||
alert.info('템플릿을 선택해주세요.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -337,6 +344,7 @@ export const TemplateList = ({ handleWidgetConfirm = null, handleWidgetCancel =
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
width: '224px',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
@@ -390,7 +398,7 @@ export const TemplateList = ({ handleWidgetConfirm = null, handleWidgetCancel =
|
||||
>
|
||||
뒤로가기
|
||||
</Button>
|
||||
<span style={{ width: '4px' }}></span>
|
||||
<span style={{ width: '4px' }} />
|
||||
<Button
|
||||
onClick={() => handleConfirmClick()}
|
||||
sx={{
|
||||
@@ -533,8 +541,8 @@ function RecommendDashboardPopup({ recommendOpen = false, handleComplete = null
|
||||
position: 'absolute',
|
||||
right: '0px',
|
||||
top: '0px',
|
||||
paddingRight: '19px',
|
||||
paddingTop: '19px',
|
||||
marginRight: '12px',
|
||||
marginTop: '12px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
size="medium"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Box, Button, Card, Stack, TextField } from '@mui/material';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
@@ -7,6 +7,7 @@ import AddWidgetPopup from '@/pages/Dashboard/Components/AddWidgetPopup';
|
||||
import ConfirmCancelButton from '@/components/button/ConfirmCancelButton';
|
||||
import { Responsive, WidthProvider } from 'react-grid-layout';
|
||||
import { useAlert } from 'react-alert';
|
||||
import { SnackbarContext } from '@/contexts/AlertContext';
|
||||
|
||||
import '/node_modules/react-grid-layout/css/styles.css';
|
||||
import '/node_modules/react-resizable/css/styles.css';
|
||||
@@ -18,13 +19,17 @@ import { STATUS } from '@/constant';
|
||||
import DashboardTitleBox from '../Components/DashboardTitleBox';
|
||||
import CloseButton from '@/components/button/CloseButton';
|
||||
import bg from '@/assets/images/dashboard-bg.svg';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
function DashboardModify() {
|
||||
const alert = useAlert();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const snackbar = useAlert(SnackbarContext);
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
|
||||
const [dashboardId, setDashboardId] = useState(null); // dashboard id
|
||||
const [dashboardTitle, setDashboardTitle] = useState(''); // dashboard 제목
|
||||
const [widgets, setWidgets] = useState([]); // widget 정보
|
||||
@@ -62,22 +67,26 @@ function DashboardModify() {
|
||||
|
||||
// dashboard info 조회
|
||||
const getDashboardInfo = id => {
|
||||
// todo 서비스 완료시 연결
|
||||
DashboardService.selectDashboard(id).then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setDashboardTitle(response.data.data.title);
|
||||
setWidgets(response.data.data.widgets);
|
||||
showLoading();
|
||||
DashboardService.selectDashboard(id)
|
||||
.then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setDashboardTitle(response.data.data.title);
|
||||
setWidgets(response.data.data.widgets);
|
||||
|
||||
response.data.data.layout.map((item, index) => {
|
||||
if (item.i !== undefined) {
|
||||
item.i = item.i.toString();
|
||||
}
|
||||
});
|
||||
setLayout(response.data.data.layout);
|
||||
} else {
|
||||
alert.error('조회를 실패하였습니다.');
|
||||
}
|
||||
});
|
||||
response.data.data.layout.map(item => {
|
||||
if (item.i !== undefined) {
|
||||
item.i = item.i.toString();
|
||||
}
|
||||
});
|
||||
setLayout(response.data.data.layout);
|
||||
} else {
|
||||
alert.error('대시보드 조회에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -101,18 +110,86 @@ function DashboardModify() {
|
||||
setLayout(changeLayout);
|
||||
};
|
||||
|
||||
// 추가 할 layout
|
||||
// @tempLayout 현재 배치되어 있는 layout 정보
|
||||
// @x : 새로 배치될 위젯의 x 초기값
|
||||
// @y : 새로 배치될 위젯의 y 초기값
|
||||
// @w : 새로 배치될 위젯의 width 값
|
||||
// @h : 새로 배치될 위젯의 height 값
|
||||
const getCalculatorPosition = (tempLayout, x = 0, y = 0, w = 6, h = 5, layoutMaxW = 12) => {
|
||||
const tempPos = { startX: x, endX: w - 1, startY: y, endY: h - 1 }; // layout 계산을 위한 값 보정
|
||||
tempLayout.sort((a, b) => a.y - b.y || a.x - b.x);
|
||||
|
||||
const tmepPosArr = [];
|
||||
tempLayout.map(item => {
|
||||
tmepPosArr.push({ startX: item.x, endX: item.x + item.w - 1, startY: item.y, endY: item.y + item.h - 1 }); // layout 계산을 위한 값 보정
|
||||
});
|
||||
|
||||
// layout 위치 계산 (재귀함수로 사용. 해당 함수를 수정할 때 무한 loop 되지 않게 조심할 것)
|
||||
const calculator = (pos, posArr) => {
|
||||
const maxW = layoutMaxW - 1; // layout 계산을 위한 값 보정
|
||||
|
||||
// 추가할 위젯의 위치를 1칸씩 변경
|
||||
const getCalculatorPos = pos => {
|
||||
if (pos.endX + 1 > maxW) {
|
||||
pos.endX = pos.endX - pos.startX;
|
||||
pos.startX = 0;
|
||||
pos.startY = pos.startY + 1;
|
||||
pos.endY = pos.endY + 1;
|
||||
} else {
|
||||
pos.startX = pos.startX + 1;
|
||||
pos.endX = pos.endX + 1;
|
||||
}
|
||||
return pos;
|
||||
};
|
||||
|
||||
for (let i = 0; i < posArr.length; i++) {
|
||||
const item = posArr[i];
|
||||
let isCompareHit = false;
|
||||
if (pos.startX <= item.startX && (pos.endX >= item.startX || pos.endX > item.endX)) {
|
||||
// x 좌표가 겹치는 상황
|
||||
isCompareHit = true;
|
||||
} else if (pos.startX >= item.startX && pos.startX <= item.endX) {
|
||||
// x 좌표가 겹치는 상황
|
||||
isCompareHit = true;
|
||||
}
|
||||
|
||||
if (isCompareHit) {
|
||||
if (pos.startY <= item.startY && (pos.endY >= item.startY || pos.endY > item.endY)) {
|
||||
// x, y 좌표가 겹치는 상황
|
||||
return calculator(getCalculatorPos(pos), posArr);
|
||||
} else if (pos.startY >= item.startY && pos.startY <= item.endY) {
|
||||
// x, y 좌표가 겹치는 상황
|
||||
return calculator(getCalculatorPos(pos), posArr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
};
|
||||
|
||||
const resultPos = calculator(tempPos, tmepPosArr);
|
||||
// 보정된 layout 값을 원래대로 복원
|
||||
return {
|
||||
x: resultPos.startX,
|
||||
y: resultPos.startY,
|
||||
w: resultPos.endX - resultPos.startX + 1,
|
||||
h: resultPos.endY - resultPos.startY + 1,
|
||||
};
|
||||
};
|
||||
|
||||
// widget 생성
|
||||
const generateWidget = () => {
|
||||
useWidgetIds.length = 0;
|
||||
const addLayouts = [];
|
||||
|
||||
widgets.map((item, index) => {
|
||||
if (layout.length <= index) {
|
||||
const calculatorPosition = getCalculatorPosition([...layout, ...addLayouts], 0, 0, 6, 5, 12);
|
||||
|
||||
addLayouts.push({
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 5,
|
||||
h: 5,
|
||||
i: item.id.toString(),
|
||||
...calculatorPosition,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -121,7 +198,7 @@ function DashboardModify() {
|
||||
setLayout([...layout, ...addLayouts]);
|
||||
}
|
||||
|
||||
return widgets.map((item, index) => {
|
||||
return widgets.map(item => {
|
||||
useWidgetIds.push(item.id); // 현재 widget id 를 담는다.
|
||||
return (
|
||||
<Card
|
||||
@@ -145,6 +222,7 @@ function DashboardModify() {
|
||||
paddingRight: '8px',
|
||||
paddingTop: '8px',
|
||||
cursor: 'pointer',
|
||||
zIndex: 2000,
|
||||
}}
|
||||
size="medium"
|
||||
onClick={event => {
|
||||
@@ -174,16 +252,17 @@ function DashboardModify() {
|
||||
// title null 체크, widgets 수 체크 (0개면 저장 못함)
|
||||
if (dashboardTitle == null || dashboardTitle.trim() == '') {
|
||||
// title 이 없을 경우
|
||||
alert.info('제목을 입력 해주세요.', {
|
||||
onClose: () => {
|
||||
// todo 아래 기능 연결하기
|
||||
console.log('title 로 포커스 이동하기');
|
||||
},
|
||||
});
|
||||
alert.info('제목을 입력해주세요.');
|
||||
} else if (layout.length == 0 || widgets.length == 0) {
|
||||
// 배치된 widget 이 없을경우
|
||||
alert.info('배치된 위젯이 없습니다.');
|
||||
} else {
|
||||
// 저장 전 react grid layout 에서 필요없는 속성 제거
|
||||
layout.map(item => {
|
||||
delete item.moved;
|
||||
delete item.static;
|
||||
});
|
||||
|
||||
// 저장 로직
|
||||
dashboardInfo.dashboardId = dashboardId;
|
||||
dashboardInfo.title = dashboardTitle;
|
||||
@@ -198,9 +277,19 @@ function DashboardModify() {
|
||||
{
|
||||
copy: '수정',
|
||||
onClick: () => {
|
||||
DashboardService.updateDashboard(dashboardId, dashboardInfo).then(() => {
|
||||
navigate('/dashboard');
|
||||
});
|
||||
showLoading();
|
||||
DashboardService.updateDashboard(dashboardId, dashboardInfo)
|
||||
.then(response => {
|
||||
if (response.data.status === 'SUCCESS') {
|
||||
navigate('/dashboard/' + dashboardId, { replace: true });
|
||||
snackbar.success('대시보드가 수정되었습니다.');
|
||||
} else {
|
||||
alert.error('대시보드 수정에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -213,9 +302,19 @@ function DashboardModify() {
|
||||
{
|
||||
copy: '생성',
|
||||
onClick: () => {
|
||||
DashboardService.createDashboard(dashboardInfo).then(() => {
|
||||
navigate('/dashboard');
|
||||
});
|
||||
showLoading();
|
||||
DashboardService.createDashboard(dashboardInfo)
|
||||
.then(response => {
|
||||
if (response.data.status === 'SUCCESS') {
|
||||
navigate('/dashboard');
|
||||
snackbar.success('대시보드가 생성되었습니다.');
|
||||
} else {
|
||||
alert.error('대시보드 생성에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -225,7 +324,7 @@ function DashboardModify() {
|
||||
};
|
||||
|
||||
// 취소 여부 버튼 이벤트
|
||||
const handleCancelDialogSelect = detail => {
|
||||
const handleCancelDialogSelect = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
@@ -237,7 +336,7 @@ function DashboardModify() {
|
||||
console.log(dashboardInfo);
|
||||
setWidgets(dashboardInfo.widgets);
|
||||
|
||||
dashboardInfo.layout.map((item, index) => {
|
||||
dashboardInfo.layout.map(item => {
|
||||
if (item.i !== undefined) {
|
||||
item.i = item.i.toString();
|
||||
}
|
||||
@@ -266,7 +365,7 @@ function DashboardModify() {
|
||||
title={
|
||||
<TextField
|
||||
id="userDashboardName"
|
||||
label=""
|
||||
label="대시보드 이름"
|
||||
required
|
||||
sx={{
|
||||
width: '960px',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Box, Card, Stack, Typography } from '@mui/material';
|
||||
import { Link as RouterLink, useNavigate, useParams } from 'react-router-dom';
|
||||
import PageTitleBox from '@/components/PageTitleBox';
|
||||
@@ -13,6 +13,8 @@ import DashboardTitleBox from '../Components/DashboardTitleBox';
|
||||
import ModifyButton from '@/components/button/ModifyButton';
|
||||
import DeleteButton from '@/components/button/DeleteButton';
|
||||
import ReloadButton from '@/components/button/ReloadButton';
|
||||
import { SnackbarContext } from '@/contexts/AlertContext';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
|
||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||
|
||||
@@ -20,7 +22,9 @@ const DashboardView = () => {
|
||||
const { dashboardId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const alert = useAlert();
|
||||
const snackbar = useAlert(SnackbarContext);
|
||||
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
const [dashboardInfo, setDashboardInfo] = useState({ title: '', widgets: [], layout: [], updatedAt: '' }); // dashboard 정보
|
||||
const [layout, setLayout] = useState([]); // grid layout
|
||||
// dashboard id
|
||||
@@ -32,7 +36,7 @@ const DashboardView = () => {
|
||||
|
||||
// dashboardInfo useEffect
|
||||
useEffect(() => {
|
||||
dashboardInfo.layout.map((item, index) => {
|
||||
dashboardInfo.layout.map(item => {
|
||||
if (item.i !== undefined) {
|
||||
item.i = item.i.toString();
|
||||
}
|
||||
@@ -44,13 +48,18 @@ const DashboardView = () => {
|
||||
|
||||
// dashboard info 조회
|
||||
const getDashboardInfo = id => {
|
||||
DashboardService.selectDashboard(id).then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setDashboardInfo(response.data.data);
|
||||
} else {
|
||||
alert.error('조회 실패하였습니다.');
|
||||
}
|
||||
});
|
||||
showLoading();
|
||||
DashboardService.selectDashboard(id)
|
||||
.then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setDashboardInfo(response.data.data);
|
||||
} else {
|
||||
alert.error('대시보드 조회에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const dateData = data => {
|
||||
@@ -72,7 +81,7 @@ const DashboardView = () => {
|
||||
|
||||
// widget 생성
|
||||
const generateWidget = () => {
|
||||
return dashboardInfo.widgets.map((item, index) => {
|
||||
return dashboardInfo.widgets.map(item => {
|
||||
return (
|
||||
<Card
|
||||
key={item.id}
|
||||
@@ -92,23 +101,25 @@ const DashboardView = () => {
|
||||
};
|
||||
|
||||
const handleDeleteSelect = () => {
|
||||
alert.success(dashboardInfo.title + '\n삭제하겠습니까?', {
|
||||
alert.success(dashboardInfo.title + '\n대시보드를 삭제하시겠습니까?', {
|
||||
closeCopy: '취소',
|
||||
actions: [
|
||||
{
|
||||
copy: '확인',
|
||||
onClick: () => {
|
||||
DashboardService.deleteDashboard(dashboardId).then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
alert.info('삭제되었습니다.', {
|
||||
onClose: () => {
|
||||
navigate('/dashboard', { replace: true });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
alert.info('삭제 실패하였습니다.');
|
||||
}
|
||||
});
|
||||
showLoading();
|
||||
DashboardService.deleteDashboard(dashboardId)
|
||||
.then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
navigate('/dashboard', { replace: true });
|
||||
snackbar.success('대시보드가 삭제되었습니다.');
|
||||
} else {
|
||||
alert.error('대시보드 삭제에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import PageTitleBox from '@/components/PageTitleBox';
|
||||
import BoardList from '@/components/BoardList';
|
||||
@@ -10,17 +10,19 @@ import DashboardService from '@/api/dashboardService';
|
||||
import { STATUS } from '@/constant';
|
||||
import { useAlert } from 'react-alert';
|
||||
import { styled } from '@mui/system';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
import { SnackbarContext } from '@/contexts/AlertContext';
|
||||
|
||||
const title = '대시보드';
|
||||
|
||||
function Dashboard() {
|
||||
const { dashboardId } = useParams();
|
||||
const alert = useAlert();
|
||||
const snackbar = useAlert(SnackbarContext);
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loadedDashboardData, setLoadedDashboardData] = useState([]);
|
||||
const [loadedCount, setLoadedCount] = useState(1);
|
||||
const [noData, setNoData] = useState(false);
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
|
||||
const GTSpan = styled('span')({
|
||||
fontFamily: 'Pretendard',
|
||||
@@ -41,41 +43,46 @@ function Dashboard() {
|
||||
|
||||
useEffect(() => {
|
||||
getDashboardList();
|
||||
setIsLoading(true);
|
||||
}, []);
|
||||
|
||||
// dashboard info 조회
|
||||
const getDashboardList = () => {
|
||||
DashboardService.selectDashboardList().then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setLoadedDashboardData(response.data.data);
|
||||
setNoData(response.data.data.length == 0);
|
||||
} else {
|
||||
alert.error('서비스 실패!');
|
||||
}
|
||||
});
|
||||
setIsLoading(true);
|
||||
showLoading();
|
||||
DashboardService.selectDashboardList()
|
||||
.then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
setLoadedDashboardData(response.data.data);
|
||||
setNoData(response.data.data.length == 0);
|
||||
} else {
|
||||
alert.error('대시보드 조회에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteSelect = item => {
|
||||
console.log(item);
|
||||
alert.success(item.title + '\n삭제하겠습니까?', {
|
||||
alert.success(item.title + '\n대시보드를 삭제하시겠습니까?', {
|
||||
closeCopy: '취소',
|
||||
actions: [
|
||||
{
|
||||
copy: '확인',
|
||||
onClick: () => {
|
||||
DashboardService.deleteDashboard(item.id).then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
alert.info('삭제되었습니다.', {
|
||||
onClose: () => {
|
||||
getDashboardList();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
alert.error('삭제 실패하였습니다.');
|
||||
}
|
||||
});
|
||||
showLoading();
|
||||
DashboardService.deleteDashboard(item.id)
|
||||
.then(response => {
|
||||
if (response.data.status == STATUS.SUCCESS) {
|
||||
getDashboardList();
|
||||
snackbar.success('대시보드가 삭제되었습니다.');
|
||||
} else {
|
||||
alert.error('대시보드 삭제에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -9,6 +9,7 @@ import DatasetService from '@/api/datasetService';
|
||||
import AddButton from '@/components/button/AddButton';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
import { SnackbarContext } from '@/contexts/AlertContext';
|
||||
|
||||
const DataLayout = props => {
|
||||
const { isViewMode, setDataSet } = props;
|
||||
@@ -16,6 +17,7 @@ const DataLayout = props => {
|
||||
const [datasetList, setDatasetList] = useState([]);
|
||||
const [tableList, setTableList] = useState([]);
|
||||
const alert = useAlert();
|
||||
const snackbar = useAlert(SnackbarContext);
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
|
||||
const [selectedDatabase, setSelectedDatabase] = useState({
|
||||
@@ -57,10 +59,17 @@ const DataLayout = props => {
|
||||
showLoading();
|
||||
DatabaseService.selectDatabase(selectedDatabase.databaseId)
|
||||
.then(response => {
|
||||
setDatasetList(response.data.data.datasets);
|
||||
setTableList(response.data.data.tables);
|
||||
if (response.data.status === 'SUCCESS') {
|
||||
setDatasetList(response.data.data.datasets);
|
||||
setTableList(response.data.data.tables);
|
||||
} else {
|
||||
alert.error('데이터베이스 조회에 실패했습니다.');
|
||||
setDatasetList([]);
|
||||
setTableList([]);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(error => {
|
||||
snackbar.error(error.message);
|
||||
setDatasetList([]);
|
||||
setTableList([]);
|
||||
})
|
||||
@@ -81,6 +90,7 @@ const DataLayout = props => {
|
||||
DatabaseService.deleteDatabase(id).then(response => {
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
getDatabaseList();
|
||||
snackbar.success('데이터베이스가 삭제되었습니다.');
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -97,15 +107,16 @@ const DataLayout = props => {
|
||||
|
||||
const handleDeleteDataset = item => {
|
||||
console.log('handleDeleteDataset', item);
|
||||
alert.success(`${item.title}\n데이터셋를 삭제하시겠습니까?`, {
|
||||
alert.success(`${item.title}\n데이터셋을 삭제하시겠습니까?`, {
|
||||
title: '데이터베이스 삭제',
|
||||
closeCopy: '취소',
|
||||
actions: [
|
||||
{
|
||||
copy: '삭제',
|
||||
onClick: () => {
|
||||
DatasetService.deleteDataset(item.id).then(response => {
|
||||
DatasetService.deleteDataset(item.id).then(() => {
|
||||
getDatabaseInfo();
|
||||
snackbar.success('데이터셋이 삭제되었습니다.');
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { MenuItem, Select, Stack, TextField } from '@mui/material';
|
||||
import { MenuItem, Select, Stack, TextField, Typography } from '@mui/material';
|
||||
import { useAlert } from 'react-alert';
|
||||
import PageTitleBox from '@/components/PageTitleBox';
|
||||
import SubmitButton from '@/components/button/SubmitButton';
|
||||
@@ -16,6 +16,7 @@ import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { STATUS } from '@/constant';
|
||||
import { getDatabaseIcon } from '@/widget/utils/iconUtil';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
import { SnackbarContext } from '@/contexts/AlertContext';
|
||||
|
||||
const DataSet = () => {
|
||||
const { setId, sourceId } = useParams();
|
||||
@@ -26,20 +27,19 @@ const DataSet = () => {
|
||||
const [isModifyMode, setIsModifyMode] = useState(false);
|
||||
const [testCompleted, setTestCompleted] = useState(false);
|
||||
const [datasetInfo, setDatasetInfo] = useState({ databaseId: sourceId, title: '', query: '' });
|
||||
// 'select A.*, C.bizId from UserInfo A, BizUserMap B, BizInfo C where A.userId = B.userId and B.bizId = C.bizId',
|
||||
|
||||
const [data, setData] = useState([]);
|
||||
const [columns, setColumns] = useState([]);
|
||||
const [databaseId, setDatabaseId] = useState(null);
|
||||
const [databaseList, setDatabaseList] = useState([]);
|
||||
const [tableList, setTableList] = useState([]);
|
||||
const alert = useAlert();
|
||||
|
||||
// console.log(data, 'data', sourceId, 'sourceId', databaseId, 'databaseId', setId, 'setId');
|
||||
const alert = useAlert();
|
||||
const snackbar = useAlert(SnackbarContext);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
console.log('modify', pathname.indexOf('modify'));
|
||||
console.log('setId', setId);
|
||||
console.log('sourceId', sourceId);
|
||||
console.log('setId:', setId, 'sourceId:', sourceId);
|
||||
getDatabaseTypeList();
|
||||
|
||||
if (pathname.indexOf('modify') > 0 && setId) {
|
||||
@@ -57,6 +57,10 @@ const DataSet = () => {
|
||||
addCompleter();
|
||||
}, [tableList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!databaseId) getDatabaseId();
|
||||
}, [databaseList, datasetInfo]);
|
||||
|
||||
/**
|
||||
* 데이터 그리드 컬럼 생성
|
||||
* @param data
|
||||
@@ -68,10 +72,9 @@ const DataSet = () => {
|
||||
} else if (data instanceof Object) {
|
||||
target = data;
|
||||
}
|
||||
const columns = Object.keys(target).map(key => {
|
||||
return Object.keys(target).map(key => {
|
||||
return { name: key, header: key, align: key, width: 200, sortable: true };
|
||||
});
|
||||
return columns;
|
||||
};
|
||||
|
||||
const addCompleter = () => {
|
||||
@@ -107,7 +110,7 @@ const DataSet = () => {
|
||||
};
|
||||
|
||||
const onChangeDatabaseId = event => {
|
||||
console.log('onChaonChangeDatabaseIdngeTitle', event.target.value);
|
||||
console.log('changeDatabaseId', event.target.value);
|
||||
setDatabaseId(event.target.value);
|
||||
setDatasetInfo(prevState => ({ ...prevState, databaseId: event.target.value }));
|
||||
};
|
||||
@@ -116,28 +119,45 @@ const DataSet = () => {
|
||||
* 데이터베이스 타입 목록 조회
|
||||
*/
|
||||
const getDatabaseTypeList = () => {
|
||||
DatabaseService.selectDatabaseList().then(response => {
|
||||
console.log('selectDatabaseTypeList', response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
const list = response.data.data;
|
||||
list.map(item => (item.icon = getDatabaseIcon(item.engine)));
|
||||
setDatabaseList(list);
|
||||
if (!isModifyMode && list.length > 0) {
|
||||
// setDatabaseId(list[0].id);
|
||||
setDatabaseId(sourceId);
|
||||
showLoading();
|
||||
DatabaseService.selectDatabaseList()
|
||||
.then(response => {
|
||||
console.log('selectDatabaseTypeList', response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
const list = response.data.data;
|
||||
list.map(item => (item.icon = getDatabaseIcon(item.engine)));
|
||||
setDatabaseList(list);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const getDatabaseId = () => {
|
||||
if (databaseList.length > 0) {
|
||||
if (!isModifyMode) {
|
||||
setDatabaseId(sourceId);
|
||||
} else {
|
||||
setDatabaseId(datasetInfo?.databaseId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getDatabaseInfo = () => {
|
||||
showLoading();
|
||||
DatabaseService.selectDatabase(databaseId)
|
||||
.then(response => {
|
||||
setTableList(response.data.data.tables);
|
||||
console.log('tableList ', response.data.data.tables);
|
||||
if (response.data.status === 'SUCCESS') {
|
||||
setTableList(response.data.data.tables);
|
||||
console.log('tableList ', response.data.data.tables);
|
||||
} else {
|
||||
alert.error('데이터베이스 조회에 실패했습니다.');
|
||||
setTableList([]);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(error => {
|
||||
snackbar.error(error.message);
|
||||
setTableList([]);
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -149,14 +169,19 @@ const DataSet = () => {
|
||||
* 수정일 경우 데이터셋 정보 조회
|
||||
*/
|
||||
const getDatasetInfo = () => {
|
||||
DatasetService.selectDataset(setId).then(response => {
|
||||
console.log('selectDataset', response.data.data.datasetId);
|
||||
if (response.data.status === 'SUCCESS') {
|
||||
setDatasetInfo(response.data.data);
|
||||
setDatabaseId(response.data.data.databaseId);
|
||||
// setDataType(list[0]);
|
||||
}
|
||||
});
|
||||
showLoading();
|
||||
DatasetService.selectDataset(setId)
|
||||
.then(response => {
|
||||
console.log('selectDataset', response.data.data.id, response.data.data.databaseId);
|
||||
if (response.data.status === 'SUCCESS') {
|
||||
setDatasetInfo(response.data.data);
|
||||
} else {
|
||||
alert.error('데이터베이스 조회에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -164,9 +189,6 @@ const DataSet = () => {
|
||||
*/
|
||||
const excuteQuery = () => {
|
||||
showLoading();
|
||||
if (datasetInfo.query.trim().length < 1) {
|
||||
alert.info('쿼리를 입력해주세요.');
|
||||
}
|
||||
const param = {
|
||||
id: databaseId,
|
||||
query: datasetInfo.query,
|
||||
@@ -176,15 +198,22 @@ const DataSet = () => {
|
||||
.then(response => {
|
||||
console.log(response.data);
|
||||
if (response.data.status === 'SUCCESS') {
|
||||
setData(response.data.datas);
|
||||
setTestCompleted(true);
|
||||
setData(response.data.datas);
|
||||
setColumns(createColumns(response.data.datas));
|
||||
snackbar.success('Success!');
|
||||
} else {
|
||||
setTestCompleted(false);
|
||||
setData([]);
|
||||
setColumns([]);
|
||||
snackbar.error(`${response.data.message}`);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(error => {
|
||||
snackbar.error(error.message);
|
||||
setTestCompleted(false);
|
||||
setData([]);
|
||||
setColumns([]);
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
@@ -201,28 +230,35 @@ const DataSet = () => {
|
||||
{
|
||||
copy: '확인',
|
||||
onClick: () => {
|
||||
showLoading();
|
||||
if (isModifyMode) {
|
||||
DatasetService.updateDataset(setId, datasetInfo).then(response => {
|
||||
console.log(response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
alert.info('데이터셋이 수정되었습니다.', {
|
||||
onClose: () => {
|
||||
navigate('/data');
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
DatasetService.updateDataset(setId, datasetInfo)
|
||||
.then(response => {
|
||||
console.log(response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
navigate('/data');
|
||||
snackbar.success('데이터셋이 수정되었습니다.');
|
||||
} else {
|
||||
alert.error('데이터셋 수정에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
} else {
|
||||
DatasetService.createDataset(datasetInfo).then(response => {
|
||||
console.log(response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
alert.info('데이터셋이 생성되었습니다.', {
|
||||
onClose: () => {
|
||||
navigate('/data');
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
DatasetService.createDataset(datasetInfo)
|
||||
.then(response => {
|
||||
console.log(response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
navigate('/data');
|
||||
snackbar.success('데이터셋이 생성되었습니다.');
|
||||
} else {
|
||||
alert.error('데이터셋 생성에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -273,7 +309,15 @@ const DataSet = () => {
|
||||
displayEmpty
|
||||
disabled={isModifyMode}
|
||||
size="small"
|
||||
value={databaseId || ''}
|
||||
value={databaseId ?? ''}
|
||||
// renderValue={selected => {
|
||||
// if (selected.length === 0) {
|
||||
// return <Typography sx={{ color: '#929292', fontStyle: 'italic' }}>데이터베이스를 선택해 주세요.</Typography>;
|
||||
// } else {
|
||||
// const item = databaseList?.find(({ id: value }) => value === selected);
|
||||
// return item?.name;
|
||||
// }
|
||||
// }}
|
||||
onChange={onChangeDatabaseId}
|
||||
>
|
||||
{databaseList.map(item => (
|
||||
@@ -290,7 +334,6 @@ const DataSet = () => {
|
||||
value={datasetInfo?.title}
|
||||
onChange={onChangeTitle}
|
||||
required
|
||||
// helperText="데이터셋의 이름을 입력해 주세요"
|
||||
/>
|
||||
<AceEditor
|
||||
placeholder="Please enter a query."
|
||||
@@ -312,19 +355,29 @@ const DataSet = () => {
|
||||
tabSize: 2,
|
||||
}}
|
||||
/>
|
||||
<SubmitButton label="Run" type="button" sx={{ width: '374px', height: '50px' }} onClick={excuteQuery} />
|
||||
</Stack>
|
||||
<Stack sx={{ p: '30px 25px 40px 25px', backgroundColor: '#f5f6f8' }}>
|
||||
<DataGrid
|
||||
minBodyHeight={300}
|
||||
bodyHeight={500}
|
||||
data={data}
|
||||
columns={columns}
|
||||
columnOptions={{
|
||||
resizable: true,
|
||||
}}
|
||||
<SubmitButton
|
||||
label="Run"
|
||||
type="button"
|
||||
size="large"
|
||||
sx={{ width: '374px', fontSize: '13px' }}
|
||||
onClick={excuteQuery}
|
||||
/>
|
||||
</Stack>
|
||||
{data.length ? (
|
||||
<Stack sx={{ p: '30px 25px 40px 25px', backgroundColor: '#f5f6f8' }}>
|
||||
<DataGrid
|
||||
minBodyHeight={300}
|
||||
bodyHeight={500}
|
||||
data={data}
|
||||
columns={columns}
|
||||
columnOptions={{
|
||||
resizable: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</PageTitleBox>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { Stack, TextField } from '@mui/material';
|
||||
import SubmitButton from '@/components/button/SubmitButton';
|
||||
|
||||
const inputStyle = {
|
||||
width: '800px',
|
||||
};
|
||||
|
||||
const BigQueryDatabaseForm = props => {
|
||||
const { testConnect, formData, setFormData } = props;
|
||||
const handleSubmit = data => {
|
||||
data.preventDefault();
|
||||
const item = {
|
||||
name: data.target.name.value,
|
||||
projectId: data.target.projectId.value,
|
||||
keyFilename: data.target.keyFilename.value,
|
||||
schema: data.target.schema.value,
|
||||
};
|
||||
testConnect(item);
|
||||
};
|
||||
|
||||
const handleNameChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, [event.target.name]: event.target.value }));
|
||||
};
|
||||
|
||||
const handleChange = event => {
|
||||
setFormData(prevState => ({
|
||||
...prevState,
|
||||
bigquery: { ...prevState.bigquery, [event.target.name]: event.target.value },
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack component="form" sx={{ maxWidth: 800, mx: 'auto', mt: 3 }} onSubmit={handleSubmit}>
|
||||
<Stack sx={{ display: 'flex', justifyContent: 'space-between' }} spacing="20px">
|
||||
<TextField
|
||||
label="이름"
|
||||
name="name"
|
||||
value={formData?.name || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Project Id"
|
||||
name="projectId"
|
||||
value={formData?.bigquery?.projectId || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="File Name"
|
||||
name="keyFilename"
|
||||
value={formData?.bigquery?.keyFilename || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Schema"
|
||||
name="schema"
|
||||
value={formData?.bigquery?.schema || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<SubmitButton label="TEST CONNECT" type="submit" sx={{ height: '50px', fontSize: '13px' }} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default BigQueryDatabaseForm;
|
||||
@@ -8,14 +8,12 @@ const inputStyle = {
|
||||
|
||||
const DatabaseForm = props => {
|
||||
const { testConnect, formData, setFormData } = props;
|
||||
console.log('formData', formData);
|
||||
const handleSubmit = data => {
|
||||
console.log('DatabaseForm');
|
||||
data.preventDefault();
|
||||
const item = {
|
||||
name: data.target.name.value,
|
||||
host: data.target.host.value,
|
||||
port: data.target.port.value,
|
||||
port: Number(data.target.port.value),
|
||||
user: data.target.user.value,
|
||||
password: data.target.password.value,
|
||||
database: data.target.database.value,
|
||||
@@ -23,26 +21,30 @@ const DatabaseForm = props => {
|
||||
testConnect(item);
|
||||
};
|
||||
|
||||
const handleChange = event => {
|
||||
const handleNameChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, [event.target.name]: event.target.value }));
|
||||
};
|
||||
|
||||
const handleChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, default: { ...prevState.default, [event.target.name]: event.target.value } }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack component="form" sx={{ maxWidth: 800, mx: 'auto', mt: 3 }} onSubmit={handleSubmit}>
|
||||
<Stack sx={{ display: 'flex', justifyContent: 'space-between' }} spacing="20px">
|
||||
<TextField
|
||||
label="이름"
|
||||
name="databaseName"
|
||||
value={formData.databaseName}
|
||||
name="name"
|
||||
value={formData?.name || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Host"
|
||||
name="host"
|
||||
value={formData.host}
|
||||
value={formData?.default?.host || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
@@ -51,7 +53,8 @@ const DatabaseForm = props => {
|
||||
<TextField
|
||||
label="Port"
|
||||
name="port"
|
||||
value={formData.port}
|
||||
type="number"
|
||||
value={formData?.default?.port || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
@@ -60,7 +63,7 @@ const DatabaseForm = props => {
|
||||
<TextField
|
||||
label="User"
|
||||
name="user"
|
||||
value={formData.user}
|
||||
value={formData?.default?.user || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
@@ -69,7 +72,7 @@ const DatabaseForm = props => {
|
||||
<TextField
|
||||
label="Password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
value={formData?.default?.password || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
@@ -78,13 +81,13 @@ const DatabaseForm = props => {
|
||||
<TextField
|
||||
label="Schema"
|
||||
name="database"
|
||||
value={formData.database}
|
||||
value={formData?.default?.database || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<SubmitButton label="Test Connect" type="submit" />
|
||||
<SubmitButton label="TEST CONNECT" type="submit" sx={{ height: '50px', fontSize: '13px' }} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import { Stack, TextField } from '@mui/material';
|
||||
import SubmitButton from '@/components/button/SubmitButton';
|
||||
|
||||
const inputStyle = {
|
||||
width: '800px',
|
||||
};
|
||||
|
||||
const OracleDatabaseForm = props => {
|
||||
const { testConnect, formData, setFormData } = props;
|
||||
const handleSubmit = data => {
|
||||
data.preventDefault();
|
||||
const item = {
|
||||
name: data.target.name.value,
|
||||
host: data.target.host.value,
|
||||
port: Number(data.target.port.value),
|
||||
user: data.target.user.value,
|
||||
password: data.target.password.value,
|
||||
database: data.target.database.value,
|
||||
instanceName: data.target.instanceName.value,
|
||||
fetchAsString: data.target.fetchAsString.value,
|
||||
requestTimeout: data.target.requestTimeout.value,
|
||||
};
|
||||
testConnect(item);
|
||||
};
|
||||
|
||||
const handleNameChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, [event.target.name]: event.target.value }));
|
||||
};
|
||||
|
||||
const handleChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, oracle: { ...prevState.oracle, [event.target.name]: event.target.value } }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack component="form" sx={{ maxWidth: 800, mx: 'auto', mt: 3 }} onSubmit={handleSubmit}>
|
||||
<Stack sx={{ display: 'flex', justifyContent: 'space-between' }} spacing="20px">
|
||||
<TextField
|
||||
label="이름"
|
||||
name="name"
|
||||
value={formData?.name || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Host"
|
||||
name="host"
|
||||
value={formData?.oracle?.host || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Port"
|
||||
name="port"
|
||||
type="number"
|
||||
value={formData?.oracle?.port || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="User"
|
||||
name="user"
|
||||
value={formData?.oracle?.user || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Password"
|
||||
name="password"
|
||||
value={formData?.oracle?.password || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Database"
|
||||
name="database"
|
||||
value={formData?.oracle?.database || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="InstanceName"
|
||||
name="instanceName"
|
||||
value={formData?.oracle?.instanceName || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="FetchAsString"
|
||||
name="fetchAsString"
|
||||
value={formData?.oracle?.fetchAsString || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="RequestTimeout"
|
||||
name="requestTimeout"
|
||||
value={formData?.oracle?.requestTimeout || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<SubmitButton label="TEST CONNECT" type="submit" sx={{ height: '50px', fontSize: '13px' }} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default OracleDatabaseForm;
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { Stack, TextField } from '@mui/material';
|
||||
import SubmitButton from '@/components/button/SubmitButton';
|
||||
|
||||
const inputStyle = {
|
||||
width: '800px',
|
||||
};
|
||||
|
||||
const SnowflakeDatabaseForm = props => {
|
||||
const { testConnect, formData, setFormData } = props;
|
||||
const handleSubmit = data => {
|
||||
data.preventDefault();
|
||||
const item = {
|
||||
name: data.target.name.value,
|
||||
account: data.target.account.value,
|
||||
username: data.target.username.value,
|
||||
password: data.target.password.value,
|
||||
database: data.target.database.value,
|
||||
application: data.target.application.value,
|
||||
schema: data.target.schema.value,
|
||||
warehouse: data.target.warehouse.value,
|
||||
};
|
||||
testConnect(item);
|
||||
};
|
||||
|
||||
const handleNameChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, [event.target.name]: event.target.value }));
|
||||
};
|
||||
|
||||
const handleChange = event => {
|
||||
setFormData(prevState => ({
|
||||
...prevState,
|
||||
snowflake: { ...prevState.snowflake, [event.target.name]: event.target.value },
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack component="form" sx={{ maxWidth: 800, mx: 'auto', mt: 3 }} onSubmit={handleSubmit}>
|
||||
<Stack sx={{ display: 'flex', justifyContent: 'space-between' }} spacing="20px">
|
||||
<TextField
|
||||
label="이름"
|
||||
name="name"
|
||||
value={formData?.name || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Account"
|
||||
name="account"
|
||||
value={formData?.snowflake?.account || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Username"
|
||||
name="username"
|
||||
value={formData?.snowflake?.username || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Password"
|
||||
name="password"
|
||||
value={formData?.snowflake?.password || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Database"
|
||||
name="database"
|
||||
value={formData?.snowflake?.database || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>{' '}
|
||||
<TextField
|
||||
label="Application"
|
||||
name="application"
|
||||
value={formData?.snowflake?.application || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Schema"
|
||||
name="schema"
|
||||
value={formData?.snowflake?.schema || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Warehouse"
|
||||
name="warehouse"
|
||||
value={formData?.snowflake?.warehouse || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<SubmitButton label="TEST CONNECT" type="submit" sx={{ height: '50px', fontSize: '13px' }} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SnowflakeDatabaseForm;
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { Stack, TextField } from '@mui/material';
|
||||
import SubmitButton from '@/components/button/SubmitButton';
|
||||
|
||||
const inputStyle = {
|
||||
width: '800px',
|
||||
};
|
||||
|
||||
const SqliteDatabaseForm = props => {
|
||||
const { testConnect, formData, setFormData } = props;
|
||||
const handleSubmit = data => {
|
||||
data.preventDefault();
|
||||
const item = {
|
||||
name: data.target.name.value,
|
||||
filename: data.target.filename.value,
|
||||
};
|
||||
testConnect(item);
|
||||
};
|
||||
|
||||
const handleNameChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, [event.target.name]: event.target.value }));
|
||||
};
|
||||
|
||||
const handleChange = event => {
|
||||
setFormData(prevState => ({ ...prevState, sqlite: { ...prevState.sqlite, [event.target.name]: event.target.value } }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack component="form" sx={{ maxWidth: 800, mx: 'auto', mt: 3 }} onSubmit={handleSubmit}>
|
||||
<Stack sx={{ display: 'flex', justifyContent: 'space-between' }} spacing="20px">
|
||||
<TextField
|
||||
label="이름"
|
||||
name="name"
|
||||
value={formData?.name || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleNameChange}
|
||||
/>
|
||||
<TextField
|
||||
label="Filename"
|
||||
name="filename"
|
||||
value={formData?.sqlite?.filename || ''}
|
||||
required
|
||||
fullWidth
|
||||
sx={inputStyle}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<SubmitButton label="TEST CONNECT" type="submit" sx={{ height: '50px', fontSize: '13px' }} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SqliteDatabaseForm;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useLayoutEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import PageTitleBox from '@/components/PageTitleBox';
|
||||
@@ -10,23 +10,66 @@ import { getDatabaseIcon } from '@/widget/utils/iconUtil';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useAlert } from 'react-alert';
|
||||
import DatabaseForm from '@/pages/Data/DataSource/form/DatabaseForm';
|
||||
import SqliteDatabaseForm from '@/pages/Data/DataSource/form/SqliteDatabaseForm';
|
||||
import BigQueryDatabaseForm from '@/pages/Data/DataSource/form/BigQueryDatabaseForm';
|
||||
import { SnackbarContext } from '@/contexts/AlertContext';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
import SnowflakeDatabaseForm from '@/pages/Data/DataSource/form/SnowflakeDatabaseForm';
|
||||
import OracleDatabaseForm from '@/pages/Data/DataSource/form/OracleDatabaseForm';
|
||||
|
||||
function DataSource() {
|
||||
const { sourceId } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const alert = useAlert();
|
||||
const snackbar = useAlert(SnackbarContext);
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
|
||||
const [isModifyMode, setIsModifyMode] = useState(false);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [dataType, setDataType] = useState(null);
|
||||
const [typeList, setTypeList] = useState([]);
|
||||
const [formData, setFormData] = useState({
|
||||
databaseName: '',
|
||||
host: '',
|
||||
port: '',
|
||||
user: '',
|
||||
password: '',
|
||||
database: '',
|
||||
const [formData, setFormData] = useState<any>({
|
||||
type: '',
|
||||
name: '',
|
||||
default: {
|
||||
host: '',
|
||||
port: null,
|
||||
user: '',
|
||||
password: '',
|
||||
database: '',
|
||||
},
|
||||
sqlite: {
|
||||
// name: '',
|
||||
filename: '',
|
||||
},
|
||||
bigquery: {
|
||||
// name: '',
|
||||
projectId: '',
|
||||
filename: '',
|
||||
database: '',
|
||||
},
|
||||
oracle: {
|
||||
// name: '',
|
||||
host: '',
|
||||
port: null,
|
||||
user: '',
|
||||
password: '',
|
||||
database: '',
|
||||
instanceName: '',
|
||||
fetchAsString: '',
|
||||
requestTimeout: '',
|
||||
},
|
||||
snowflake: {
|
||||
// name: '',
|
||||
account: '',
|
||||
username: '',
|
||||
password: '',
|
||||
database: '',
|
||||
application: '',
|
||||
schema: '',
|
||||
warehouse: '',
|
||||
},
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -44,93 +87,180 @@ function DataSource() {
|
||||
/**
|
||||
* 수정일 경우 데이터베이스 정보 조회
|
||||
*/
|
||||
useEffect(() => {
|
||||
getType();
|
||||
}, [typeList, formData.type]);
|
||||
|
||||
const getDatabaseInfo = () => {
|
||||
DatabaseService.selectDatabase(sourceId).then(response => {
|
||||
const info = response.data;
|
||||
if (info.status === 'SUCCESS') {
|
||||
const databaseInfo = info.data.databaseInfo;
|
||||
const temp = {
|
||||
databaseName: databaseInfo.name,
|
||||
host: databaseInfo.connectionConfig.host,
|
||||
port: databaseInfo.connectionConfig.port,
|
||||
user: databaseInfo.connectionConfig.user,
|
||||
password: databaseInfo.connectionConfig.password,
|
||||
database: databaseInfo.connectionConfig.database,
|
||||
};
|
||||
setFormData(temp);
|
||||
}
|
||||
});
|
||||
showLoading();
|
||||
DatabaseService.selectDatabaseInfo(sourceId)
|
||||
.then(response => {
|
||||
const info = response.data;
|
||||
if (info.status === 'SUCCESS') {
|
||||
const databaseInfo = info.data.databaseInfo;
|
||||
const temp: any = {
|
||||
name: databaseInfo.name,
|
||||
type: databaseInfo.type,
|
||||
};
|
||||
if (databaseInfo.type === 'sqlite') {
|
||||
temp.sqlite = {
|
||||
filename: databaseInfo.connectionConfig.filename,
|
||||
};
|
||||
} else if (databaseInfo.type === 'bigquery') {
|
||||
temp.bigquery = {
|
||||
projectId: databaseInfo.connectionConfig.projectId,
|
||||
keyFilename: databaseInfo.connectionConfig.keyFilename,
|
||||
schema: databaseInfo.connectionConfig.schema,
|
||||
};
|
||||
} else if (databaseInfo.type === 'oracle') {
|
||||
temp.oracle = {
|
||||
host: databaseInfo.connectionConfig.host,
|
||||
port: Number(databaseInfo.connectionConfig.port),
|
||||
user: databaseInfo.connectionConfig.user,
|
||||
password: databaseInfo.connectionConfig.password,
|
||||
database: databaseInfo.connectionConfig.database,
|
||||
instanceName: databaseInfo.connectionConfig.instanceName,
|
||||
fetchAsString: databaseInfo.connectionConfig.fetchAsString,
|
||||
requestTimeout: databaseInfo.connectionConfig.requestTimeout,
|
||||
};
|
||||
} else if (databaseInfo.type === 'snowflake') {
|
||||
temp.snowflake = {
|
||||
account: databaseInfo.connectionConfig.account,
|
||||
username: databaseInfo.connectionConfig.username,
|
||||
password: databaseInfo.connectionConfig.password,
|
||||
database: databaseInfo.connectionConfig.database,
|
||||
application: databaseInfo.connectionConfig.application,
|
||||
schema: databaseInfo.connectionConfig.schema,
|
||||
warehouse: databaseInfo.connectionConfig.warehouse,
|
||||
};
|
||||
} else {
|
||||
temp.default = {
|
||||
host: databaseInfo.connectionConfig.host,
|
||||
port: Number(databaseInfo.connectionConfig.port),
|
||||
user: databaseInfo.connectionConfig.user,
|
||||
password: databaseInfo.connectionConfig.password,
|
||||
database: databaseInfo.connectionConfig.database,
|
||||
};
|
||||
}
|
||||
setFormData(temp);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const testConnect = item => {
|
||||
showLoading();
|
||||
const param = {
|
||||
// name: item.name,
|
||||
// description: item.name,
|
||||
name: item.name,
|
||||
description: item.name,
|
||||
connectionConfig: item,
|
||||
engine: dataType.type,
|
||||
engine: dataType.engine,
|
||||
};
|
||||
console.log(param, 'param');
|
||||
DatabaseService.testConnection(param).then(response => {
|
||||
console.log(response);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
setIsConnected(true);
|
||||
alert.info('데이터베이스 연결에 성공하였습니다.');
|
||||
} else {
|
||||
alert.info('데이터베이스에 연결할 수 없습니다.\n데이터 베이스 정보를 확인해주세요.');
|
||||
}
|
||||
});
|
||||
DatabaseService.testConnection(param)
|
||||
.then(response => {
|
||||
console.log(response);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
setIsConnected(true);
|
||||
snackbar.success('데이터베이스 연결에 성공했습니다.');
|
||||
} else {
|
||||
snackbar.error('데이터베이스에 연결할 수 없습니다. 데이터베이스 정보를 확인해주세요');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const getDatabaseTypeList = () => {
|
||||
DatabaseService.selectDatabaseTypeList().then(response => {
|
||||
console.log('selectDatabaseTypeList', response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
const list = response.data.data;
|
||||
list.map(item => (item.icon = getDatabaseIcon(item.type)));
|
||||
setTypeList(list);
|
||||
if (list.length > 0) {
|
||||
setDataType(list[0]);
|
||||
showLoading();
|
||||
DatabaseService.selectDatabaseTypeList()
|
||||
.then(response => {
|
||||
console.log('selectDatabaseTypeList', response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
const list = response.data.data;
|
||||
list.map(item => (item.icon = getDatabaseIcon(item.type)));
|
||||
setTypeList(list);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
|
||||
const getType = () => {
|
||||
if (typeList.length > 0 && formData.type) {
|
||||
const type = typeList.filter(item => item.type === formData.type);
|
||||
setDataType(type[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const getFormComponentType = () => {
|
||||
switch (dataType.type) {
|
||||
case 'sqlite':
|
||||
return 'sqlite';
|
||||
case 'bigquery':
|
||||
return 'bigquery';
|
||||
case 'oracle':
|
||||
return 'oracle';
|
||||
case 'snowflake':
|
||||
return 'snowflake';
|
||||
default:
|
||||
return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveClick = () => {
|
||||
const param = {
|
||||
name: formData.databaseName,
|
||||
description: formData.databaseName,
|
||||
connectionConfig: formData,
|
||||
engine: dataType.type,
|
||||
name: formData.name,
|
||||
description: formData.name,
|
||||
type: dataType.type,
|
||||
engine: dataType.engine,
|
||||
connectionConfig: formData[getFormComponentType()],
|
||||
};
|
||||
|
||||
// 숫자 형변환
|
||||
if (param?.connectionConfig?.port) {
|
||||
param.connectionConfig.port = Number(param.connectionConfig.port);
|
||||
}
|
||||
|
||||
alert.success(`데이터베이스를 ${isModifyMode ? '수정' : '생성'}하시겠습니까?`, {
|
||||
closeCopy: '취소',
|
||||
actions: [
|
||||
{
|
||||
copy: '확인',
|
||||
onClick: () => {
|
||||
showLoading();
|
||||
if (isModifyMode) {
|
||||
DatabaseService.updateDatabase(sourceId, param).then(response => {
|
||||
console.log(response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
console.log('데이터 베이스 저장', param);
|
||||
alert.info('데이터베이스가 수정되었습니다.', {
|
||||
onClose: () => {
|
||||
navigate('/data');
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
DatabaseService.updateDatabase(sourceId, param)
|
||||
.then(response => {
|
||||
console.log(response.data);
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
console.log('데이터베이스 저장', param);
|
||||
navigate('/data');
|
||||
snackbar.success('데이터베이스가 수정되었습니다.');
|
||||
} else {
|
||||
alert.error('데이터베이스 저장에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
} else {
|
||||
DatabaseService.createDatabase(param).then(response => {
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
alert.info('데이터베이스가 생성되었습니다.', {
|
||||
onClose: () => {
|
||||
navigate('/data');
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
DatabaseService.createDatabase(param)
|
||||
.then(response => {
|
||||
if (response.data.status === STATUS.SUCCESS) {
|
||||
console.log('데이터베이스 저장', param);
|
||||
navigate('/data');
|
||||
snackbar.success('데이터베이스가 생성되었습니다.');
|
||||
} else {
|
||||
alert.error('데이터베이스 저장에 실패했습니다.');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -138,10 +268,32 @@ function DataSource() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleTypeClick = item => {
|
||||
setDataType(item);
|
||||
setFormData({ type: item.type });
|
||||
};
|
||||
|
||||
const handleCancelClick = () => {
|
||||
navigate('/data');
|
||||
};
|
||||
|
||||
const dbType = () => {
|
||||
switch (dataType?.type) {
|
||||
case 'sqlite':
|
||||
return <SqliteDatabaseForm testConnect={testConnect} formData={formData} setFormData={setFormData} />;
|
||||
case 'bigquery':
|
||||
return <BigQueryDatabaseForm testConnect={testConnect} formData={formData} setFormData={setFormData} />;
|
||||
case 'oracle':
|
||||
return <OracleDatabaseForm testConnect={testConnect} formData={formData} setFormData={setFormData} />;
|
||||
case 'snowflake':
|
||||
return <SnowflakeDatabaseForm testConnect={testConnect} formData={formData} setFormData={setFormData} />;
|
||||
case undefined:
|
||||
return <Typography sx={{ my: '160px', mx: 'auto' }}>데이터 소스 타입을 먼저 선택해 주세요.</Typography>;
|
||||
default:
|
||||
return <DatabaseForm testConnect={testConnect} formData={formData} setFormData={setFormData} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageTitleBox
|
||||
@@ -150,7 +302,7 @@ function DataSource() {
|
||||
title={'데이터 소스 연결'}
|
||||
button={
|
||||
<ConfirmCancelButton
|
||||
confirmProps={{ disabled: false, onClick: handleSaveClick }}
|
||||
confirmProps={{ disabled: !isConnected, onClick: handleSaveClick }}
|
||||
cancelProps={{ onClick: handleCancelClick }}
|
||||
/>
|
||||
}
|
||||
@@ -165,7 +317,12 @@ function DataSource() {
|
||||
>
|
||||
step.01 타입 설정
|
||||
</Typography>
|
||||
<ImgCardList data={typeList} selectedType={dataType} setSelectedType={setDataType} />
|
||||
<ImgCardList
|
||||
data={typeList}
|
||||
selectedType={dataType}
|
||||
setSelectedType={setDataType}
|
||||
handleTypeClick={handleTypeClick}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack sx={{ p: '30px 25px 50px 25px', bgcolor: '#f5f6f8' }}>
|
||||
<Typography
|
||||
@@ -175,7 +332,7 @@ function DataSource() {
|
||||
>
|
||||
step.02 연결 정보 입력
|
||||
</Typography>
|
||||
<DatabaseForm testConnect={testConnect} formData={formData} setFormData={setFormData} />
|
||||
{dbType()}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</PageTitleBox>
|
||||
|
||||
@@ -7,7 +7,7 @@ import PageTitleBox from '@/components/PageTitleBox';
|
||||
* 데이터 관리 페이지
|
||||
* @constructor
|
||||
*/
|
||||
function DataPage() {
|
||||
const Data = () => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageTitleBox title="데이터" sx={{ paddingLeft: 0, paddingRight: 0, width: '100%', height: '100%' }}>
|
||||
@@ -15,6 +15,6 @@ function DataPage() {
|
||||
</PageTitleBox>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default DataPage;
|
||||
export default Data;
|
||||
|
||||
120
frontend-web/src/pages/Login/index.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Box, Button, Link, Stack, TextField, Typography } from '@mui/material';
|
||||
import { useAlert } from 'react-alert';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
import { ReactComponent as Logo } from '@/assets/images/logo.svg';
|
||||
import backgroundImage from '@/assets/images/visual-bg.png';
|
||||
|
||||
function Copyright(props: any) {
|
||||
return (
|
||||
<Typography color="text.secondary" align="center" {...props}>
|
||||
<Link
|
||||
color="inherit"
|
||||
href="https://vanillabrain.com/"
|
||||
sx={{ fontSize: '13px', color: '#767676', textDecoration: 'none' }}
|
||||
>
|
||||
@ Vanilla Meta 2022
|
||||
</Link>
|
||||
{/*{' '}*/}
|
||||
{/*{new Date().getFullYear()}*/}
|
||||
{/*{'.'}*/}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
const Login = () => {
|
||||
const { onLogin } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const alert = useAlert();
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
const [userInfo] = useState({
|
||||
userId: process.env.REACT_APP_MODE === 'local' ? process.env.REACT_APP_ID : '',
|
||||
userPwd: process.env.REACT_APP_MODE === 'local' ? process.env.REACT_APP_PWD : '',
|
||||
});
|
||||
|
||||
const handleLogin = async event => {
|
||||
event.preventDefault();
|
||||
showLoading();
|
||||
await onLogin(event.target.userId.value, event.target.userPwd.value)
|
||||
.then(res => {
|
||||
if (res) {
|
||||
navigate('/dashboard');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading();
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
component="main"
|
||||
sx={{ position: 'relative', zIndex: 0, width: '100%', height: '100%', minWidth: '100%', backgroundColor: '#f5f6f8' }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: '90px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Logo width="223px" height="43px" />
|
||||
|
||||
<Typography sx={{ mt: '17px', fontSize: '16px', color: '#043f84' }}>
|
||||
통합 데이터분석을 위한{' '}
|
||||
<Typography component="span" sx={{ fontSize: '16px', fontWeight: 'bold' }}>
|
||||
대시보드 리포팅 솔루션
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Stack component="form" onSubmit={handleLogin} noValidate sx={{ width: '360px', mt: '56px' }} spacing="20px">
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="userId"
|
||||
label="User ID"
|
||||
name="email"
|
||||
defaultValue={userInfo.userId}
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
name="userPwd"
|
||||
defaultValue={userInfo.userPwd}
|
||||
label="Password"
|
||||
type="password"
|
||||
id="password"
|
||||
sx={{ height: '36px' }}
|
||||
/>
|
||||
{/*<FormControlLabel control={<Checkbox value="remember" color="primary" />} label="Remember me" />*/}
|
||||
<Button type="submit" size="large" fullWidth variant="contained" sx={{ mt: 3, mb: 2 }}>
|
||||
Login
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Copyright sx={{ mt: 8, mb: 4 }} />
|
||||
<Box
|
||||
component="img"
|
||||
src={backgroundImage}
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
zIndex: -1,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
margin: 'auto',
|
||||
width: '1024px',
|
||||
height: '608px',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
@@ -8,13 +8,13 @@ import { STATUS } from '@/constant';
|
||||
import { LayoutContext } from '@/contexts/LayoutContext';
|
||||
import { LoadingContext } from '@/contexts/LoadingContext';
|
||||
import grid from '@/assets/images/grid.svg';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SnackbarContext } from '@/contexts/AlertContext';
|
||||
|
||||
const WidgetAttributeSelect = props => {
|
||||
const { widgetOption, saveWidgetInfo, dataset, isModifyMode = false, widgetTypeName, widgetTypeDescription } = props;
|
||||
|
||||
const alert = useAlert();
|
||||
const navigate = useNavigate();
|
||||
const snackbar = useAlert(SnackbarContext);
|
||||
const { fixLayout } = useContext(LayoutContext);
|
||||
const { showLoading, hideLoading } = useContext(LoadingContext);
|
||||
|
||||
@@ -46,7 +46,9 @@ const WidgetAttributeSelect = props => {
|
||||
const getData = () => {
|
||||
const param = isModifyMode
|
||||
? { datasetType: widgetOption.datasetType, datasetId: widgetOption.datasetId }
|
||||
: { datasetType: dataset.datasetType, datasetId: dataset.id };
|
||||
: dataset.datasetType === 'TABLE'
|
||||
? { databaseId: dataset.databaseId, datasetType: dataset.datasetType, tableName: dataset.tableName }
|
||||
: { databaseId: dataset.databaseId, datasetType: dataset.datasetType, datasetId: dataset.id };
|
||||
console.log('getData param', param);
|
||||
showLoading();
|
||||
DatabaseService.selectData(param)
|
||||
@@ -65,7 +67,7 @@ const WidgetAttributeSelect = props => {
|
||||
event.preventDefault();
|
||||
|
||||
// confirm sample
|
||||
alert.success('위젯 속성을 저장하시겠습니까?', {
|
||||
alert.success('위젯을 저장하시겠습니까?', {
|
||||
title: '위젯 저장',
|
||||
closeCopy: '취소',
|
||||
actions: [
|
||||
@@ -73,7 +75,7 @@ const WidgetAttributeSelect = props => {
|
||||
copy: '저장',
|
||||
onClick: () => {
|
||||
saveWidgetInfo(option, title);
|
||||
navigate('/widget');
|
||||
snackbar.success('위젯이 저장되었습니다.');
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -99,12 +101,14 @@ const WidgetAttributeSelect = props => {
|
||||
<Box
|
||||
sx={{
|
||||
width: '60%',
|
||||
height: '500px',
|
||||
minHeight: '500px',
|
||||
maxHeight: '100%',
|
||||
margin: '54px auto',
|
||||
border: '1px solid #e2e2e2',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '2px 2px 9px 0 rgba(42, 50, 62, 0.1), 0 4px 4px 0 rgba(0, 0, 0, 0.02)',
|
||||
backgroundColor: '#fff',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<WidgetViewer
|
||||
|
||||
@@ -1,39 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import DataLayout from '@/pages/Data/DataLayout';
|
||||
|
||||
function WidgetDataSelect(props) {
|
||||
const { setDataSet } = props;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loadedData, setLoadedData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
}, []);
|
||||
|
||||
// 선택한 데이터베이스를 CardList에서 가져와서 저장하고 dataSet과 dataList를 보여주는 state
|
||||
const [presentedData, setPresentedData] = useState({
|
||||
dataSource: 0,
|
||||
dataList: '',
|
||||
dataSet: '',
|
||||
});
|
||||
|
||||
const handleUpdate = enteredData => {
|
||||
if (typeof enteredData === 'object') {
|
||||
setPresentedData(prevState => ({ ...prevState, ...enteredData }));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadedData.filter((item, index) => {
|
||||
if (presentedData.dataSource === item.id) {
|
||||
const selectedArray = loadedData[index];
|
||||
handleUpdate({ dataList: selectedArray.dataList, dataSet: selectedArray.dataSet });
|
||||
// console.log(presentedData);
|
||||
}
|
||||
});
|
||||
}, [presentedData.dataSource]);
|
||||
|
||||
return <DataLayout isViewMode={true} setDataSet={setDataSet} />;
|
||||
}
|
||||
|
||||
|
||||