lodash#has TypeScript Examples

The following examples show how to use lodash#has. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts    From ui5-language-assistant with Apache License 2.0 7 votes vote down vote up
export function findValueInMaps<T>(
  key: string,
  ...maps: Record<string, T>[]
): T | undefined {
  for (const mapElement of maps) {
    if (has(mapElement, key)) {
      return mapElement[key];
    }
  }
  return undefined;
}
Example #2
Source File: utils.ts    From redux-with-domain with MIT License 7 votes vote down vote up
// get state by path
// parent module's data aside in .base
export function getStateByNamespace(state, namespace, initialState) {
  const path = toStorePath(namespace)
  const initialStateKeys = keys(initialState)
  const findState = get(state, path)
  if (findState === undefined) {
    throw Error(`Please check if you forget to add module ${path} `)
  }

  if (isEmpty(initialState)) {
    if (findState['@@loading']) {
      return findState
    }
    return get(state, `${path}.base`) // not in base
  }
  let isModuleState = true
  initialStateKeys.forEach(key => {
    if (!has(findState, key)) {
      isModuleState = false
    }
  })
  if (isModuleState) return findState
  return get(state, `${path}.base`)
}
Example #3
Source File: pivot-data-set.ts    From S2 with MIT License 6 votes vote down vote up
public getTotalStatus = (query: DataType) => {
    const { columns, rows } = this.fields;
    const isTotals = (dimensions: string[], isSubTotal?: boolean) => {
      if (isSubTotal) {
        const firstDimension = find(dimensions, (item) => !has(query, item));
        return firstDimension && firstDimension !== first(dimensions);
      }
      return every(dimensions, (item) => !has(query, item));
    };
    const getDimensions = (dimensions: string[], hasExtra: boolean) => {
      return hasExtra
        ? dimensions.filter((item) => item !== EXTRA_FIELD)
        : dimensions;
    };

    return {
      isRowTotal: isTotals(
        getDimensions(rows, !this.spreadsheet.isValueInCols()),
      ),
      isRowSubTotal: isTotals(rows, true),
      isColTotal: isTotals(
        getDimensions(columns, this.spreadsheet.isValueInCols()),
      ),
      isColSubTotal: isTotals(columns, true),
    };
  };
Example #4
Source File: packet.utils.ts    From nestjs-jaeger-tracing with MIT License 6 votes vote down vote up
export function isRequestPacket<T>(
  packet: unknown,
): packet is RequestPacket<T> {
  const keys: Array<keyof RequestPacket<T>> = ['id', 'data', 'pattern'];
  return every(keys, partial(has, packet));
}
Example #5
Source File: get-right-field-in-query.ts    From S2 with MIT License 6 votes vote down vote up
export function getRightFieldInQuery(
  rowQuery: Record<string, any>,
  rowFields: string[],
): string {
  let field = '';
  for (let k = rowFields.length - 1; k >= 0; k--) {
    if (has(rowQuery, rowFields[k])) {
      field = rowFields[k]; // 行头中的维度
      break;
    }
  }
  return field;
}
Example #6
Source File: global-secondary-index.ts    From dyngoose with ISC License 6 votes vote down vote up
/**
   * Performs a query and returns the first item matching your filters.
   *
   * The regular DynamoDB.GetItem does not work for indexes, because DynamoDB only enforces a
   * unique cross of the tale's primary HASH and RANGE key. The combination of those two values
   * must always be unique, however, for a GlobalSecondaryIndex, there is no uniqueness checks
   * or requirements. This means that querying for a record by the HASH and RANGE on a
   * GlobalSecondaryIndex it is always possible there are multiple matching records.
   *
   * So use this with caution. It sets the DynamoDB Limit parameter to 1, which means this will
   * not work for additional filtering. If you want to provide additional filtering, use the
   * .query() method and pass your filters, then handle if the query has more than one result.
   *
   * Avoid use whenever you do not have uniqueness for the GlobalSecondaryIndex's HASH + RANGE.
   */
  public async get(filters: QueryFilters<T>, input: GlobalSecondaryIndexGetInput = {}): Promise<T | undefined> {
    if (!has(filters, this.metadata.hash.propertyName)) {
      throw new QueryError('Cannot perform .get() on a GlobalSecondaryIndex without specifying a hash key value')
    } else if (this.metadata.range != null && !has(filters, this.metadata.range.propertyName)) {
      throw new QueryError('Cannot perform .get() on a GlobalSecondaryIndex without specifying a range key value')
    } else if (Object.keys(filters).length > 2) {
      throw new QueryError('Cannot perform a .get() on a GlobalSecondaryIndex with additional filters, use .query() instead')
    }

    (input as GlobalSecondaryIndexQueryInput).limit = 1

    // because you are specifying the hashKey and rangeKey, we can apply a limit to this search
    // DynamoDB will start the search at the first match and limit means it will only process
    // that document and return it, however, you cannot use any additional filters on this .get
    // method; for that, you need to use .query()
    const results = await this.query(filters, input)

    if (results.count > 0) {
      return results[0]
    }
  }
