Browse Source

support json

Signed-off-by: shanghaikid <jiangruiyi@gmail.com>
shanghaikid 2 years ago
parent
commit
f7dfedd2fd

+ 48 - 45
client/src/components/advancedSearch/Condition.tsx

@@ -11,6 +11,7 @@ import { ConditionProps, Field } from './Types';
 import CustomSelector from '../customSelector/CustomSelector';
 import { LOGICAL_OPERATORS } from '../../consts/Util';
 import { DataTypeStringEnum } from '../../pages/collections/Types';
+import { formatValue, checkValue } from './utils';
 
 const Condition: FC<ConditionProps> = props => {
   const {
@@ -28,65 +29,52 @@ const Condition: FC<ConditionProps> = props => {
   const [conditionField, setConditionField] = useState<Field>(
     initData?.field || fields[0] || {}
   );
-  const [conditionValue, setConditionValue] = useState(initData?.value || '');
+  const [jsonKeyValue, setJsonKeyValue] = useState(initData?.jsonKey || '');
+  const [conditionValue, setConditionValue] = useState(
+    initData?.originValue || ''
+  );
   const [isValuelegal, setIsValueLegal] = useState(
     initData?.isCorrect || false
   );
 
+  const [isKeyLegal, setIsKeyLegal] = useState(initData?.isCorrect || false);
+
   /**
    * Check condition's value by field's and operator's type.
    * Trigger condition change event.
    */
   useEffect(() => {
-    const regInt = /^\d+$/;
-    const regFloat = /^\d+\.\d+$/;
-    const regIntInterval = /^\[\d+(,\d+)*\]$/;
-    const regFloatInterval = /^\[\d+\.\d+(,\d+\.\d+)*\]$/;
-
     const type = conditionField?.type;
-    const isIn = operator === 'in';
-    let isLegal = false;
     const conditionValueWithNoSpace = conditionValue.replaceAll(' ', '');
+    let isKeyLegal = false;
+    let isLegal = checkValue({
+      value: conditionValueWithNoSpace,
+      type,
+      operator,
+    });
 
-    switch (type) {
-      case DataTypeStringEnum.Int8:
-      case DataTypeStringEnum.Int16:
-      case DataTypeStringEnum.Int32:
-      case DataTypeStringEnum.Int64:
-        // case DataTypeStringEnum:
-        isLegal = isIn
-          ? regIntInterval.test(conditionValueWithNoSpace)
-          : regInt.test(conditionValueWithNoSpace);
-        break;
-      case DataTypeStringEnum.Float:
-      case DataTypeStringEnum.Double:
-      case DataTypeStringEnum.FloatVector:
-        isLegal = isIn
-          ? regFloatInterval.test(conditionValueWithNoSpace)
-          : regFloat.test(conditionValueWithNoSpace);
-        break;
-      case DataTypeStringEnum.Bool:
-        const legalValues = ['false', 'true'];
-        isLegal = legalValues.includes(conditionValueWithNoSpace);
-        break;
-      case DataTypeStringEnum.VarChar:
-        isLegal = conditionValueWithNoSpace !== '';
-        break;
-      default:
-        isLegal = false;
-        break;
+    // if type is json, check the json key is valid
+    if (type === DataTypeStringEnum.JSON) {
+      isKeyLegal = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(jsonKeyValue.trim());
     }
+
+    setIsKeyLegal(isKeyLegal);
     setIsValueLegal(isLegal);
     triggerChange(id, {
       field: conditionField,
       op: operator,
-      value: conditionValue,
-      isCorrect: isLegal,
+      jsonKey: jsonKeyValue,
+      value: formatValue(conditionValue, type, operator),
+      originValue: conditionValue,
+      isCorrect:
+        isLegal &&
+        ((type === DataTypeStringEnum.JSON && isKeyLegal) ||
+          type !== DataTypeStringEnum.JSON),
       id,
     });
     // No need for 'id' and 'triggerChange'.
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [conditionField, operator, conditionValue]);
+  }, [conditionField, operator, conditionValue, jsonKeyValue]);
 
   const classes = useStyles();
 
@@ -115,22 +103,36 @@ const Condition: FC<ConditionProps> = props => {
   // Value input change.
   const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
     const value = event.target.value;
-    const type = conditionField?.type;
-    setConditionValue(
-      type === DataTypeStringEnum.VarChar ? `"${value}"` : value
-    );
+    setConditionValue(value);
+  };
+
+  // Value JSON change.
+  const handleJSONKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    const value = event.target.value;
+    setJsonKeyValue(value);
   };
 
   return (
     <div className={`${classes.wrapper} ${className}`} {...others}>
       <CustomSelector
-        label="Field Name"
+        label={conditionField.type}
         value={conditionField?.name}
         onChange={handleFieldNameChange}
         options={fields.map(i => ({ value: i.name, label: i.name }))}
         variant="filled"
         wrapperClass={classes.fieldName}
       />
+      {conditionField?.type === DataTypeStringEnum.JSON ? (
+        <TextField
+          className={classes.key}
+          label="key"
+          variant="filled"
+          value={jsonKeyValue}
+          // size="small"
+          onChange={handleJSONKeyChange}
+          error={!isKeyLegal}
+        />
+      ) : null}
       <CustomSelector
         label="Logic"
         value={operator}
@@ -143,7 +145,7 @@ const Condition: FC<ConditionProps> = props => {
         className={classes.value}
         label="Value"
         variant="filled"
-        // size="small"
+        value={conditionValue}
         onChange={handleValueChange}
         error={!isValuelegal}
       />
@@ -178,7 +180,8 @@ const useStyles = makeStyles((theme: Theme) =>
       minHeight: '38px',
       minWidth: '130px',
     },
-    logic: { minHeight: '38px', minWidth: '70px', margin: '0 24px' },
+    logic: { minHeight: '38px', minWidth: '100px', margin: '0 24px' },
+    key: { minHeight: '38px', width: '100px', margin: '0 0' },
     value: { minHeight: '38px', minWidth: '130px' },
   })
 );

+ 15 - 1
client/src/components/advancedSearch/Filter.tsx

@@ -77,14 +77,28 @@ const Filter = forwardRef((props: FilterProps, ref) => {
     const expression = conditions.reduce((prev, item) => {
       const { type, data } = item;
       if (type === 'break') return `${prev} || `;
+
       const {
         field: { name },
         op,
+        jsonKey,
         value,
       } = data;
+
+      let n = name;
+
+      // if type is json, format json expression
+      switch (data.field.type) {
+        case 'JSON':
+          n = `${name}["${jsonKey}"]`;
+          break;
+        default:
+          break;
+      }
+
       return `${prev}${
         prev && !prev.endsWith('|| ') ? ' && ' : ''
-      }${name} ${op} ${value}`;
+      }${n} ${op} ${value}`;
     }, '');
     func(expression);
   };

+ 2 - 0
client/src/components/advancedSearch/Types.ts

@@ -19,8 +19,10 @@ export interface TriggerChangeData {
   field: Field;
   op: string;
   value: string;
+  originValue: string;
   isCorrect: boolean;
   id: string;
+  jsonKey: string;
 }
 
 export interface ConditionGroupProps {

+ 89 - 0
client/src/components/advancedSearch/utils.ts

@@ -0,0 +1,89 @@
+import { DataTypeStringEnum } from '../../pages/collections/Types';
+
+export const formatValue = (value: string, type: string, operator: string) => {
+  let conditionValue: string = ''; //
+  switch (type) {
+    case DataTypeStringEnum.VarChar:
+      conditionValue = `"${value}"`;
+      break;
+    case DataTypeStringEnum.JSON:
+      switch (operator) {
+        case '<':
+        case '>':
+        case '==':
+        case '>=':
+        case '<=':
+          conditionValue = value;
+          break;
+        case 'in':
+        case 'not in':
+          conditionValue = `[${value}]`;
+          break;
+        default:
+          conditionValue = `"${value}"`;
+          break;
+      }
+      break;
+    default:
+      conditionValue = value;
+  }
+
+  return conditionValue;
+};
+
+export const checkValue = (data: any): boolean => {
+  let isLegal = false;
+
+  const regInt = /^\d+$/;
+  const regFloat = /^\d+\.\d+$/;
+  const regIntInterval = /^\[\d+(,\d+)*\]$/;
+  const regFloatInterval = /^\[\d+\.\d+(,\d+\.\d+)*\]$/;
+  const isIn = data.operator === 'in';
+
+  switch (data.type) {
+    case DataTypeStringEnum.Int8:
+    case DataTypeStringEnum.Int16:
+    case DataTypeStringEnum.Int32:
+    case DataTypeStringEnum.Int64:
+      // case DataTypeStringEnum:
+      isLegal = isIn
+        ? regIntInterval.test(data.value)
+        : regInt.test(data.value);
+      break;
+    case DataTypeStringEnum.Float:
+    case DataTypeStringEnum.Double:
+    case DataTypeStringEnum.FloatVector:
+      isLegal = isIn
+        ? regFloatInterval.test(data.value)
+        : regFloat.test(data.value);
+      break;
+    case DataTypeStringEnum.Bool:
+      const legalValues = ['false', 'true'];
+      isLegal = legalValues.includes(data.value);
+      break;
+    case DataTypeStringEnum.VarChar:
+      isLegal = data.value !== '';
+      break;
+    case DataTypeStringEnum.JSON:
+      let type = DataTypeStringEnum.VarChar;
+      switch (data.operator) {
+        case '>':
+        case '<':
+        case '>=':
+        case '<=':
+          type = DataTypeStringEnum.Int64;
+      }
+
+      isLegal = checkValue({
+        value: data.value,
+        type: type,
+        operator: data.operator,
+      });
+      break;
+    default:
+      isLegal = false;
+      break;
+  }
+
+  return isLegal;
+};

+ 8 - 0
client/src/consts/Util.ts

@@ -34,4 +34,12 @@ export const LOGICAL_OPERATORS = [
     value: 'in',
     label: 'in',
   },
+  {
+    value: 'not in',
+    label: 'not in',
+  },
+  {
+    value: 'like',
+    label: 'like',
+  },
 ];

+ 4 - 1
client/src/pages/collections/Constants.ts

@@ -61,7 +61,6 @@ export const ALL_OPTIONS: KeyValuePair[] = [
     label: 'Double',
     value: DataTypeEnum.Double,
   },
-
   {
     label: 'Boolean',
     value: DataTypeEnum.Bool,
@@ -70,6 +69,10 @@ export const ALL_OPTIONS: KeyValuePair[] = [
     label: 'VarChar',
     value: DataTypeEnum.VarChar,
   },
+  {
+    label: 'JSON',
+    value: DataTypeEnum.JSON,
+  },
 ];
 
 export const AUTO_ID_OPTIONS: KeyValuePair[] = [

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

@@ -68,6 +68,7 @@ export enum DataTypeEnum {
   Double = 11,
   String = 20,
   VarChar = 21,
+  JSON = 23,
   BinaryVector = 100,
   FloatVector = 101,
 }
@@ -81,6 +82,7 @@ export enum DataTypeStringEnum {
   Double = 'Double',
   String = 'String',
   VarChar = 'VarChar',
+  JSON = 'JSON',
   BinaryVector = 'BinaryVector',
   FloatVector = 'FloatVector',
 }

+ 1 - 1
client/src/pages/dialogs/CreateCollectionDialog.tsx

@@ -68,7 +68,7 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
   });
 
   const [consistencyLevel, setConsistencyLevel] =
-    useState<ConsistencyLevelEnum>(ConsistencyLevelEnum.Session); // Session is the default value of consistency level
+    useState<ConsistencyLevelEnum>(ConsistencyLevelEnum.Bounded); // Session is the default value of consistency level
 
   const [fields, setFields] = useState<Field[]>([
     {

+ 20 - 14
client/src/pages/preview/Preview.tsx

@@ -37,21 +37,27 @@ const Preview: FC<{
         // Iterate resultItem keys, then format vector(array) items.
         const tmp = Object.keys(resultItem).reduce(
           (prev: { [key: string]: any }, item: string) => {
-            if (Array.isArray(resultItem[item])) {
-              const list2Str = JSON.stringify(resultItem[item]);
-              prev[item] = (
-                <div className={classes.vectorTableCell}>
-                  <div>{list2Str}</div>
-                  <CopyButton
-                    label={copyTrans.label}
-                    value={list2Str}
-                    className={classes.copyBtn}
-                  />
-                </div>
-              );
-            } else {
-              prev[item] = `${resultItem[item]}`;
+            switch (item) {
+              case 'json':
+                prev[item] = <div>{JSON.stringify(resultItem[item])}</div>;
+                break;
+              case 'vector':
+                const list2Str = JSON.stringify(resultItem[item]);
+                prev[item] = (
+                  <div className={classes.vectorTableCell}>
+                    <div>{list2Str}</div>
+                    <CopyButton
+                      label={copyTrans.label}
+                      value={list2Str}
+                      className={classes.copyBtn}
+                    />
+                  </div>
+                );
+                break;
+              default:
+                prev[item] = `${resultItem[item]}`;
             }
+
             return prev;
           },
           {}

+ 8 - 1
client/src/utils/search.ts

@@ -30,7 +30,14 @@ export const transferSearchResult = (
     // When value is boolean ,table will not render bool value.
     // So we need to use toString() here.
     Object.keys(others).forEach(v => {
-      data[v] = others[v].toString();
+      switch (v) {
+        case 'json':
+          data[v] = JSON.stringify(others[v]);
+          break;
+        default:
+          data[v] = others[v].toString();
+          break;
+      }
     });
     return data;
   });

+ 1 - 1
server/package.json

@@ -12,7 +12,7 @@
     "url": "https://github.com/zilliztech/attu"
   },
   "dependencies": {
-    "@zilliz/milvus2-sdk-node": "^2.2.9",
+    "@zilliz/milvus2-sdk-node": "^2.2.17",
     "axios": "^1.3.2",
     "chalk": "^4.1.2",
     "class-sanitizer": "^1.0.1",

+ 14 - 0
server/src/utils/Helper.ts

@@ -28,6 +28,8 @@ export const genDataByType = ({ data_type, type_params }: FieldSchema) => {
       );
     case 'VarChar':
       return makeRandomId((type_params as any)[0].value);
+    case 'JSON':
+      return makeRandomJSON();
   }
 };
 
@@ -59,3 +61,15 @@ export const makeRandomId = (length: number): string => {
   }
   return result;
 };
+
+export const makeRandomJSON = () => {
+  const obj: any = {};
+  const numKeys = Math.floor(Math.random() * 10) + 1; // generate a random number of keys between 1 and 10
+  for (let i = 0; i < numKeys; i++) {
+    const key = `key${i}`;
+    const value =
+      Math.random() < 0.5 ? Math.floor(Math.random() * 100) : `value${i}`; // randomly choose between a number or a string value
+    obj[key] = value;
+  }
+  return obj;
+};

+ 194 - 8
server/yarn.lock

@@ -355,6 +355,11 @@
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
+"@colors/colors@1.5.0":
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
+  integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
+
 "@cspotcode/source-map-consumer@0.8.0":
   version "0.8.0"
   resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
@@ -367,6 +372,15 @@
   dependencies:
     "@cspotcode/source-map-consumer" "0.8.0"
 
+"@dabh/diagnostics@^2.0.2":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a"
+  integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==
+  dependencies:
+    colorspace "1.1.x"
+    enabled "2.0.x"
+    kuler "^2.0.0"
+
 "@develar/schema-utils@~2.6.5":
   version "2.6.5"
   resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6"
@@ -412,7 +426,7 @@
     "@grpc/proto-loader" "^0.7.0"
     "@types/node" ">=12.12.47"
 
-"@grpc/proto-loader@^0.7.0", "@grpc/proto-loader@^0.7.6":
+"@grpc/proto-loader@^0.7.0":
   version "0.7.6"
   resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.6.tgz#b71fdf92b184af184b668c4e9395a5ddc23d61de"
   integrity sha512-QyAXR8Hyh7uMDmveWxDSUcJr9NAWaZ2I6IXgAYvQmfflwouTM+rArE2eEaCtLlRqO81j7pRLCt81IefUei6Zbw==
@@ -423,6 +437,17 @@
     protobufjs "^7.0.0"
     yargs "^16.2.0"
 
+"@grpc/proto-loader@^0.7.7":
+  version "0.7.7"
+  resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.7.tgz#d33677a77eea8407f7c66e2abd97589b60eb4b21"
+  integrity sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==
+  dependencies:
+    "@types/long" "^4.0.1"
+    lodash.camelcase "^4.3.0"
+    long "^4.0.0"
+    protobufjs "^7.0.0"
+    yargs "^17.7.2"
+
 "@istanbuljs/load-nyc-config@^1.0.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1010,6 +1035,11 @@
     "@types/express" "*"
     "@types/serve-static" "*"
 
+"@types/triple-beam@^1.3.2":
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8"
+  integrity sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==
+
 "@types/validator@^13.1.3":
   version "13.6.6"
   resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.6.6.tgz#6e6e2d086148db5ae14851614971b715670cbd52"
@@ -1053,14 +1083,17 @@
   dependencies:
     "@types/node" "*"
 
-"@zilliz/milvus2-sdk-node@^2.2.9":
-  version "2.2.9"
-  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.2.9.tgz#7be9ad6e60285cfe57dcb0124bf579c62e52ab6d"
-  integrity sha512-tfes4xOQNj2ZxmbaKUH4wOVFnJ5Jy2vQlwJPqfwQl6DzBHB14Nxkwjz7gtQuhPlVvMF0W7lVQ6W8KBRdbN1n5Q==
+"@zilliz/milvus2-sdk-node@^2.2.17":
+  version "2.2.17"
+  resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.2.17.tgz#533eb52d257ce2c9e550ed30b8021d037bc64496"
+  integrity sha512-sJLYia6hC6E2BKStaKs1jpvOU95RaEM3LFNCQG45Y8TDsWBq+5mFzyoQBdxc8OWWVuACBTUtwzxTZ83eI2FVew==
   dependencies:
     "@grpc/grpc-js" "^1.8.14"
-    "@grpc/proto-loader" "^0.7.6"
+    "@grpc/proto-loader" "^0.7.7"
+    dayjs "^1.11.7"
+    lru-cache "^9.1.2"
     protobufjs "^7.2.3"
+    winston "^3.9.0"
 
 abab@^2.0.3, abab@^2.0.5:
   version "2.0.5"
@@ -1730,7 +1763,7 @@ collect-v8-coverage@^1.0.0:
   resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59"
   integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==
 
-color-convert@^1.9.0:
+color-convert@^1.9.0, color-convert@^1.9.3:
   version "1.9.3"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
   integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
@@ -1749,16 +1782,40 @@ color-name@1.1.3:
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@~1.1.4:
+color-name@^1.0.0, color-name@~1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
+color-string@^1.6.0:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
+  integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
+  dependencies:
+    color-name "^1.0.0"
+    simple-swizzle "^0.2.2"
+
+color@^3.1.3:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
+  integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
+  dependencies:
+    color-convert "^1.9.3"
+    color-string "^1.6.0"
+
 colors@1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
   integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
 
+colorspace@1.1.x:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243"
+  integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==
+  dependencies:
+    color "^3.1.3"
+    text-hex "1.0.x"
+
 combined-stream@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@@ -1939,6 +1996,11 @@ data-urls@^2.0.0:
     whatwg-mimetype "^2.3.0"
     whatwg-url "^8.0.0"
 
+dayjs@^1.11.7:
+  version "1.11.8"
+  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea"
+  integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==
+
 debug@2.6.9, debug@^2.6.8:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -2211,6 +2273,11 @@ emoji-regex@^8.0.0:
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
   integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
 
+enabled@2.0.x:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
+  integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
+
 encodeurl@^1.0.2, encodeurl@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@@ -2436,6 +2503,11 @@ fd-slicer@~1.1.0:
   dependencies:
     pend "~1.2.0"
 
+fecha@^4.2.0:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
+  integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==
+
 filelist@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
@@ -2471,6 +2543,11 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+fn.name@1.x.x:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
+  integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
+
 follow-redirects@^1.15.0:
   version "1.15.2"
   resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
@@ -2913,6 +2990,11 @@ ipaddr.js@1.9.1:
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
   integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
 
+is-arrayish@^0.3.1:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
+  integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
+
 is-binary-path@~2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -3608,6 +3690,11 @@ kleur@^3.0.3:
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
   integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
 
+kuler@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
+  integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
+
 latest-version@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
@@ -3675,6 +3762,18 @@ lodash@^4.17.10, lodash@^4.17.15, lodash@^4.7.0:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
+logform@^2.3.2, logform@^2.4.0:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/logform/-/logform-2.5.1.tgz#44c77c34becd71b3a42a3970c77929e52c6ed48b"
+  integrity sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==
+  dependencies:
+    "@colors/colors" "1.5.0"
+    "@types/triple-beam" "^1.3.2"
+    fecha "^4.2.0"
+    ms "^2.1.1"
+    safe-stable-stringify "^2.3.1"
+    triple-beam "^1.3.0"
+
 long@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
@@ -3702,6 +3801,11 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
+lru-cache@^9.1.2:
+  version "9.1.2"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.2.tgz#255fdbc14b75589d6d0e73644ca167a8db506835"
+  integrity sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==
+
 make-dir@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@@ -4001,6 +4105,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
   dependencies:
     wrappy "1"
 
+one-time@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
+  integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==
+  dependencies:
+    fn.name "1.x.x"
+
 onetime@^5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
@@ -4306,6 +4417,15 @@ read-config-file@6.2.0:
     json5 "^2.2.0"
     lazy-val "^1.0.4"
 
+readable-stream@^3.4.0:
+  version "3.6.2"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
+  integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+  dependencies:
+    inherits "^2.0.3"
+    string_decoder "^1.1.1"
+    util-deprecate "^1.0.1"
+
 readable-stream@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
@@ -4402,6 +4522,11 @@ safe-buffer@~5.2.0:
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
+safe-stable-stringify@^2.3.1:
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
+  integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
+
 "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -4539,6 +4664,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3:
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
   integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
 
+simple-swizzle@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
+  integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
+  dependencies:
+    is-arrayish "^0.3.1"
+
 simple-update-notifier@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc"
@@ -4637,6 +4769,11 @@ sprintf-js@~1.0.2:
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
 
+stack-trace@0.0.x:
+  version "0.0.10"
+  resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
+  integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==
+
 stack-utils@^2.0.3:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
@@ -4834,6 +4971,11 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     minimatch "^3.0.4"
 
+text-hex@1.0.x:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
+  integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
+
 throat@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
@@ -4908,6 +5050,11 @@ tr46@^2.1.0:
   dependencies:
     punycode "^2.1.1"
 
+triple-beam@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
+  integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
+
 truncate-utf8-bytes@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b"
@@ -5200,6 +5347,32 @@ widest-line@^3.1.0:
   dependencies:
     string-width "^4.0.0"
 
+winston-transport@^4.5.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa"
+  integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==
+  dependencies:
+    logform "^2.3.2"
+    readable-stream "^3.6.0"
+    triple-beam "^1.3.0"
+
+winston@^3.9.0:
+  version "3.9.0"
+  resolved "https://registry.yarnpkg.com/winston/-/winston-3.9.0.tgz#2bbdeb8167a75fac6d9a0c6d002890cd908016c2"
+  integrity sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==
+  dependencies:
+    "@colors/colors" "1.5.0"
+    "@dabh/diagnostics" "^2.0.2"
+    async "^3.2.3"
+    is-stream "^2.0.0"
+    logform "^2.4.0"
+    one-time "^1.0.0"
+    readable-stream "^3.4.0"
+    safe-stable-stringify "^2.3.1"
+    stack-trace "0.0.x"
+    triple-beam "^1.3.0"
+    winston-transport "^4.5.0"
+
 word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
@@ -5315,6 +5488,19 @@ yargs@^17.5.1:
     y18n "^5.0.5"
     yargs-parser "^21.1.1"
 
+yargs@^17.7.2:
+  version "17.7.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+  integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+  dependencies:
+    cliui "^8.0.1"
+    escalade "^3.1.1"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.3"
+    y18n "^5.0.5"
+    yargs-parser "^21.1.1"
+
 yauzl@^2.10.0:
   version "2.10.0"
   resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"