Example #7
Source File: api.ts    From brick-design with MIT License 6 votes vote down vote up
function responseAdaptor(ret: any, api: ApiObject) {
  let hasStatusField = true;
  if (!ret) {
    throw new Error('Response is empty!');
  } else if (!has(ret, 'status')) {
    hasStatusField = false;
  }
  const result = ret.data || ret.result;
  const payload: Payload = {
    ok: hasStatusField === false || ret.status == 0,
    status: hasStatusField === false ? 0 : ret.status,
    msg: ret.msg || ret.message,
    msgTimeout: ret.msgTimeout,
    data: Array.isArray(result) ? { items: result } : result, // 兼容直接返回数据的情况
    isNotState: api.isNotState,
    isPageState: api.isPageState,
  };

  if (payload.status == 422) {
    payload.errors = ret.errors;
  }

  if (payload.ok && api.responseData) {
    payload.data = dataMapping(
      api.responseData,
      createObject({ api }, payload.data || {}),
    );
  }
  return payload;
}
Example #8
Source File: packet.utils.ts    From nestjs-jaeger-tracing with MIT License 6 votes vote down vote up
export function isTracingContext(packet: unknown): packet is TracingContext {
  const keys: Array<keyof TracingContext> = ['payload', 'isSerialized'];
  return every(keys, partial(has, packet));
}
Example #9
Source File: fixture.ts    From ts-di-starter with MIT License 6 votes vote down vote up
checkFixture = (fixture, expected): void => {
  const { expressMiddleware, expressControllers } = fixture;

  const updateExpectationsFromFixture = (fixtureObj, expectedObj) => {
    return Object.keys(fixtureObj).reduce((_expectedObj, key) => {
      const isStub = has(fixtureObj[key], 'callsFake');

      if (isStub) {
        fixtureObj[key] = fixtureObj[key].callCount;
      }

      if (!has(_expectedObj, key)) {
        if (isStub) {
          _expectedObj[key] = 0;
        } else {
          _expectedObj[key] = {};
        }
      }

      if (isObject(fixtureObj[key])) {
        updateExpectationsFromFixture(fixtureObj[key], _expectedObj[key]);
      }

      return _expectedObj;
    }, expectedObj);
  };

  updateExpectationsFromFixture(expressControllers, expected.controllers);
  updateExpectationsFromFixture(expressMiddleware, expected.middleware);

  expect(expressControllers).to.deep.equal(expected.controllers);
  expect(expressMiddleware).to.deep.equal(expected.middleware);
}
Example #10
Source File: external-item.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
getSelectOptions = (options: Option[], filterKey: string) => {
  if (!filterKey) return options;
  const useableOptions: Option[] = [];

  options.forEach((item) => {
    let curOp: Option | null = null;
    if (has(item, 'children')) {
      curOp = { ...item, children: [] };
      item.children?.forEach((cItem) => {
        if (filterMatch(`${cItem.label}`, filterKey)) {
          curOp?.children?.push(cItem);
        }
      });
      if (curOp.children?.length) useableOptions.push(curOp);
    } else if (filterMatch(`${item.label}`, filterKey)) {
      curOp = item;
      curOp && useableOptions.push(curOp);
    }
  });
  return useableOptions;
}
Example #11
Source File: util.ts    From gio-design with Apache License 2.0 6 votes vote down vote up
dimensionToPropertyItem: TypeMapping = (item: Dimension, localeText: typeof defaultLocale) => {
  const result: PropertyItem = { label: item.name, value: item.id, ...item };
  const { id, groupId, type: _type, associatedKey } = item;

  result.iconId = groupId;

  if (groupId === 'normal' && _type === 'global') {
    const [newGroupId, newGroupName] = PreparedNormalDimensionIdMap(id, localeText);
    result.groupId = newGroupId;
    result.groupName = newGroupName;
    result.iconId = newGroupId;
  }

  const dimensionGroupType = DimensionGroupTypeMapper[result.groupId ?? 'event'];
  result.previewTypeName = PropertyTypes(localeText)[dimensionGroupType === 'avar' ? 'event' : dimensionGroupType];

  // 多物品模型,物品属性不再作为事件绑定属性,而是作为事件属性的属性来展示,作为事件属性的子集
  // 所以,当存在parentId的时候,物品属性,和事件属性同组
  if (groupId === 'item' && _type === 'itm' && associatedKey) {
    result.iconId = 'item';
    result.groupId = 'event';
    result.previewTypeName = localeText.dimensionTable;
    result.groupName = localeText.eventVariables;
  }

  // 虚拟属性需要添加到事件属性中,但是有自己的type和groupId,所以和维度表(多物品模型)做相同处理
  if (groupId === 'virtual' && _type === 'vvar') {
    result.iconId = 'virtual';
    result.groupName = localeText.virtualProperties;
    result.previewTypeName = localeText.virtualProperties;
  }

  const gOrder = PropertyGroupOrder.indexOf(result.groupId as string);
  result.groupOrder = gOrder > -1 ? gOrder : 99999;

  result.type = DimensionGroupTypeMapper[result.groupId || 'unkown'];
  result.typeName = PropertyTypes(localeText)[result.type];
  const tOrder = ['event', 'avar', 'usr'].indexOf(result.type);
  result.typeOrder = tOrder > -1 ? tOrder : 99999;

  if (has(item, 'isSystem') && groupId !== 'virtual') {
    return regroup(result, localeText);
  }
  return result;
}
Example #12
Source File: kbn.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
kbn.describe_interval = (str: string) => {
  const matches = str.match(kbn.interval_regex);
  if (!matches || !has(kbn.intervals_in_seconds, matches[2])) {
    throw new Error('Invalid interval string, expecting a number followed by one of "Mwdhmsy"');
  } else {
    return {
      sec: kbn.intervals_in_seconds[matches[2]],
      type: matches[2],
      count: parseInt(matches[1], 10),
    };
  }
};
Example #13
Source File: semantic-model-provider.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
export async function downloadLibraries(
  version: TestModelVersion
): Promise<void> {
  if (!has(downloadedLibrariesPromises, version)) {
    downloadedLibrariesPromises[version] = addUi5Resources(
      version,
      getModelFolder(version)
    );
  }
  return downloadedLibrariesPromises[version];
}
Example #14
Source File: pivot-data-set.ts    From S2 with MIT License 6 votes vote down vote up
protected standardTransform(originData: Data[], fieldsValues: string[]) {
    if (isEmpty(fieldsValues)) {
      return originData;
    }
    const transformedData = [];
    forEach(fieldsValues, (value) => {
      forEach(originData, (dataItem) => {
        if (has(dataItem, value)) {
          transformedData.push({
            ...dataItem,
            [EXTRA_FIELD]: value,
            [VALUE_FIELD]: dataItem[value],
          });
        }
      });
    });
    return transformedData;
  }
Example #15
Source File: resolve.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
function fixTypeName(
  fqn: string | undefined,
  typeNameFix: TypeNameFix
): string | undefined {
  if (fqn === undefined || ignoreType(fqn)) {
    return undefined;
  }
  if (has(typeNameFix, fqn)) {
    return typeNameFix[fqn];
  }
  return fqn;
}
Example #16
Source File: operation.ts    From openapi-mock-express-middleware with MIT License 6 votes vote down vote up
getResponseSchema(responseStatus = 200): JSONSchema | null {
    if (
      has(this.operation, ['responses', responseStatus, 'content', 'application/json', 'schema'])
    ) {
      const { schema, example, examples } = get(this.operation, [
        'responses',
        responseStatus,
        'content',
        'application/json',
      ]);

      if (schema && !isReferenceObject(schema)) {
        const resultSchema: JSONSchema = schema as JSONSchema;

        if (example) {
          resultSchema.example = example;
        }

        if (examples) {
          resultSchema.examples = examples;
        }

        return resultSchema;
      }

      return null;
    }

    return null;
  }
Example #17
Source File: analysis-utils.ts    From prism-frontend with MIT License 6 votes vote down vote up
hasKeys = (obj: any, keys: string[]): boolean =>
  !keys.find(key => !has(obj, key))
Example #18
Source File: validateBody.ts    From openapi-mock-express-middleware with MIT License 6 votes vote down vote up
validateBody = (
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): void | express.Response => {
  if (
    !res.locals.operation ||
    !(res.locals.operation instanceof Operation) ||
    !has(res.locals.operation.operation, 'requestBody')
  ) {
    return next();
  }

  const bodySchema = res.locals.operation.getBodySchema(
    req.get('content-type') || 'application/json'
  );

  if (Object.keys(req.body).length && !bodySchema) {
    return res.status(400).json({
      message: 'Bad request. Invalid content type.',
    });
  }

  if (bodySchema) {
    const isBodyValid = validator.validate(bodySchema, req.body);

    if (!isBodyValid) {
      return res.status(400).json({
        message: 'Bad request. Invalid request body.',
        errors: validator.errors,
      });
    }
  }

  return next();
}
Example #19
Source File: pivot-data-set.ts    From S2 with MIT License 5 votes vote down vote up
public getMultiData(
    query: DataType,
    isTotals?: boolean,
    isRow?: boolean,
    drillDownFields?: string[],
  ): DataType[] {
    if (isEmpty(query)) {
      return compact(customFlattenDeep(this.indexesData));
    }
    const { rows, columns, values: valueList } = this.fields;
    const totalRows = !isEmpty(drillDownFields)
      ? rows.concat(drillDownFields)
      : rows;
    const rowDimensionValues = getQueryDimValues(totalRows, query);
    const colDimensionValues = getQueryDimValues(columns, query);
    const path = getDataPath({
      rowDimensionValues,
      colDimensionValues,
      careUndefined: true,
      isFirstCreate: true,
      rowFields: rows,
      colFields: columns,
      rowPivotMeta: this.rowPivotMeta,
      colPivotMeta: this.colPivotMeta,
    });
    const currentData = this.getCustomData(path);
    let result = compact(customFlatten(currentData));
    if (isTotals) {
      // 总计/小计(行/列)
      // need filter extra data
      // grand total =>  {$$extra$$: 'price'}
      // sub total => {$$extra$$: 'price', category: 'xxxx'}
      // [undefined, undefined, "price"] => [category]
      let fieldKeys = [];
      const rowKeys = getFieldKeysByDimensionValues(rowDimensionValues, rows);
      const colKeys = getFieldKeysByDimensionValues(
        colDimensionValues,
        columns,
      );
      if (isRow) {
        // 行总计
        fieldKeys = rowKeys;
      } else {
        // 只有一个值,此时为列总计
        const isCol = keys(query)?.length === 1 && has(query, EXTRA_FIELD);

        if (isCol) {
          fieldKeys = colKeys;
        } else {
          const getTotalStatus = (dimensions: string[]) => {
            return isEveryUndefined(
              dimensions?.filter((item) => !valueList?.includes(item)),
            );
          };
          const isRowTotal = getTotalStatus(colDimensionValues);
          const isColTotal = getTotalStatus(rowDimensionValues);

          if (isRowTotal) {
            // 行小计
            fieldKeys = rowKeys;
          } else if (isColTotal) {
            // 列小计
            fieldKeys = colKeys;
          } else {
            // 行小计+列 or 列小计+行
            fieldKeys = [...rowKeys, ...colKeys];
          }
        }
      }
      result = result.filter(
        (r) =>
          !fieldKeys?.find(
            (item) => item !== EXTRA_FIELD && keys(r)?.includes(item),
          ),
      );
    }

    return result || [];
  }
Example #20
Source File: settings-config-spec.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
describe("settings configuration properties", () => {
  let packageJsonSettings: Record<string, Setting>;

  before(() => {
    // Get the settings from the package.json
    const packageJsonPath = require.resolve(
      "vscode-ui5-language-assistant/package.json"
    );
    const packageJsonContent = readJsonSync(packageJsonPath);
    packageJsonSettings =
      packageJsonContent.contributes.configuration.properties;
  });

  it("default setting values in package.json have the correct type", () => {
    forEach(packageJsonSettings, (value, key) => {
      expect(
        typeof value.default,
        `Setting ${key} default value type does not match the setting's defined type`
      ).to.equal(value.type);
    });
  });

  it("settings in package.json are in sync with the settings package", () => {
    const defaultSettingsFromPackageJson = parseSettings(packageJsonSettings);
    const defaultSettings = getDefaultSettings();
    expect(
      defaultSettingsFromPackageJson,
      "settings from package.json don't match the default settings in the language server"
    ).to.deep.equal({
      UI5LanguageAssistant: defaultSettings,
    });
  });

  it("enums in package.json are in sync with the settings package", () => {
    const pkgJsonSettingsWithEnum = pickBy(packageJsonSettings, (_) =>
      has(_, "enum")
    );
    forEach(pkgJsonSettingsWithEnum, (pkgJsonSetting, settingsKey) => {
      const settingsModulePropName = camelCase(
        settingsKey.replace(
          /UI5LanguageAssistant.(\w+)\.(\w+)/,
          "valid $1 $2 values"
        )
      );
      const settingsModulePropValue = keys(
        settingsModule[settingsModulePropName]
      );
      const pkgJsonPropValue = pkgJsonSetting.enum;
      expect(settingsModulePropValue).to.deep.equalInAnyOrder(pkgJsonPropValue);
    });
  });

  it("use the correct logging configuration property name", () => {
    expect(packageJsonSettings[LOGGING_LEVEL_CONFIG_PROP]).to.exist;
    expect(
      packageJsonSettings[LOGGING_LEVEL_CONFIG_PROP].description
    ).to.include("logging");
  });

  type Setting = {
    scope: string;
    type: string;
    default: unknown;
    description: string;
    enum?: string[];
  };

  function parseSettings(properties: Record<string, Setting>): unknown {
    const defaultSettings = {};
    forEach(properties, (value, key) => {
      set(defaultSettings, key, value.default);
    });
    return defaultSettings;
  }
});
Example #21
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
PureFilter = (props: IPureFilterProps) => {
  const {
    filterTirgger = 'onChange',
    connectUrlSearch = false,
    updateSearch,
    onFilter = noop,
    query = {},
    className = '',
    formatFormData,
    urlExtra,
    ...rest
  } = props;
  // const query = routeInfoStore.getState(s => s.query);
  const filterRef: any = React.useRef(null as any);
  useMount(() => {
    const { pageNo: _, ...fieldsValue } = query;
    // 关联url, 将query初始化到表单
    if (connectUrlSearch && !isEmpty(fieldsValue)) {
      setTimeout(() => {
        setFieldsValue(fieldsValue);
      }, 0);
    } else if (filterTirgger === 'onChange') {
      setTimeout(() => {
        // 只能在setTimeout中拿到初始请求的值
        const formData = filterRef.current?.form.getFieldsValue();
        changeFilterData(formData);
      }, 0);
    }
  });

  React.useEffect(() => {
    if (!isEmpty(urlExtra)) {
      const filterForm = get(filterRef, 'current.form');
      const filterData = filterForm ? filterForm.getFieldsValue() : {};
      updateSearch({ ...urlExtra, ...filterData });
    }
  }, [updateSearch, urlExtra]);

  const debounceFilter = debounce((filterData: Obj) => {
    if (connectUrlSearch) {
      updateSearch(filterData);
    }
    onFilter(filterData);
  }, 1000);

  // filter变化的时候调用
  const changeFilterData = (filterData: Obj) => {
    debounceFilter(filterData);
  };

  const setFieldsValue = (obj: Obj) => {
    const filterForm = get(filterRef, 'current.form');
    if (filterForm) {
      const filterFields = filterForm.getFieldsValue();
      const formValue = {};
      map(filterFields, (_, _key) => has(obj, _key) && set(formValue, _key, obj[_key]));
      filterForm.setFieldsValue(formatFormData ? formatFormData(formValue) : formValue);
      changeFilterData(obj); // 带上除filter之外的其他参数,如pageNo
    }
  };

  const filterProps = {
    onChange: {
      actions: [],
    },
    onSubmit: {},
  };

  return (
    <BaseFilter
      onSubmit={changeFilterData}
      className={`dice-filter my-3 ${className}`}
      {...rest}
      {...(filterProps[filterTirgger] || {})}
      ref={filterRef}
    />
  );
}
Example #22
Source File: resolve.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
function ignoreType(typeName: string): boolean {
  return has(typesToIgnore, typeName);
}
Example #23
Source File: plugin.ts    From lift with MIT License 5 votes vote down vote up
resolveReference({ address }: { address: string }): { value: string } {
        return {
            /**
             * Construct variables are resolved lazily using the CDK's "Token" system.
             * CDK Lazy values generate a unique `${Token[TOKEN.63]}` string. These strings
             * can later be resolved to the real value (which we do in `initialize()`).
             * Problem:
             * - Lift variables need constructs to be resolved
             * - Constructs can be created when Serverless variables are resolved
             * - Serverless variables must resolve Lift variables
             * This is a chicken and egg problem.
             * Solution:
             * - Serverless boots, plugins are created
             * - variables are resolved
             *   - Lift variables are resolved to CDK tokens (`${Token[TOKEN.63]}`) via `Lazy.any(...)`
             *     (we can't resolve the actual values since we don't have the constructs yet)
             * - `initialize` hook
             *   - Lift builds the constructs
             *   - CDK tokens are resolved into real value: we can now do that using the CDK "token resolver"
             */
            value: Lazy.any({
                produce: () => {
                    const constructs = this.getConstructs();
                    const [id, property] = address.split(".", 2);
                    if (!has(this.constructs, id)) {
                        throw new ServerlessError(
                            `No construct named '${id}' was found, the \${construct:${id}.${property}} variable is invalid.`,
                            "LIFT_VARIABLE_UNKNOWN_CONSTRUCT"
                        );
                    }
                    const construct = constructs[id];

                    const properties = construct.variables ? construct.variables() : {};
                    if (!has(properties, property)) {
                        if (Object.keys(properties).length === 0) {
                            throw new ServerlessError(
                                `\${construct:${id}.${property}} does not exist. The construct '${id}' does not expose any property`,
                                "LIFT_VARIABLE_UNKNOWN_PROPERTY"
                            );
                        }
                        throw new ServerlessError(
                            `\${construct:${id}.${property}} does not exist. Properties available on \${construct:${id}} are: ${Object.keys(
                                properties
                            ).join(", ")}`,
                            "LIFT_VARIABLE_UNKNOWN_PROPERTY"
                        );
                    }

                    return properties[property];
                },
            }).toString(),
        };
    }
Example #24
Source File: convert.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
function convertLibraryToSemanticModel(
  libName: string,
  lib: apiJson.SchemaForApiJsonFiles,
  jsonSymbols: Record<string, apiJson.ConcreteSymbol>,
  strict: boolean
): model.UI5SemanticModel {
  const model: model.UI5SemanticModel = {
    version: lib.version,
    includedLibraries: [],
    classes: newMap(),
    interfaces: newMap(),
    enums: newMap(),
    functions: newMap(),
    namespaces: newMap(),
    typedefs: newMap(),
  };
  if (lib.symbols === undefined) {
    return model;
  }
  for (const symbol of lib.symbols) {
    const fqn = symbol.name;
    if (has(jsonSymbols, fqn)) {
      error(
        `${libName}: Duplicate symbol found: ${symbol.kind} ${fqn}. First occurrence is a ${jsonSymbols[fqn].kind}.`,
        strict
      );
      continue;
    }
    jsonSymbols[fqn] = symbol;
    // For some reason we get a non-reachable case branch here on all cases except "typedef", although it's not correct
    // noinspection JSUnreachableSwitchBranches
    switch (symbol.kind) {
      case "namespace": // Fallthrough
      case "member": {
        model.namespaces[fqn] = convertNamespace(libName, symbol);
        break;
      }
      case "class": {
        model.classes[fqn] = convertClass(libName, symbol);
        break;
      }
      case "enum": {
        model.enums[fqn] = convertEnum(libName, symbol);
        break;
      }
      case "function": {
        model.functions[fqn] = convertFunction(libName, symbol);
        break;
      }
      case "interface": {
        model.interfaces[fqn] = convertInterface(libName, symbol);
        break;
      }
      case "typedef": {
        model.typedefs[fqn] = convertTypedef(libName, symbol);
        break;
      }
    }
  }
  return model;
}
Example #25
Source File: pivot-data-set.ts    From S2 with MIT License 5 votes vote down vote up
public getDimensionValues(field: string, query?: DataType): string[] {
    const { rows = [], columns = [] } = this.fields || {};
    let meta: PivotMeta = new Map();
    let dimensions: string[] = [];
    if (includes(rows, field)) {
      meta = this.rowPivotMeta;
      dimensions = rows;
    } else if (includes(columns, field)) {
      meta = this.colPivotMeta;
      dimensions = columns;
    }

    if (!isEmpty(query)) {
      let sortedMeta = [];
      const dimensionValuePath = [];
      for (const dimension of dimensions) {
        const value = get(query, dimension);
        dimensionValuePath.push(`${value}`);
        const cacheKey = dimensionValuePath.join(`${ID_SEPARATOR}`);
        if (meta.has(value) && !isUndefined(value)) {
          const childField = meta.get(value)?.childField;
          meta = meta.get(value).children;
          if (
            find(this.sortParams, (item) => item.sortFieldId === childField) &&
            this.sortedDimensionValues[childField]
          ) {
            const dimensionValues = this.sortedDimensionValues[
              childField
            ]?.filter((item) => item?.includes(cacheKey));
            sortedMeta = getDimensionsWithoutPathPre([...dimensionValues]);
          } else {
            sortedMeta = [...meta.keys()];
          }
        }
      }
      if (isEmpty(sortedMeta)) {
        return [];
      }
      return filterUndefined(getListBySorted([...meta.keys()], sortedMeta));
    }

    if (this.sortedDimensionValues[field]) {
      return filterUndefined(
        getDimensionsWithoutPathPre([...this.sortedDimensionValues[field]]),
      );
    }

    return filterUndefined([...meta.keys()]);
  }
Example #26
Source File: primary-key.ts    From dyngoose with ISC License 5 votes vote down vote up
public fromKey(hash: QueryFilters<T> | HashKeyType, range?: RangeKeyType): T {
    // if the hash was passed a query filters, then extract the hash and range
    if (isObject(hash) && !isDate(hash)) {
      const filters = hash
      if (!has(filters, this.metadata.hash.propertyName)) {
        throw new QueryError('Cannot perform .get() on a PrimaryKey without specifying a hash key value')
      } else if (this.metadata.range != null && !has(filters, this.metadata.range.propertyName)) {
        throw new QueryError('Cannot perform .get() on a PrimaryKey with a range key without specifying a range value')
      } else if (Object.keys(filters).length > 2) {
        throw new QueryError('Cannot perform a .get() on a PrimaryKey with additional filters, use .query() instead')
      }

      hash = get(filters, this.metadata.hash.propertyName)

      if (isArray(hash)) {
        if (hash[0] === '=') {
          hash = hash[1]
        } else {
          throw new QueryError('DynamoDB only supports using equal operator for the HASH key')
        }
      }

      if (this.metadata.range != null) {
        range = get(filters, this.metadata.range.propertyName)

        if (isArray(hash)) {
          if (hash[0] === '=') {
            hash = hash[1]
          } else {
            throw new QueryError('DynamoDB only supports using equal operator for the RANGE key on GetItem operations')
          }
        }
      }
    }

    if (this.metadata.range != null && range == null) {
      throw new QueryError('Cannot use primaryKey.get without a range key value')
    }

    const keyMap: DynamoDB.AttributeMap = {
      [this.metadata.hash.name]: this.metadata.hash.toDynamoAssert(hash),
    }

    if (this.metadata.range != null) {
      keyMap[this.metadata.range.name] = this.metadata.range.toDynamoAssert(range)
    }

    return this.table.fromDynamo(keyMap, false)
  }
Example #27
Source File: index.ts    From prism-frontend with MIT License 5 votes vote down vote up
safeCountry =
  COUNTRY && has(configMap, COUNTRY) ? (COUNTRY as Country) : DEFAULT
Example #28
Source File: search.ts    From dyngoose with ISC License 5 votes vote down vote up
private checkFilters(hash: Attribute<any>, range?: Attribute<any>): boolean {
    // cannot filter by a key without a value for the hash key
    for (const filters of this.filters) {
      if (!has(filters, hash.name)) {
        continue
      }

      const hashFilter: Filter<any> = get(filters, hash.name)

      // if there is an operator, ensure it is allowed as a key expression
      if (isArray(hashFilter)) {
        const operator = hashFilter[0]

        if (!includes(keyConditionAllowedOperators, operator)) {
          continue
        }
      }

      // if it has no range, then we're all done
      if (range == null) {
        return true
      }

      // check for the range now
      if (!has(filters, range.name)) {
        continue
      }

      const rangeFilter: Filter<any> = get(filters, range.name)

      // if there is an operator, ensure it is allowed as a key expression
      if (isArray(rangeFilter)) {
        const operator = rangeFilter[0]

        if (!includes(keyConditionAllowedOperators, operator)) {
          continue
        }
      }

      return true
    }

    return false
  }
Example #29
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
EditIssueDrawer = (props: IProps) => {
  const {
    id: propId,
    visible,
    closeDrawer,
    issueType: propsIssueType,
    iterationID,
    projectId,
    shareLink,
    ticketType,
    customUrl,
  } = props;
  const [issueType, setIssueType] = React.useState(propsIssueType);
  const type = issueType.toLowerCase();
  const {
    getIssueDetail,
    updateIssue,
    updateType,
    getIssueStreams,
    createIssue,
    copyIssue,
    addIssueStream,
    deleteIssue,
    getFieldsByIssue,
    addFieldsToIssue,
  } = issueStore.effects;
  const { clearIssueDetail } = issueStore.reducers;
  const [bugStageList, taskTypeList, fieldList] = issueFieldStore.useStore((s) => [
    s.bugStageList,
    s.taskTypeList,
    s.fieldList,
  ]);
  const id = propId;
  const isEditMode = !!id;
  const defaultCustomFormData = React.useMemo(() => {
    const customFieldDefaultValues = {};
    map(fieldList, (item) => {
      if (item && item.required) {
        if (item.propertyType === 'Select') {
          customFieldDefaultValues[item.propertyName] = item.enumeratedValues?.[0].id;
        }
        if (item.propertyType === 'MultiSelect') {
          customFieldDefaultValues[item.propertyName] = [item.enumeratedValues?.[0].id];
        }
      }
    });
    return customFieldDefaultValues;
  }, [fieldList]);

  const defaultFormData = React.useMemo(() => {
    return {
      priority: ISSUE_PRIORITY_MAP.NORMAL.value,
      complexity: ISSUE_COMPLEXITY_MAP.NORMAL.value,
      severity: BUG_SEVERITY_MAP.NORMAL.value,
      taskType: taskTypeList?.length ? taskTypeList[0].value : '',
      bugStage: bugStageList?.length ? bugStageList[0].value : '',
      assignee: userStore.getState((s) => s.loginUser.id),
      planFinishedAt: issueType === ISSUE_TYPE.EPIC ? new Date() : undefined,
      planStartedAt: issueType === ISSUE_TYPE.EPIC ? new Date() : undefined,
      iterationID,
      content: isEditMode ? '' : templateMap[issueType] || '',
      ...defaultCustomFormData,
    };
  }, [bugStageList, defaultCustomFormData, isEditMode, issueType, iterationID, taskTypeList]);
  const [formData, setFormData] = React.useState(defaultFormData as any);
  const drawerVisibleRef = React.useRef(visible);
  const issueDetail: ISSUE.IssueType = issueStore.useStore((s) => s[`${type}Detail`]);

  // 监听bugDetail、taskDetail、requirementDetail的变化,切换类型后触发刷新
  issueStore.useStore((s) => [s.bugDetail, s.taskDetail, s.requirementDetail]);

  const labels = labelStore.useStore((s) => s.list);
  const { getLabels } = labelStore.effects;
  const [updateIssueLoading] = useLoading(issueStore, ['updateIssue']);
  const labelNames = map(labels, ({ name }) => name);
  const [isLoading, setIsLoading] = React.useState(false);
  const [hasEdited, setHasEdited] = React.useState(false);
  const [tempDescContent, setTempDescContent] = React.useState('');
  const [disableSubmit, setDisableSubmit] = React.useState(false);
  const isBug = issueType === ISSUE_TYPE.BUG;
  const customFieldDetail = issueStore.useStore((s) => s.customFieldDetail);
  const [customFormData, setCustomFormData] = React.useState(customFieldDetail as any);
  const { getFieldsByIssue: getCustomFieldsByProject } = issueFieldStore.effects;

  React.useEffect(() => {
    if (!labels?.length) {
      getLabels({ type: 'issue' });
    }
  }, [labels, getLabels]);

  const savingRef = React.useRef(false);
  const isBacklog = iterationID === -1;
  const isMonitorTicket = ticketType === 'monitor';

  const { creator, assignee, testPlanCaseRels } = issueDetail || {};
  const specialProps = EDIT_PROPS[issueType];
  const projectPerm = usePerm((s) => s.project);
  const permObjMap = {
    [ISSUE_TYPE.REQUIREMENT]: projectPerm.requirement,
    [ISSUE_TYPE.TASK]: projectPerm.task,
    [ISSUE_TYPE.BUG]: projectPerm.bug,
    [ISSUE_TYPE.TICKET]: projectPerm.ticket,
    [ISSUE_TYPE.EPIC]: projectPerm.epic,
  };
  const permObj = permObjMap[issueType];
  const checkRole = [isCreator(creator), isAssignee(assignee)];
  const deleteAuth = isMonitorTicket ? true : getAuth(permObj.delete, checkRole);
  const createAuth = permObj.create.pass;
  const editAuth = isMonitorTicket ? true : !isEditMode || getAuth(permObj.edit, checkRole);
  const switchTypeAuth = getAuth(permObj.switchType, checkRole);

  const addRelatedMattersProjectId = routeInfoStore.getState((s) => s.params).projectId;
  const { addIssueRelation } = issueStore.effects;
  const { updateCustomFieldDetail } = issueStore.reducers;
  const { id: orgID } = orgStore.useStore((s) => s.currentOrg);
  const metaFieldsRef: React.RefObject<unknown> = React.useRef(null);
  const [tempStateData, setTempStateData] = React.useState('');

  React.useEffect(() => {
    setFormData((prev: any) => ({ ...prev, iterationID }));
  }, [iterationID]);

  const getCustomFields = React.useCallback(() => {
    id && getFieldsByIssue({ issueID: id, propertyIssueType: issueType, orgID });
  }, [getFieldsByIssue, id, issueType, orgID]);

  React.useEffect(() => {
    setIssueType(propsIssueType);
  }, [propsIssueType]);

  React.useEffect(() => {
    drawerVisibleRef.current = visible;
  }, [visible]);

  React.useEffect(() => {
    if (visible) {
      if (id) {
        getIssueStreams({ type: issueType, id, pageNo: 1, pageSize: 100 });
        getCustomFields();
      }
      getCustomFieldsByProject({
        propertyIssueType: issueType,
        orgID,
      }).then((res) => {
        updateCustomFieldDetail({
          property: res,
          orgID,
          projectID: +addRelatedMattersProjectId,
          issueID: undefined,
        });
      });
    }
  }, [
    addRelatedMattersProjectId,
    getCustomFields,
    getCustomFieldsByProject,
    getFieldsByIssue,
    getIssueDetail,
    getIssueStreams,
    id,
    issueType,
    orgID,
    updateCustomFieldDetail,
    visible,
  ]);

  const customFieldValues = React.useMemo(() => {
    customFieldDetail && setCustomFormData(customFieldDetail);
    const tempFormData = {};
    map(customFieldDetail?.property, (item) => {
      const { arbitraryValue, propertyType, values, propertyName } = item;
      const _values = values || [];
      tempFormData[propertyName] = FIELD_WITH_OPTION[propertyType]
        ? propertyType === 'MultiSelect'
          ? _values
          : _values[0]
        : arbitraryValue;
    });
    return tempFormData;
  }, [customFieldDetail]);

  React.useEffect(() => {
    issueDetail && setFormData({ ...issueDetail, ...customFieldValues });
  }, [customFieldValues, issueDetail]);

  const dataCheck = (_data: Obj) => {
    if (ISSUE_TYPE.TASK === issueType) {
      // 创建时任务必填预估工时, 任务类型
      if (!isEditMode && !_data.taskType && issueType === ISSUE_TYPE.TASK) {
        message.warn(i18n.t('Task type'));
        return false;
      }
      // if (!isEditMode && !_data.issueManHour?.estimateTime) {
      //   message.warn(i18n.t('dop:missing estimateTime'));
      //   return false;
      // }
    }
    if (!_data.title) {
      message.warn(i18n.t('dop:missing title'));
      return false;
    }
    if (!_data.assignee) {
      message.warn(i18n.t('dop:missing assignee'));
      return false;
    }

    // if (!_data.iterationID) {
    //   message.warn(i18n.t('please choose the {name}', { name: i18n.t('dop:Iteration-owned') }));
    //   return false;
    // }

    if (ISSUE_TYPE.BUG === issueType) {
      if (!_data.bugStage) {
        message.warn(i18n.t('dop:missing import source'));
        return false;
      }
    }
    if (!_data.planFinishedAt && ISSUE_TYPE.EPIC === issueType) {
      message.warn(i18n.t('dop:missing deadline'));
      return false;
    }

    if (!_data.planStartedAt && ISSUE_TYPE.EPIC === issueType) {
      message.warn(i18n.t('dop:missing startTime'));
      return false;
    }

    return true;
  };

  const checkFieldNotEmpty = (propertyType: ISSUE_FIELD.IPropertyType, propertyValue: any) => {
    if (propertyType === 'MultiSelect') {
      return !isEmpty(propertyValue);
    } else if (propertyType === 'Number') {
      return propertyValue || String(propertyValue) === '0';
    } else {
      return propertyValue;
    }
  };

  const checkCustomFormData = (filterKey?: string) => {
    if (customFormData?.property) {
      const tempList = customFormData.property;
      for (let i = 0, len = tempList.length; i < len; i++) {
        const { displayName, required, arbitraryValue, propertyType, values, propertyName } = tempList[i];
        const _values = values || [];

        let propertyValue = [];
        if (propertyType === 'MultiSelect') {
          propertyValue = _values;
        } else if (propertyType === 'Select') {
          propertyValue = _values.length ? [_values[0]] : [];
        } else {
          propertyValue = arbitraryValue || String(arbitraryValue) === '0' ? [arbitraryValue] : [];
        }

        if (isEmpty(propertyValue) && required && (!filterKey || filterKey !== propertyName)) {
          message.warn(i18n.t('missing {name}', { name: displayName }));
          return false;
        }
      }
    }
    return true;
  };

  const focusOnFields = (fieldKey: string) => {
    metaFieldsRef?.current?.onFocus(fieldKey);
  };

  const setField = (value: Obj<any>) => {
    const formattedValue = value;

    // 处理 issueManHour
    if (has(value, 'issueManHour')) {
      formattedValue.issueManHour = merge({}, formData.issueManHour, value.issueManHour);
    }

    const params: ISSUE.IssueType = merge({}, formData, formattedValue);

    if (value.labels) {
      params.labels = value.labels;
    }
    if (has(value, 'planFinishedAt') && !value.planFinishedAt) {
      params.planFinishedAt = ''; // replace null to mark delete
    }
    if (has(value, 'planStartedAt') && !value.planStartedAt) {
      params.planStartedAt = '';
    }

    if ([ISSUE_TYPE.TASK, ISSUE_TYPE.BUG].includes(issueType)) {
      const warnMessage = [];
      if (value.state && isEditMode) {
        setTempStateData(value.state);
        // 编辑模式下修改状态时,必填时间追踪和预估工时, 任务类型
        if (!params.taskType && issueType === ISSUE_TYPE.TASK) {
          warnMessage.push({ msg: i18n.t('dop:missing task type'), key: 'taskType' });
        }
        if (!params.issueManHour.estimateTime) {
          warnMessage.push({ msg: i18n.t('dop:Estimated time'), key: 'issueManHour.estimateTime' });
        }
        if (params.issueManHour.elapsedTime === 0 && params.issueManHour.thisElapsedTime === 0) {
          // filter out the working
          const workingState = formData.issueButton.find((item) => item.stateBelong === 'WORKING');
          // When working exists and select working, don't warn
          if (!workingState || value.state !== workingState.stateID) {
            warnMessage.push({ msg: i18n.t('dop:time spent in time tracing'), key: 'issueManHour.elapsedTime' });
          }
        }
      }
      if (warnMessage.length !== 0) {
        message.warn(
          <>
            <span className="font-bold">{map(warnMessage, 'msg').join(', ')}</span>
            <span>{i18n.t('dop:missing')}</span>
          </>,
        );
        focusOnFields(warnMessage[0].key);
        return false;
      }
    }
    // after validation, then set temp state in data. prevent enter line 857. see erda bug #235076
    if (has(value, 'issueManHour') && tempStateData) {
      formattedValue.state = tempStateData;
      setTempStateData('');
    }

    let promise;
    let customFieldKey = '';
    let customFieldValue: any;
    map(Object.keys(value), (k) => {
      customFieldKey = k;
      if (!(['planFinishedAt', 'planStartedAt'].includes(k) && !value[k])) {
        params[k] = value[k];
      }
      customFieldValue = value[k];
    });
    const customFieldData = find(customFormData?.property, (item) => item.propertyName === customFieldKey);
    const tempCustomFormData: ISSUE.ICreateField = produce(customFormData, (draft: any) => {
      map(draft?.property, (draftData) => {
        if (draftData.propertyName === customFieldKey) {
          if (FIELD_WITH_OPTION[draftData?.propertyType]) {
            const _values = customFieldValue || [];
            // eslint-disable-next-line no-param-reassign
            draftData.values = Array.isArray(_values) ? _values : [_values];
          } else if (draftData?.propertyType === 'Number') {
            // eslint-disable-next-line no-param-reassign
            draftData.arbitraryValue = Number(customFieldValue);
          } else {
            // eslint-disable-next-line no-param-reassign
            draftData.arbitraryValue = customFieldValue;
          }
        }
      });
    });
    setCustomFormData(tempCustomFormData);

    if (isEditMode) {
      setHasEdited(true);
      if (dataCheck({ ...params, customUrl })) {
        params.iterationID = +(params.iterationID as number) || -1;
        if (tempDescContent) {
          params.content = tempDescContent;
        }
        if (!customFieldValues?.hasOwnProperty(customFieldKey)) {
          savingRef.current = true;
          promise = updateIssue({ ...params, customUrl })
            .then(() => {
              getIssueStreams({ type: issueType, id: id as number, pageNo: 1, pageSize: 100 });
              getIssueDetail({ id: id as number }).then(() => {
                savingRef.current = false;
              });
              // setHasEdited(false); // 更新后置为false
              return true;
            })
            .catch(() => {
              savingRef.current = false;
              return false;
            });
        } else {
          addFieldsToIssue(
            { ...tempCustomFormData, orgID, projectID: +addRelatedMattersProjectId },
            { customMsg: i18n.t('updated successfully') },
          ).then(() => {
            getCustomFields();
          });
        }
      }
      if (!checkFieldNotEmpty(customFieldData?.propertyType, customFieldValue) && customFieldData?.required) {
        const name = customFieldData?.displayName;
        message.warn(i18n.t('missing {name}', { name }));

        focusOnFields(name);
        return;
      }

      if (!checkCustomFormData(customFieldKey)) return;
    }
    setFormData(params);

    return promise;
  };

  const setFieldCb = (value: Obj<any>, fieldType?: string) => {
    if (!drawerVisibleRef.current) {
      return;
    }

    if (fieldType && fieldType === 'markdown') {
      setTempDescContent(value?.content);
      return;
    }
    if (value.labels) {
      const labelName = cloneDeep(value.labels).pop();
      if (isEmpty(value.labels) || includes(labelNames, labelName)) {
        return setField(value);
      } else {
        message.info(i18n.t('dop:the label does not exist, please select again'));
        return setField({ ...value, labels: value.labels.slice(0, -1) }); // remove the last label, which is not exist
      }
    } else {
      return setField(value);
    }
  };

  const onClose = (isCreate = false, isDelete = false) => {
    if (savingRef.current) return;
    drawerVisibleRef.current = false;
    setFormData(defaultFormData);
    closeDrawer({ hasEdited, isCreate, isDelete });
    setTempDescContent('');
    setIssueType(propsIssueType);
    setHasEdited(false);
    setIsLoading(false);
    updateSearch({
      id: undefined,
    });
    setCustomFormData(customFieldDetail);
    isEditMode && issueType && clearIssueDetail(issueType);
  };

  const onDelete = () => {
    id &&
      deleteIssue(id).then(() => {
        onClose(false, true);
      });
  };

  const handleSubmit = (isCopy = false, copyTitle = '') => {
    if (!dataCheck(formData)) return;
    if (!checkCustomFormData()) return;
    setDisableSubmit(true);
    const params: ISSUE.IssueType = {
      projectID: Number(projectId),
      iterationID, // keep it first, allow form overwrite iterationID
      ...formData,
      type: issueType,
    };
    params.iterationID = +(params.iterationID as number) || -1; // 需求池指定迭代为-1
    isBug && !(params as ISSUE.Bug).owner && ((params as ISSUE.Bug).owner = params.assignee); // 责任人暂时默认设为处理人

    if (isCopy) {
      const { creator, ...restFormData } = formData;
      copyIssue({
        ...restFormData,
        title: copyTitle,
        issueManHour: { ...formData.issueManHour, elapsedTime: undefined },
        customUrl,
      })
        .then((res) => {
          addFieldsToIssue(
            { ...customFormData, issueID: res, projectID: params.projectID },
            { customMsg: i18n.t('copied successfully') },
          );
          onClose(true);
        })
        .finally(() => {
          setDisableSubmit(false);
        });
      return;
    }

    if (id) {
      updateIssue({
        ...formData,
        issueManHour: { ...formData.issueManHour, elapsedTime: undefined },
        customUrl,
      }).then(() => {
        onClose();
      });
    } else {
      createIssue({ ...params, customUrl }, { hideActionMsg: true })
        .then((res) => {
          savingRef.current = false;
          addFieldsToIssue(
            { ...customFormData, issueID: res, projectID: params.projectID },
            { customMsg: i18n.t('created successfully') },
          );
          onClose(true);
        })
        .finally(() => {
          setDisableSubmit(false);
        });
    }
  };

  const switchType = (currentType: string) => {
    setIsLoading(true);
    updateType({ id: formData.id, type: currentType }).then(() => {
      setHasEdited(true);
      setIssueType(currentType as ISSUE_TYPE);
      setIsLoading(false);
    });
  };

  const handleMenuClick = ({ key }: { key: string }) => {
    iterationStore.effects
      .createIssue({
        // 创建事件
        projectID: +addRelatedMattersProjectId,
        iterationID: -1,
        type: key,
        priority: 'NORMAL',
        title: issueDetail.title,
        content: issueDetail.content,
      })
      .then((res: number) => {
        addRelation(res); // 添加关联
      });
  };

  const addRelation = (val: number) => {
    addIssueRelation({
      relatedIssues: [val],
      id: issueDetail.id,
      projectId: +addRelatedMattersProjectId,
      type: 'connection',
    }).then(() => {
      setHasEdited(true);
      getIssueRelation.fetch({
        issueId: issueDetail.id,
      });
    });
  };

  const addQuickIssueAuth = usePerm((s) => s.project.requirement.create.pass); // 目前迭代、任务、缺陷添加权限都一致

  let extraHeaderOp: JSX.Element | React.ElementType | null = null;
  if (isEditMode && issueType === ISSUE_TYPE.TICKET) {
    if (addQuickIssueAuth) {
      extraHeaderOp = (
        <Dropdown
          key="quick-add"
          overlay={
            <Menu onClick={handleMenuClick}>
              <Menu.Item key="REQUIREMENT">
                <IssueIcon type="REQUIREMENT" withName />
              </Menu.Item>
              <Menu.Item key="TASK">
                <IssueIcon type="TASK" withName />
              </Menu.Item>
              <Menu.Item key="BUG">
                <IssueIcon type="BUG" withName />
              </Menu.Item>
            </Menu>
          }
        >
          <Button size="small" className="mr-2 shadow-none">
            {i18n.t('dop:One Click to Backlog')}
          </Button>
        </Dropdown>
      );
    } else {
      extraHeaderOp = (
        <WithAuth key="create" pass={addQuickIssueAuth}>
          <Button size="small" className="mr-2 shadow-none">
            {i18n.t('dop:One Click to Backlog')}
          </Button>
        </WithAuth>
      );
    }
  }

  let footer;
  if (!isEditMode) {
    footer = (isChanged: boolean, confirmCloseTip: string | undefined) => (
      <Spin key="submit" spinning={updateIssueLoading}>
        <div className="pl-8 py-2 space-x-2 border-solid border-transparent border-top">
          <Button disabled={disableSubmit} onClick={() => handleSubmit()} type="primary">
            {i18n.t('OK')}
          </Button>

          {isChanged && confirmCloseTip ? (
            <Popconfirm title={confirmCloseTip} placement="topLeft" onConfirm={() => onClose()}>
              <Button>{i18n.t('Cancel')}</Button>
            </Popconfirm>
          ) : (
            <Button onClick={() => onClose()}>{i18n.t('Cancel')}</Button>
          )}
        </div>
      </Spin>
    );
  } else {
    footer = issueDetail ? (
      <IssueCommentBox
        onSave={(content) => {
          addIssueStream(issueDetail, { content });
          emit('issue:scrollToLatestComment');
        }}
        editAuth={editAuth}
      />
    ) : null;
  }

  const relationData = getIssueRelation.useData();

  return (
    <IssueDrawer
      editMode={isEditMode}
      visible={visible}
      loading={isLoading || updateIssueLoading}
      onClose={() => onClose()}
      onDelete={isEditMode ? onDelete : undefined}
      shareLink={shareLink}
      canDelete={deleteAuth && !isMonitorTicket}
      canCreate={createAuth}
      confirmCloseTip={isEditMode ? undefined : i18n.t('dop:The new data will be lost if closed. Continue?')}
      handleCopy={handleSubmit}
      maskClosable={isEditMode}
      data={formData}
      projectId={projectId}
      issueType={issueType}
      setData={setFormData}
      footer={footer}
      header={(scrollY) => (
        <div className="flex items-center">
          <IF check={isEditMode}>
            {[ISSUE_TYPE.REQUIREMENT, ISSUE_TYPE.TASK, ISSUE_TYPE.BUG].includes(issueType) ? (
              <WithAuth pass={switchTypeAuth}>
                <EditField
                  name="type"
                  type="select"
                  data={formData}
                  itemProps={{
                    options: getIssueTypeOption(),
                    optionLabelProp: 'data-icon',
                    dropdownMatchSelectWidth: false,
                    allowClear: false,
                    showArrow: true,
                    size: 'small',
                    className: 'switch-type-selector bg-default-06',
                    style: { width: 60 },
                    getPopupContainer: () => document.body,
                  }}
                  onChangeCb={(field: any) => {
                    if (field.type !== issueType) {
                      switchType(field.type);
                    }
                  }}
                />
              </WithAuth>
            ) : (
              <span className="mr-2 flex items-center h-full">{ISSUE_TYPE_MAP[issueType]?.icon}</span>
            )}
            <IF.ELSE />
            <IssueIcon type={issueType} withName />
          </IF>
          <div className={`ml-2 issue-header-title-wrap ${scrollY > 20 ? 'slide-up' : ''}`}>
            <div className="issue-header-title">
              <div className="flex items-center h-7">
                {relationData?.beIncluded?.[0] && (
                  <>
                    <ErdaIcon className="mx-2 text-sub" type="right" size="16px" />
                    <a
                      href={`${location.href.split('?')[0]}?${mergeSearch(
                        {
                          id: relationData.beIncluded[0].id,
                          type: relationData.beIncluded[0].type,
                          tab: 'ALL',
                        },
                        true,
                      )}`}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="inline-flex items-center bg-default-06 rounded-sm px-2 hover:text-purple-deep overflow-hidden cursor-pointer"
                      style={{ maxWidth: '320px' }}
                    >
                      <ErdaIcon className="mr-1" type="xuqiu" size="20px" />
                      <span className="flex-1 truncate">{relationData?.beIncluded?.[0].title}</span>
                    </a>
                  </>
                )}
              </div>
              <div className="truncate text-base font-medium leading-7" style={{ maxWidth: '480px' }}>
                {issueDetail?.title}
              </div>
            </div>
          </div>
        </div>
      )}
      extraHeaderOp={extraHeaderOp}
    >
      <div className="mt-1">
        <EditField
          name="title"
          onChangeCb={setFieldCb}
          data={formData}
          disabled={!editAuth}
          className="flex-1 mt-2 ml-[-8px] mr-[-8px]"
          itemProps={{
            autoFocus: !isEditMode,
            className: 'text-xl text-normal px-2 font-medium',
            maxLength: 255,
            placeholder: firstCharToUpper(specialProps.titlePlaceHolder),
          }}
        />
        <IssueMetaFields
          ref={metaFieldsRef}
          projectId={projectId}
          labels={labels}
          isEditMode={isEditMode}
          issueType={issueType}
          isBacklog={isBacklog}
          ticketType={ticketType}
          editAuth={editAuth}
          formData={formData}
          setFieldCb={setFieldCb}
        />
      </div>
      <EditField
        name="content"
        disabled={!editAuth}
        type="markdown"
        onChangeCb={setFieldCb}
        itemProps={{
          placeHolder: i18n.t('dop:No content'),
          className: 'w-full',
          hasEdited,
          isEditMode,
          maxLength: 3000,
          defaultMode: isEditMode ? 'html' : 'md',
        }} // 编辑时默认显示预览
        data={formData}
      />
      <Choose>
        <When condition={[ISSUE_TYPE.TASK].includes(issueType) && isEditMode}>
          <IssueWorkflow projectID={+addRelatedMattersProjectId} id={propId!} type={issueType} metaIssue={formData} />
        </When>
        <Otherwise>{IssueDrawer.Empty}</Otherwise>
      </Choose>
      <Choose>
        <When condition={isEditMode && !!issueDetail && issueType === ISSUE_TYPE.REQUIREMENT}>
          <IssueInclusion issueDetail={issueDetail} iterationID={iterationID} setHasEdited={setHasEdited} />
        </When>
        <Otherwise>{IssueDrawer.Empty}</Otherwise>
      </Choose>
      <Choose>
        <When condition={isEditMode && !!issueDetail}>
          <IssueConnection
            editAuth={editAuth}
            issueDetail={issueDetail}
            iterationID={iterationID}
            setHasEdited={setHasEdited}
          />
        </When>
        <Otherwise>{IssueDrawer.Empty}</Otherwise>
      </Choose>
      <Choose>
        <When condition={isEditMode && !!issueDetail}>
          <IssueActivities type={issueType} />
        </When>
        <Otherwise>{IssueDrawer.Empty}</Otherwise>
      </Choose>
    </IssueDrawer>
  );
}