lodash#remove TypeScript Examples

The following examples show how to use lodash#remove. 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: i18n-utils.ts    From erda-ui with GNU Affero General Public License v3.0 7 votes vote down vote up
filterTranslationGroup = (
  toTranslateEnWords: string[],
  zhResource: Obj<Obj>,
  untranslatedWords: Set<string>,
  translatedWords: Obj,
) => {
  const notTranslatedWords = [...toTranslateEnWords]; // The English collection of the current document that needs to be translated
  // Traverse namespaces of zh.json to see if there is any English that has been translated
  Object.keys(zhResource).forEach((namespaceKey) => {
    // All translations in the current namespace
    const namespaceWords = zhResource[namespaceKey];
    toTranslateEnWords.forEach((enWord) => {
      const convertedEnWord = enWord.replace(/:/g, '&#58;');
      // When there is an existing translation and translatedWords does not contains it, add it to the translated list and remove it from the untranslated list
      if (namespaceWords[convertedEnWord] && !translatedWords[convertedEnWord]) {
        // eslint-disable-next-line no-param-reassign
        translatedWords[convertedEnWord] =
          namespaceKey === 'default'
            ? namespaceWords[convertedEnWord]
            : `${namespaceKey}:${namespaceWords[convertedEnWord]}`;
        remove(notTranslatedWords, (w) => w === enWord);
      }
    });
  });
  notTranslatedWords.forEach(untranslatedWords.add, untranslatedWords);
}
Example #2
Source File: fix-api-json.ts    From ui5-language-assistant with Apache License 2.0 6 votes vote down vote up
export function fixSelfImplementingClass(
  libraryName: string,
  content: unknown
): void {
  // This fixes class "sap.ui.vk.BaseNodeProxy" which has itself in its "implements" array
  forEach(get(content, "symbols"), (symbol) => {
    if (get(symbol, "kind") === "class") {
      const symbolName = get(symbol, "name");
      remove(symbol.implements, (name) => name === symbolName);
    }
  });
}
Example #3
Source File: entitiesStore.ts    From jitsu with MIT License 6 votes vote down vote up
public *delete(id: string) {
    this.resetError()
    this.setStatus(BACKGROUND_LOADING)
    try {
      yield services.storageService.table<T>(this.type).delete(id)
      remove(this._entities, entity => this.getId(entity) === id)
    } finally {
      this.setStatus(IDLE)
    }
  }
Example #4
Source File: team.ts    From fishbowl with MIT License 6 votes vote down vote up
export function teamsWithSequenceWithUpdate(
  players: Players,
  updatedPlayer: Player
) {
  remove(players, (player) => player.id === updatedPlayer.id)
  players.push(updatedPlayer)
  const redTeam = addTeamAndSequence(
    filter(players, (player) => player.team === Team.Red),
    Team.Red
  )
  const blueTeam = addTeamAndSequence(
    filter(players, (player) => player.team === Team.Blue),
    Team.Blue
  )
  return redTeam.concat(blueTeam)
}
Example #5
Source File: pack-external-module.ts    From malagu with MIT License 6 votes vote down vote up
/**
 * Remove a given list of excluded modules from a module list
 * @this - The active plugin instance
 */
function removeExcludedModules(modules: string[], packageForceExcludes: string[], log: boolean): void {
    const excludedModules = remove(modules, externalModule => {
        const splitModule = externalModule.split('@');
        // If we have a scoped module we have to re-add the @
        if (externalModule.startsWith('@')) {
            splitModule.splice(0, 1);
            splitModule[0] = '@' + splitModule[0];
        }
        const moduleName = splitModule[0];
        return packageForceExcludes.indexOf((moduleName)) !== -1;
    });

    if (log && excludedModules.length > 0) {
        console.log(`Excluding external modules: ${excludedModules.join(', ')}`);
    }
}
Example #6
Source File: pipeline-yml-data-convert.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
pushTask = (options: IOption) => {
  const { task, taskGroup, item, actions, editConvertor, pipelineTaskAlias } = options;
  const otherTaskAlias = remove([...pipelineTaskAlias], (alias) => alias !== task.alias);
  const propertyViewDataSource = omit(task, ['alias', 'type', 'resources']);
  taskGroup.push({
    ...item,
    lineTo: ['all'],
    icon: 'wfw',
    editView: (editing: boolean) => {
      return (
        <EditStage
          editing={editing}
          actions={actions}
          task={task}
          otherTaskAlias={otherTaskAlias}
          onSubmit={editConvertor}
        />
      );
    },
    title: task.type,
    data: task,
    name: task.alias,
    content: () => {
      return <PropertyView dataSource={propertyViewDataSource} />;
    },
  });
}
Example #7
Source File: gamelogic.ts    From sc2-planner with MIT License 5 votes vote down vote up
static addItemToBO(
        prevGamelogic: GameLogic,
        item: IBuildOrderElement,
        insertIndex: number
    ): [GameLogic, number] {
        const bo = prevGamelogic.bo
        const initialBOLength = bo.length

        if (item.type === "upgrade" && prevGamelogic.upgrades.has(item.name)) {
            // upgrade already researched, don't do anything.
            return [prevGamelogic, insertIndex]
        }

        bo.splice(insertIndex, 0, item)
        // Re-calculate build order

        // // Caching using snapshots - idk why this isnt working properly
        // const latestSnapshot = gamelogic.getLastSnapshot()
        // if (latestSnapshot) {
        //     gamelogic.loadFromSnapshotObject(latestSnapshot)
        // }
        // gamelogic.bo = cloneDeep(bo)
        // gamelogic.runUntilEnd()

        // Non cached:
        // Fill up with missing items
        let gamelogic = GameLogic.simulatedBuildOrder(prevGamelogic, bo)
        let fillingLoop = 0
        // Add required items if need be
        if (insertIndex === bo.length - 1 && !prevGamelogic.errorMessage) {
            do {
                if (fillingLoop > 0) {
                    // Simulation is done already, the first time
                    gamelogic = GameLogic.simulatedBuildOrder(prevGamelogic, bo)
                }
                if (gamelogic.errorMessage && gamelogic.requirements) {
                    fillingLoop++
                    const duplicatesToRemove: IBuildOrderElement[] = []
                    for (const req of gamelogic.requirements) {
                        let duplicateItem: IBuildOrderElement | undefined
                        if (!gamelogic.canRequirementBeDuplicated(req.name, item.name)) {
                            duplicateItem = find(bo, req)
                        }
                        // Add item if absent, or present later in the bo
                        if (!duplicateItem || bo.indexOf(duplicateItem) >= insertIndex) {
                            bo.splice(insertIndex, 0, req)
                            if (duplicateItem) {
                                duplicatesToRemove.push(duplicateItem)
                            }
                        }
                    }
                    for (const duplicate of duplicatesToRemove) {
                        remove(bo, (item) => item === duplicate) // Specificaly remove the later one
                    }
                }
            } while (gamelogic.errorMessage && gamelogic.requirements && fillingLoop < 25)
        }
        const insertedItems = bo.length - initialBOLength
        return [gamelogic, insertedItems]
    }
Example #8
Source File: explore.ts    From nebula-studio with Apache License 2.0 5 votes vote down vote up
updateTagMap = (tagMap, vertexes) => {
  Object.keys(tagMap).forEach(tag => {
    const colorGroup = tagMap[tag];
    colorGroup.forEach(colorMap => {
      colorMap.countIds = [];
    });
  });
  vertexes.forEach(vertex => {
    const { color, tags = [], id } = vertex;
    const group = tags.sort().join('-');
    const colorMap = tagMap[group];
    if (colorMap) {
      const hasColor = colorMap.some(item => {
        if (item.color === color && !item.countIds.includes(id)) {
          item.countIds.push(String(id));
          return true;
        }
      });
      if (!hasColor) {
        colorMap.push({
          color,
          countIds: [String(id)]
        });
      }
    } else {
      tagMap[group] = [{
        color,
        countIds: [String(id)]
      }];
    }
  });
  // remove color without data, but need to remain one
  Object.keys(tagMap).forEach(tag => {
    const colorGroup = tagMap[tag];
    const noDataList = colorGroup.filter(item => item.countIds.length === 0).map(item => item.color);
    const removeList = colorGroup.length === noDataList.length ? noDataList.slice(1) : noDataList;
    remove(tagMap[tag], (item: any) => removeList.includes(item.color));
  });
  return { ...tagMap };
}
Example #9
Source File: index.ts    From TidGi-Desktop with Mozilla Public License 2.0 5 votes vote down vote up
/**
   * Insert provided sub menu items into menubar, so user and services can register custom menu items
   * @param menuID Top level menu name to insert menu items
   * @param newSubMenuItems An array of menu item to insert or update, if some of item is already existed, it will be updated instead of inserted
   * @param afterSubMenu The `id` or `role` of a submenu you want your submenu insert after. `null` means inserted as first submenu item; `undefined` means inserted as last submenu item;
   * @param withSeparator Need to insert a separator first, before insert menu items
   * @param menuPartKey When you update a part of menu, you can overwrite old menu part with same key
   */
  public async insertMenu(
    menuID: string,
    newSubMenuItems: Array<DeferredMenuItemConstructorOptions | MenuItemConstructorOptions>,
    afterSubMenu?: string | null,
    withSeparator = false,
    menuPartKey?: string,
  ): Promise<void> {
    let foundMenuName = false;
    const copyOfNewSubMenuItems = [...newSubMenuItems];
    // try insert menu into an existed menu's submenu
    for (const menu of this.menuTemplate) {
      // match top level menu
      if (menu.id === menuID) {
        foundMenuName = true;
        // heck some menu item existed, we update them and pop them out
        const currentSubMenu = compact(menu.submenu);
        // we push old and new content into this array, and assign back to menu.submenu later
        let filteredSubMenu: Array<DeferredMenuItemConstructorOptions | MenuItemConstructorOptions> = currentSubMenu;
        // refresh menu part by delete previous menuItems that belongs to the same partKey
        if (menuPartKey !== undefined) {
          filteredSubMenu = filteredSubMenu.filter((currentSubMenuItem) => !this.belongsToPart(menuPartKey, menuID, currentSubMenuItem.id));
        }
        for (const newSubMenuItem of newSubMenuItems) {
          const existedItemIndex = currentSubMenu.findIndex((existedItem) => MenuService.isMenuItemEqual(existedItem, newSubMenuItem));
          // replace existed item, and remove it from needed-to-add-items
          if (existedItemIndex !== -1) {
            filteredSubMenu[existedItemIndex] = newSubMenuItem;
            remove(newSubMenuItems, (item) => item.id === newSubMenuItem.id);
          }
        }

        if (afterSubMenu === undefined) {
          // inserted as last submenu item
          if (withSeparator) {
            filteredSubMenu.push({ type: 'separator' });
          }
          filteredSubMenu = [...filteredSubMenu, ...newSubMenuItems];
        } else if (afterSubMenu === null) {
          // inserted as first submenu item
          if (withSeparator) {
            newSubMenuItems.push({ type: 'separator' });
          }
          filteredSubMenu = [...newSubMenuItems, ...filteredSubMenu];
        } else if (typeof afterSubMenu === 'string') {
          // insert after afterSubMenu
          const afterSubMenuIndex = filteredSubMenu.findIndex((item) => item.id === afterSubMenu || item.role === afterSubMenu);
          if (afterSubMenuIndex === -1) {
            throw new InsertMenuAfterSubMenuIndexError(afterSubMenu, menuID, menu);
          }
          filteredSubMenu = [...take(filteredSubMenu, afterSubMenuIndex + 1), ...newSubMenuItems, ...drop(filteredSubMenu, afterSubMenuIndex - 1)];
        }
        menu.submenu = filteredSubMenu;
        // leave this finding menu loop
        break;
      }
    }
    // if user wants to create a new menu in menubar
    if (!foundMenuName) {
      this.menuTemplate.push({
        label: menuID,
        submenu: newSubMenuItems,
      });
    }
    // update menuPartRecord
    if (menuPartKey !== undefined) {
      this.updateMenuPartRecord(menuPartKey, menuID, copyOfNewSubMenuItems);
    }
    await this.buildMenu();
  }
Example #10
Source File: test-pie-chart.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
TestPieChart = ({ data }: IProps): JSX.Element => {
  const { result = [] } = data;
  remove(result, { value: 0 });
  const colorsMap = {
    failed: '#DF3409',
    error: '#e63871',
    skipped: '#FEAB00',
    passed: '#2DC083',
  };
  const useColor = result.map((item) => colorsMap[item.type]);
  const getOption = () => {
    const option = {
      tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>{b}: {c} ({d}%)',
      },
      legend: {
        orient: 'vertical',
        left: '0',
        bottom: '0',
        data: ['failed', 'error', 'skipped', 'passed'],
      },
      calculable: true,
      series: [
        {
          name: i18n.t('Status'),
          type: 'pie',
          radius: '60%',
          center: ['50%', '50%'],
          label: {
            normal: { formatter: '{b}:{c}' },
            emphasis: {
              show: true,
              textStyle: {
                fontSize: '16',
                fontWeight: 'bold',
              },
            },
            itemStyle: {
              emphasis: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)',
              },
            },
          },
          data: result,
        },
      ],
      color: useColor,
    };
    return option;
  };
  return <ChartRender data={data} hasData={size(result) > 0} getOption={getOption} />;
}
Example #11
Source File: multi-input.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
MultiInput = (props: any) => {
  const { value, placeholder } = props;
  const [renderData, setValue] = React.useState([] as Array<string | undefined>);

  React.useEffect(() => {
    const curVal = isEmpty(value) ? [undefined] : value;
    setValue(curVal);
  }, [value]);

  const addOne = () => {
    const lastItem = renderData[renderData.length - 1];
    if (!isEmpty(lastItem)) {
      setValue([...renderData, undefined]);
      props.onChange([...renderData, undefined]);
    }
  };

  const dropOne = (index: number) => {
    const valArr = [...renderData];
    remove(valArr, (_v, idx) => idx === index);
    setValue(valArr);
    props.onChange(valArr);
  };

  const changeItemValue = (val: string, index: number) => {
    const valArr = [...renderData];
    set(valArr, `[${index}]`, val);
    setValue(valArr);
    props.onChange(valArr);
  };

  return (
    <div className="w-full">
      {map(renderData, (item: any, index: number) => {
        return (
          <div className="flex justify-between items-center multi-input-item" key={index}>
            <Input
              className="multi-input-input flex-1"
              value={item}
              onChange={(e: any) => changeItemValue(e.target.value, index)}
              placeholder={placeholder || i18n.t('please enter')}
            />
            <div className="multi-input-icons">
              <ErdaIcon size="20" type="add-one" className="input-with-icon plus-circle" onClick={() => addOne()} />
              {index !== 0 ? (
                <ErdaIcon
                  type="reduce-one"
                  size="20"
                  className="input-with-icon minus-circle"
                  onClick={() => {
                    dropOne(index);
                  }}
                />
              ) : null}
            </div>
          </div>
        );
      })}
    </div>
  );
}
Example #12
Source File: semantic-model-provider.ts    From ui5-language-assistant with Apache License 2.0 5 votes vote down vote up
libraryFixes: Record<TestModelVersion, Record<string, LibraryFix[]>> = {
  "1.60.14": {
    "sap.ushell": [
      (content: Json): void => {
        const symbol = find(
          get(content, "symbols"),
          (symbol) => symbol.name === "sap.ushell.services.EndUserFeedback"
        );
        const method = find(
          get(symbol, "methods"),
          (method) => method.name === "getLegalText"
        );
        remove(method.parameters, (parameter) => get(parameter, "name") === "");
      },
    ],
  },
  "1.71.14": {},
  "1.74.0": {
    "sap.ui.generic.app": [
      (content: Json): void => {
        // Removing from this library. There is another symbol with the same name in library "sap.fe".
        remove(
          get(content, "symbols"),
          (symbol) =>
            get(symbol, "name") ===
            "sap.ui.generic.app.navigation.service.NavigationHandler"
        );
      },
    ],
    "sap.ushell": [
      (content: Json): void => {
        const symbols = get(content, "symbols");
        remove(symbols, (symbol) => get(symbol, "basename") === "");
      },
    ],
  },
  "1.75.0": {
    // No consistency tests on this library version yet
  },
}
Example #13
Source File: limit.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
Limit = (props: IProps) => {
  const { value, mode } = props;
  const [renderData, setValue] = React.useState<API_ACCESS.BaseSlaLimit[]>([]);

  React.useEffect(() => {
    const curVal = isEmpty(value) ? [{ ...defaultData }] : (cloneDeep(value) as API_ACCESS.BaseSlaLimit[]);
    setValue(curVal);
  }, [value]);

  const handleChange = (index: number, name: 'limit' | 'unit', v: string | number | undefined) => {
    const newData = cloneDeep(renderData);
    set(newData[index], name, v);
    setValue(newData);
    props.onChange && props.onChange(newData);
  };

  const handleAddOne = () => {
    const lastItem = renderData[renderData.length - 1];
    if (lastItem.limit && lastItem.unit) {
      const newData = [...cloneDeep(renderData), { ...defaultData }];
      setValue(newData);
      props.onChange && props.onChange(newData);
    }
  };

  const handleDropOne = (index: number) => {
    const newData = cloneDeep(renderData);
    remove(newData, (_v, idx) => idx === index);
    setValue(newData);
    props.onChange && props.onChange(newData);
  };

  return (
    <>
      {renderData.map(({ limit, unit }, index) => {
        return (
          <InputGroup compact key={String(index)} className="mb-1">
            <InputNumber
              placeholder={i18n.t('please enter')}
              min={1}
              step={1}
              max={999999999999999}
              value={limit}
              style={{ width: mode === 'multiple' ? '65%' : '80%' }}
              onChange={(v) => {
                handleChange(index, 'limit', v);
              }}
            />
            <Select
              placeholder={i18n.t('Please Select')}
              style={{ width: '20%' }}
              value={unit}
              onChange={(v) => {
                handleChange(index, 'unit', v as string);
              }}
            >
              {map(slaUnitMap, (name, k) => (
                <Option value={k} key={k}>
                  {name}
                </Option>
              ))}
            </Select>
            {mode === 'multiple' ? (
              <div className="sla-limit-operation">
                <div className="flex justify-between items-center pl-3">
                  <Tooltip title={i18n.t('add {name}', { name: i18n.t('Request Limit') })}>
                    <ErdaIcon type="add-one" onClick={handleAddOne} size="20" />
                  </Tooltip>
                  {index !== 0 ? (
                    <Tooltip title={i18n.t('Delete')}>
                      <ErdaIcon
                        type="reduce-one"
                        onClick={() => {
                          handleDropOne(index);
                        }}
                        size="20"
                      />
                    </Tooltip>
                  ) : null}
                </div>
              </div>
            ) : null}
          </InputGroup>
        );
      })}
    </>
  );
}
Example #14
Source File: aliCloud-container-cluster-form.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
AliCloudContainerClusterForm = ({ visible, onClose, onSubmit, cloudVendor }: IProps) => {
  const formRef = React.useRef(null as any);
  const clusterList = clusterStore.useStore((s) => s.list);
  const { addCloudCluster, getCloudPreview } = clusterStore.effects;
  const { getVpcList, getVswList } = networkStore.effects;
  const [vpcList, vswList] = networkStore.useStore((s) => [s.vpcList, s.vswList]);

  const [{ count, previewModalVis, previewData, postData }, updater] = useUpdate({
    previewData: [],
    previewModalVis: false,
    postData: {} as any,
    count: 1, // TODO forceUpdate 由于表单内的联动时,使用setFields是无法触发父组件的重新渲染的,导致setFields(xxx)内的参数还是上次闭包中的值,使结果不生效,这里先hack一下
  });

  const getFormData = (key: string, defaultValue?: any) => {
    return formRef.current ? get(formRef.current.getData(), key) : defaultValue || undefined;
  };

  const reloadFields = () => {
    updater.count(count + 1);
  };

  const onVpcCidrChange = (value: string) => {
    const currentPodValue = getFormData('podCIDR', '');
    const currentServiceValue = getFormData('serviceCIDR', '');
    if (value.startsWith('10.')) {
      currentPodValue.startsWith('10.') && formRef.current.setFieldValue('podCIDR', '172.16.0.0/14');
      currentServiceValue.startsWith('10.') && formRef.current.setFieldValue('serviceCIDR', '172.20.0.0/16');
    } else {
      formRef.current.setFieldValue('podCIDR', '10.16.0.0/14');
      formRef.current.setFieldValue('serviceCIDR', '10.20.0.0/16');
    }
  };

  const vpcCIDRField = {
    label: i18n.t('cmp:VPC network segment'),
    component: 'input',
    key: 'vpcCIDR',
    disabled: getFormData('isNewVpc') === 'exist',
    rules: subNetRule,
    required: true,
    defaultValue: get(cloudVendorMap, 'initValue.vpcCIDR'),
    category: 'more',
    componentProps: {
      onChange: (e: any) => {
        onVpcCidrChange(e.target.value);
      },
    },
  };

  const vSwitchCIDRField = {
    label: i18n.t('cmp:switch network segment'),
    component: 'input',
    key: 'vSwitchCIDR',
    required: true,
    disabled: getFormData('isNewVsw') === 'exist',
    defaultValue: get(cloudVendorMap, 'initValue.vSwitchCIDR'),
    rules: subNetRule,
    category: 'more',
  };

  const fields = [
    {
      label: firstCharToUpper(i18n.t('{name} identifier', { name: i18n.t('cluster') })),
      component: 'input',
      key: 'clusterName',
      rules: [
        regRules.clusterName,
        {
          validator: (v: any) => {
            const curCluster = find(clusterList, { name: v });
            return [!curCluster, i18n.t('cmp:cluster already existed')];
          },
        },
      ],
      componentProps: {
        placeholder: i18n.t('cmp:Characters and numbers, separated by a hyphen and cannot be modified after creation'),
      },
      required: true,
      category: 'basic',
    },
    {
      label: i18n.t('Cluster name'),
      component: 'input',
      key: 'displayName',
      rules: [
        {
          min: '1',
        },
        {
          max: '30',
        },
      ],
      componentProps: {
        placeholder: i18n.t('Please enter the {name}', { name: i18n.t('Cluster name').toLowerCase() }),
      },
      category: 'basic',
    },
    {
      label: i18n.t('cmp:Extensive domain'),
      component: 'input',
      key: 'rootDomain',
      rules: [
        {
          pattern: '/^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/',
          msg: i18n.t('please enter the correct format'),
        },
      ],
      required: true,
      category: 'basic',
    },
    {
      label: i18n.t('Region'),
      component: 'select',
      key: 'region',
      disabled: false,
      componentProps: {
        onChange: () => {
          formRef.current.setFieldValue('isNewVpc', 'new');
          formRef.current.setFieldValue('vpcID', undefined);
          formRef.current.setFieldValue('vpcCIDR', get(cloudVendorMap, 'initValue.vpcCIDR'));
          formRef.current.setFieldValue('isNewVsw', 'new');
          formRef.current.setFieldValue('vSwitchID', undefined);
          formRef.current.setFieldValue('vSwitchCIDR', get(cloudVendorMap, 'initValue.vSwitchCIDR'));
          reloadFields();
        },
      },
      required: true,
      dataSource: {
        type: 'static',
        static: getOptions('region'),
      },
      category: 'basic',
    },
    {
      label: i18n.t('cmp:Cluster specification'),
      component: 'radio',
      key: 'clusterSpec',
      componentProps: {
        radioType: 'radio',
      },
      required: true,
      defaultValue: get(clusterSpecMap[cloudVendor], 'Standard.value'),
      dataSource: {
        static: () =>
          map(clusterSpecMap[cloudVendor], ({ name, value, tip }) => (
            <Radio.Button key={value} value={value}>
              <Tooltip title={tip}>{name}</Tooltip>
            </Radio.Button>
          )),
      },
      category: 'basic',
    },
    {
      label: firstCharToUpper(i18n.t('cmp:billing method')),
      component: 'radio',
      key: 'chargeType',
      componentProps: {
        radioType: 'button',
      },
      required: true,
      defaultValue: chargeTypeMap.PostPaid.value,
      dataSource: {
        static: getOptions('chargeType'),
      },
      category: 'basic',
    },
    {
      label: firstCharToUpper(i18n.t('cmp:purchase time')),
      component: 'select',
      key: 'chargePeriod',
      required: true,
      dataSource: {
        type: 'static',
        static: getOptions('chargePeriod'),
      },
      removeWhen: [
        [
          {
            field: 'chargeType',
            operator: '!=',
            value: 'PrePaid',
          },
        ],
      ],
      category: 'basic',
    },
    {
      label: firstCharToUpper(i18n.t('cmp:whether to enable https')),
      component: 'switch',
      key: 'enableHttps',
      required: true,
      defaultValue: false,
      category: 'basic',
    },
    {
      label: i18n.t('cmp:VPC network segment addition method'),
      component: 'select',
      key: 'isNewVpc',
      required: true,
      componentProps: {
        onChange: (e: string) => {
          if (e === 'exist') {
            formRef.current.setFieldValue('vpcCIDR', '');
            formRef.current.setFieldValue('vpcID', undefined);
            getVpcList({ vendor: 'aliyun', region: getFormData('region'), pageNo: 1, pageSize: 30 });
          } else {
            formRef.current.setFieldValue('isNewVsw', 'new');
            formRef.current.setFieldValue('vSwitchCIDR', get(cloudVendorMap, 'initValue.vSwitchCIDR'));
          }
          reloadFields();
        },
      },
      dataSource: {
        type: 'static',
        static: [
          {
            name: i18n.t('Add'),
            value: 'new',
          },
          {
            name: i18n.t('select the existing'),
            value: 'exist',
          },
        ],
      },
      defaultValue: 'new',
      category: 'more',
    },
    {
      label: 'VPC',
      component: 'select',
      key: 'vpcID',
      required: true,
      componentProps: {
        onChange: (e: string) => {
          const selectedVpc = find(vpcList, { vpcID: e });
          const vpcCidrValue = selectedVpc!.cidrBlock;
          formRef.current.setFieldValue('vpcCIDR', vpcCidrValue);
          formRef.current.setFieldValue('isNewVsw', 'new');
          formRef.current.setFieldValue('vSwitchCIDR', '');
          onVpcCidrChange(vpcCidrValue);
          reloadFields();
        },
      },
      dataSource: {
        type: 'static',
        static: map(vpcList, (item) => ({ name: item.vpcName, value: item.vpcID })),
      },
      removeWhen: [
        [
          {
            field: 'isNewVpc',
            operator: '=',
            value: 'new',
          },
        ],
      ],
      category: 'more',
    },
    vpcCIDRField,
    {
      label: i18n.t('cmp:switch network segment addition method'),
      component: 'select',
      key: 'isNewVsw',
      required: true,
      defaultValue: 'new',
      componentProps: {
        onChange: (e: string) => {
          if (e === 'exist') {
            formRef.current.setFieldValue('vSwitchCIDR', '');
            formRef.current.setFieldValue('vSwitchID', undefined);
            getVswList({
              vendor: 'aliyun',
              region: getFormData('region'),
              vpcID: getFormData('vpcID', ''),
              pageNo: 1,
              pageSize: 30,
            });
          } else {
            formRef.current.setFieldValue('vSwitchCIDR', get(cloudVendorMap, 'initValue.vSwitchCIDR'));
          }
          reloadFields();
        },
      },
      dataSource: {
        type: 'static',
        static: [
          {
            name: i18n.t('add (recommended)'),
            value: 'new',
          },
          ...insertWhen(getFormData('isNewVpc') === 'exist' && !!getFormData('vpcID', ''), [
            {
              name: i18n.t('select the existing'),
              value: 'exist',
            },
          ]),
        ],
      },
      category: 'more',
    },
    {
      label: 'VSwitch',
      component: 'select',
      key: 'vSwitchID',
      required: true,
      componentProps: {
        onChange: (e: string) => {
          const selectedVsw = find(vswList, { vSwitchID: e });
          formRef.current.setFieldValue('vSwitchCIDR', selectedVsw!.cidrBlock);
        },
      },
      dataSource: {
        // TODO 这里不使用dynamic方式书写api,是因为一旦调用setFields这个api就会被重新调用,导致重复call
        type: 'static',
        static: map(vswList, (item) => ({ name: `${item.vswName}(${item.cidrBlock})`, value: item.vSwitchID })),
      },
      removeWhen: [
        [
          {
            field: 'isNewVsw',
            operator: '=',
            value: 'new',
          },
        ],
      ],
      category: 'more',
    },
    vSwitchCIDRField,
    {
      label: i18n.t('cmp:Pod segment'),
      component: 'input',
      key: 'podCIDR',
      defaultValue: getFormData('isNewVpc') === 'exist' ? undefined : get(cloudVendorMap, 'initValue.podCIDR'),
      rules: subNetRule,
      category: 'more',
    },
    {
      label: i18n.t('cmp:Service segment'),
      component: 'input',
      key: 'serviceCIDR',
      defaultValue: get(cloudVendorMap, 'initValue.serviceCIDR'),
      rules: subNetRule,
      category: 'more',
    },
  ];

  useUpdateEffect(() => {
    formRef.current && formRef.current.setFields(cloneDeep(fields));
  }, [fields, vpcList, vswList, count]);

  const beforeAddConfirm = (values: any) => {
    const { chargePeriod, ...rest } = values;

    const currentOrg = orgStore.getState((s) => s.currentOrg);
    const { id: orgId, name: orgName } = currentOrg;
    const _postData = {
      ...rest,
      orgId,
      orgName,
      cloudVendor,
      chargePeriod: chargePeriod && Number(chargePeriod),
    };
    getCloudPreview(_postData).then((res: any) => {
      updater.postData(_postData);
      updater.previewData(res);
      updater.previewModalVis(true);
    });
  };

  const onOk = () => {
    formRef.current
      .validateFields()
      .then(async (values: any) => {
        beforeAddConfirm(values);
      })
      .catch(({ errorFields }: { errorFields: Array<{ name: any[]; errors: any[] }> }) => {
        formRef.current.scrollToField(errorFields[0].name);
      });
  };

  const onClosePreview = () => {
    updater.previewModalVis(false);
    updater.previewData([]);
  };

  const onPreviewSubmit = () => {
    const _values = cloneDeep(postData);
    remove(_values, 'isNewVpc');
    remove(_values, 'isNewVsw');
    set(_values, 'clusterType', 'Edge');
    set(_values, 'cloudVendor', cloudVendor);

    addCloudCluster(_values).then((res) => {
      const { recordID } = res;
      onSubmit({ recordID });
    });
  };

  return (
    <>
      <Modal
        title={
          cloudVendor === 'alicloud-cs'
            ? i18n.t('cmp:Add Alibaba Cloud Container Service for Kubernetes Cluster (Dedicated)')
            : i18n.t('cmp:Add Alibaba Cloud Container Service for Kubernetes Cluster (Managed)')
        }
        visible={visible}
        onCancel={onClose}
        onOk={onOk}
        destroyOnClose
        maskClosable={false}
        width={600}
      >
        <Form
          fields={fields}
          formRef={formRef}
          formRender={({ RenderFields, form, fields: totalFields }: any) => {
            const basicFields = filter(totalFields, { category: 'basic' });
            const moreFields = filter(totalFields, { category: 'more' });
            const region = getFormData('region');
            return (
              <>
                <div className="font-bold">{i18n.t('Basic settings')}</div>
                <RenderFields form={form} fields={basicFields} />
                {region ? (
                  <>
                    <div className="font-bold">{i18n.t('dop:more settings')}</div>
                    <RenderFields form={form} fields={moreFields} />
                  </>
                ) : null}
              </>
            );
          }}
        />
      </Modal>
      <AliCloudFormPreview
        dataSource={previewData}
        visible={previewModalVis}
        onClose={onClosePreview}
        onOk={onPreviewSubmit}
      />
    </>
  );
}
Example #15
Source File: aliCloud-erdc-form.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
AliCloudErdcForm = ({ visible, onClose, onSubmit }: IProps) => {
  const formRef = React.useRef(null as any);
  const clusterList = clusterStore.useStore((s) => s.list);
  const { addCloudCluster } = clusterStore.effects;

  const [{ storage }, updater] = useUpdate({
    storage: 'nas',
  });

  const basicFields = [
    {
      label: firstCharToUpper(i18n.t('{name} identifier', { name: i18n.t('cluster') })),
      component: 'input',
      key: 'clusterName',
      rules: [
        regRules.clusterName,
        {
          validator: (v: any) => {
            const curCluster = find(clusterList, { name: v });
            return [!curCluster, i18n.t('cmp:cluster already existed')];
          },
        },
      ],
      componentProps: {
        placeholder: i18n.t('cmp:Characters and numbers, separated by a hyphen and cannot be modified after creation'),
      },
      required: true,
    },
    {
      label: i18n.t('Cluster name'),
      component: 'input',
      key: 'displayName',
      rules: [
        {
          min: '1',
        },
        {
          max: '30',
        },
      ],
    },
    {
      label: i18n.t('cmp:Extensive domain'),
      component: 'input',
      key: 'rootDomain',
      rules: [
        {
          pattern: '/^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/',
          msg: i18n.t('please enter the correct format'),
        },
      ],
      required: true,
    },
    {
      label: i18n.t('cmp:Cluster specification'),
      component: 'radio',
      key: 'clusterSize',
      componentProps: {
        radioType: 'radio',
      },
      required: true,
      defaultValue: get(clusterSpecMap.erdc, 'prod.value'),
      dataSource: {
        static: () =>
          map(clusterSpecMap.erdc, ({ name, value }) => (
            <Radio.Button key={value} value={value}>
              {name}
            </Radio.Button>
          )),
      },
    },
    {
      label: i18n.t('cmp:whether to enable https'),
      component: 'switch',
      key: 'enableHttps',
      required: true,
      defaultValue: false,
    },
  ];

  const springboardFields = [
    {
      label: firstCharToUpper(i18n.t('cmp:machine IP')),
      component: 'input',
      key: 'installerIp',
      required: true,
      componentProps: {
        placeholder: i18n.t('Please enter the {name}', { name: i18n.t('cmp:machine IP') }),
      },
    },
    {
      label: i18n.t('Username'),
      component: 'input',
      key: 'user',
      required: true,
    },
    {
      label: i18n.t('Password'),
      component: 'input',
      key: 'password',
      required: true,
      isPassword: true,
    },
    {
      label: i18n.t('Port'),
      component: 'input',
      key: 'port',
      required: true,
      defaultValue: '22',
      rules: [
        {
          pattern: String(/^\d+$/),
          msg: i18n.t('msp:Please enter the number'),
        },
      ],
    },
  ];

  const storageFields = [
    {
      label: i18n.t('cmp:Shared storage'),
      component: 'select',
      key: 'storage',
      required: true,
      componentProps: {
        onChange: (e: string) => {
          updater.storage(e);
        },
      },
      dataSource: {
        type: 'static',
        static: [
          {
            name: 'NAS',
            value: 'nas',
          },
          {
            name: 'Glusterfs',
            value: 'glusterfs',
          },
        ],
      },
      defaultValue: 'nas',
    },
    ...insertWhen(storage === 'nas', [
      {
        label: i18n.t('cmp:NAS address'),
        component: 'input',
        key: 'nasDomain',
        required: true,
        componentProps: {
          placeholder: i18n.t('Please enter the {name}', { name: i18n.t('cmp:NAS address') }),
        },
      },
    ]),
    ...insertWhen(storage !== 'nas', [
      {
        label: i18n.t('cmp:Glusterfs server IP address list'),
        component: 'input',
        key: 'glusterfsIps',
        required: true,
        componentProps: {
          placeholder: i18n.t('cmp:3 or 1 is recommended, and separate them by comma'),
        },
      },
    ]),
  ];

  const networkFields = [
    {
      label: i18n.t('cmp:Container segment'),
      component: 'input',
      key: 'dockerCIDR',
      rules: [
        {
          pattern: String(regRulesMap.subnet.pattern),
          msg: regRulesMap.subnet.message,
        },
      ],
      required: true,
    },
    {
      label: i18n.t('cmp:Pod segment'),
      component: 'input',
      key: 'podCIDR',
      required: true,
      rules: [
        {
          pattern: String(regRulesMap.subnet.pattern),
          msg: regRulesMap.subnet.message,
        },
      ],
    },
    {
      label: i18n.t('cmp:Service segment'),
      component: 'input',
      key: 'serviceCIDR',
      required: true,
      rules: [
        {
          pattern: String(regRulesMap.subnet.pattern),
          msg: regRulesMap.subnet.message,
        },
      ],
    },
  ];

  const serverFields = [
    {
      label: i18n.t('cmp:Domain name server address'),
      component: 'input',
      key: 'nameservers',
      componentProps: {
        placeholder: i18n.t('cmp:Separate by comma'),
      },
      required: true,
      rules: [
        {
          pattern: String(regRulesMap.ipWithComma.pattern),
          msg: regRulesMap.ipWithComma.message,
        },
      ],
    },
  ];

  const machineFields = [
    {
      label: firstCharToUpper(i18n.t('cmp:machine IP-list')),
      component: 'input',
      key: 'hostIps',
      required: true,
      componentProps: {
        placeholder: i18n.t('cmp:Separate by comma'),
      },
      rules: [
        {
          pattern: String(regRulesMap.ipWithComma.pattern),
          msg: regRulesMap.ipWithComma.message,
        },
      ],
    },
    {
      label: i18n.t('cmp:Data disk device'),
      component: 'input',
      key: 'device',
      componentProps: {
        placeholder: i18n.t('cmp:such as vdb, which does not support multiple data disks, can be empty.'),
      },
    },
  ];

  const fields = [
    ...map(basicFields, (field) => ({ ...field, category: 'basic' })),
    ...map(springboardFields, (field) => ({ ...field, category: 'springboard' })),
    ...map(storageFields, (field) => ({ ...field, category: 'storage' })),
    ...map(networkFields, (field) => ({ ...field, category: 'network' })),
    ...map(serverFields, (field) => ({ ...field, category: 'server' })),
    ...map(machineFields, (field) => ({ ...field, category: 'machine' })),
  ];

  React.useEffect(() => {
    formRef.current && formRef.current.setFields(cloneDeep(fields));
  }, [fields, storage]);

  const onOk = () => {
    formRef.current
      .validateFields()
      .then((values: any) => {
        const currentOrg = orgStore.getState((s) => s.currentOrg);
        const { id: orgId, name: orgName } = currentOrg;
        remove(values, 'storage');
        remove(values, 'isNewVpc');
        remove(values, 'isNewVsw');
        set(values, 'clusterType', 'Edge');
        set(values, 'cloudVendor', 'alicloud-ecs');

        const _postData = {
          ...values,
          orgId,
          orgName,
        };

        addCloudCluster(_postData).then((res) => {
          const { recordID } = res;
          onSubmit({ recordID });
        });
      })
      .catch(({ errorFields }: { errorFields: Array<{ name: any[]; errors: any[] }> }) => {
        formRef.current.scrollToField(errorFields[0].name);
      });
  };

  return (
    <>
      <Modal
        title={i18n.t('cmp:Add existing resources to build a cluster')}
        visible={visible}
        onCancel={onClose}
        onOk={onOk}
        destroyOnClose
        maskClosable={false}
        width={600}
      >
        <Form
          fields={fields}
          formRef={formRef}
          formRender={({ RenderFields, form, fields: totalFields }: any) => {
            const bFields = filter(totalFields, { category: 'basic' });
            const sFields = filter(totalFields, { category: 'springboard' });
            const stFields = filter(totalFields, { category: 'storage' });
            const dFields = filter(totalFields, { category: 'network' });
            const seFields = filter(totalFields, { category: 'server' });
            const mFields = filter(totalFields, { category: 'machine' });
            return (
              <>
                <div className="font-bold mb-1">{firstCharToUpper(i18n.t('cmp:cluster configuration'))}</div>
                <RenderFields form={form} fields={bFields} />
                <div className="font-bold mb-1">{i18n.t('cmp:Jump server configuration')}</div>
                <RenderFields form={form} fields={sFields} />
                <div className="font-bold mb-1">{i18n.t('cmp:Shared storage')}</div>
                <RenderFields form={form} fields={stFields} />
                <div className="font-bold mb-1">{i18n.t('cmp:Network configuration')}</div>
                <RenderFields form={form} fields={dFields} />
                <div className="font-bold mb-1">{i18n.t('cmp:Domain name server')}</div>
                <RenderFields form={form} fields={seFields} />
                <div className="font-bold mb-1">{i18n.t('cmp:Machine information configuration')}</div>
                <RenderFields form={form} fields={mFields} />
              </>
            );
          }}
        />
      </Modal>
    </>
  );
}
Example #16
Source File: custom-label.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
CustomLabel = React.forwardRef(
  ({ value = emptyArr, onChange = emptyFun, labelName = i18n.t('dop:Add-label') }: IProps, ref) => {
    const [labels, setLabels] = React.useState([] as string[]);
    const [showInput, setShowInput] = React.useState(false);
    const [inputVal, setInputVal] = React.useState(undefined);
    const inputRef = React.useRef(null);
    React.useEffect(() => {
      const l = isEmpty(value) ? [] : isString(value) ? value.split(',') : value;
      setLabels(l);
    }, [value]);

    useUnmount(() => {
      setInputVal(undefined);
      setShowInput(false);
      setLabels([]);
    });

    React.useEffect(() => {
      const curRef = inputRef && (inputRef.current as any);
      if (showInput && curRef) {
        curRef.focus();
      }
    }, [inputRef, showInput]);

    const deleteLabel = (label: string) => {
      const labelArr = [...labels];
      remove(labelArr, (item) => item === label);
      onChange(labelArr);
    };

    const addLabel = (e: any) => {
      const label = e.target.value;
      label && label.trim();
      if (label) {
        const exitLabel = find(labels, (item) => item === label);
        !exitLabel && onChange([...labels, label]);
      }
      toggleShowInput();
      setInputVal(undefined);
    };
    const toggleShowInput = () => {
      setShowInput(!showInput);
    };
    return (
      <div ref={ref} className="custom-label-comp">
        {labels.map((item, i) => {
          return (
            <span key={`${item}_${String(i)}`} className={'tag-default'}>
              <div className="flex items-center">
                {item}
                <ErdaIcon
                  className="cursor-pointer"
                  onClick={() => {
                    deleteLabel(item);
                  }}
                  size="14"
                  color="black-600"
                  type="close"
                />
              </div>
            </span>
          );
        })}

        {showInput ? (
          <Input
            size="small"
            ref={inputRef}
            className="custom-label-input"
            placeholder={i18n.t('please enter')}
            value={inputVal}
            onChange={(e: any) => setInputVal(e.target.value)}
            onPressEnter={addLabel}
            onBlur={addLabel}
          />
        ) : (
          <Button
            type="primary"
            ghost
            className="custom-label-add"
            onClick={() => {
              toggleShowInput();
            }}
          >
            + {labelName}
          </Button>
        )}
      </div>
    );
  },
)
Example #17
Source File: service-list.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
function ServiceList({
  containerList,
  serviceList,
  depth,
  into,
  haveMetrics,
  onReload,
  haveHost = true,
  haveStatus = true,
  slot,
  extraQuery,
}: IProps) {
  const [renderOp, drawer] = useInstanceOperation<IInstance>({
    log: true,
    console: true,
    monitor: true,
    getProps(type, record) {
      return {
        console: {
          host: record.host_private_addr || record.host,
          clusterName: extraQuery.filter_cluster_name,
        },
        log: {
          fetchApi: '/api/orgCenter/logs',
          extraQuery: { clusterName: record.clusterName },
        },
        monitor: {
          api: '/api/orgCenter/metrics',
          extraQuery,
        },
      }[type];
    },
  });

  let list = [];
  let cols = [];
  if (depth && depth < 5) {
    list = serviceList;
    // 设一个id作为rocord的key,避免切换rowKey时新数据还没拿到引起rowKey和record的key不一致
    list.forEach((item: any, i: number) => {
      set(item, 'id', item.id || item.name || i);
    });
    const titleMap = ['', 'PROJECT', 'APPLICATION', 'RUNTIME', 'SERVICE', 'CONTAINER'];
    const titleCnMap = {
      '': '',
      PROJECT: i18n.t('project'),
      APPLICATION: i18n.t('application'),
      RUNTIME: i18n.t('App instance'),
      SERVICE: i18n.t('Microservice'),
      CONTAINER: 'CONTAINER',
    };
    const iconMap = ['', 'project', 'wenjianjia', 'fengchao', 'atom'];

    cols = [
      {
        title: titleCnMap[titleMap[depth]],
        dataIndex: 'id',
        key: 'id',
        // width: 320,
        render: (text: string, record: any) => (
          <span className="font-bold hover-table-text" onClick={() => into({ q: text, name: record.name })}>
            <CustomIcon type={iconMap[depth]} />
            {record.name}
          </span>
        ),
      },
      {
        title: i18n.t('Number of instances'),
        dataIndex: 'instance',
        key: 'instance',
        width: 176,
      },
      {
        title: 'CPU',
        dataIndex: 'cpu',
        key: 'cpu',
        width: 125,
        sorter: (a: any, b: any) => {
          if (!haveMetrics) return Number(a.cpu) - Number(b.cpu);
          const use_a = getMetricsInfo(a, 'cpuUsagePercent') || 0;
          const use_b = getMetricsInfo(b, 'cpuUsagePercent') || 0;
          return Number(use_a / a.cpu) - Number(use_b / b.cpu);
        },
        render: (total: number, record: any) => {
          if (!haveMetrics) return `${total} ${i18n.t('core')}`;
          const parsedTotal = total * 1000;
          const used = +(getMetricsInfo(record, 'cpuUsagePercent') || 0).toFixed(4) * 1000;
          const { percent, statusClass } = countPercent(used, parsedTotal);
          return ProgressItem(percent, round(used, 2), parsedTotal, i18n.t('millicore'), statusClass);
        },
      },
      {
        title: i18n.t('memory'),
        dataIndex: 'memory',
        key: 'memory',
        width: 125,
        sorter: (a: any, b: any) => {
          if (!haveMetrics) return Number(a.memory) - Number(b.memory);
          const use_a = getMetricsInfo(a, 'memUsage') || 0;
          const use_b = getMetricsInfo(b, 'memUsage') || 0;
          return Number(use_a / 1048576 / a.memory) - Number(use_b / 1048576 / b.memory);
        },
        render: (total: number, record: any) => {
          if (!haveMetrics) return `${total} MB`;
          const used = +((getMetricsInfo(record, 'memUsage') || 0) / 1048576).toFixed(2);
          const { percent, statusClass } = countPercent(used, total);
          return ProgressItem(percent, used, total, 'MB', statusClass);
        },
      },
      {
        title: i18n.t('Disk'),
        dataIndex: 'disk',
        key: 'disk',
        width: 125,
        sorter: (a: any, b: any) => Number(a.disk) - Number(b.disk),
        render: (size: string) => getFormatter('STORAGE', 'MB').format(size),
      },
      {
        title: i18n.t('Status'),
        dataIndex: 'unhealthy',
        width: 100,
        align: 'center',
        render: (num: number) => (
          <span>
            <IF check={!!num}>
              <span>
                <Badge status="error" /> {num}
              </span>
              <IF.ELSE />
              <Badge status="success" />
            </IF>
          </span>
        ),
      },
    ];
    // if (includes(['RUNTIME', 'SERVICE'], titleMap[depth])) { // runtime和service级别,需要展示状态
    //   (cols as any[]).push({
    //     title: '状态',
    //     dataIndex: 'status',
    //     width: 100,
    //     align: 'center',
    //     render: (text: string) => {
    //       const stateObj = get(statusConfig, `${titleMap[depth]}.${text}`) || statusConfig.Unknown;
    //       return <Tooltip title={text || 'Unknown'}><Badge status={stateObj.state} /></Tooltip>;
    //     },
    //   });
    // }
  } else {
    list = containerList;
    cols = [
      {
        title: 'IP',
        key: 'ip_addr',
        width: 120,
        sorter: (a: any, b: any) =>
          Number((a.ip_addr || a.ipAddress || '').replace(/\./g, '')) -
          Number((b.ip_addr || b.ipAddress || '').replace(/\./g, '')),
        render: (record: any) => record.ip_addr || record.ipAddress || i18n.t('cmp:no ip address'),
      },
      haveHost
        ? {
            title: i18n.t('cmp:Host address'),
            key: 'host_private_addr',
            width: 120,
            render: (record: any) => record.host_private_addr || record.host || i18n.t('cmp:no host address'),
          }
        : null,
      {
        title: i18n.t('Image'),
        key: 'image',
        // width: 400,
        className: 'item-image',
        render: (record: any) => {
          const text = record.image_name || record.image;
          if (!text) {
            return null;
          }
          return (
            <Tooltip title={`${i18n.t('click to copy')}:${text}`} overlayClassName="tooltip-word-break">
              <span
                className="image-name for-copy-image w-[400px]"
                data-clipboard-tip={i18n.t('Image name')}
                data-clipboard-text={text}
              >
                {getImageText(text)}
              </span>
            </Tooltip>
          );
        },
      },
      {
        title: 'CPU',
        dataIndex: 'cpu',
        key: 'cpu',
        width: 120,
        sorter: (a: any, b: any) => {
          if (!haveMetrics) return Number(a.cpu) - Number(b.cpu);
          const use_a = getMetricsInfo(a, 'cpuUsagePercent') || 0;
          const use_b = getMetricsInfo(b, 'cpuUsagePercent') || 0;
          return Number(use_a / a.cpu) - Number(use_b / b.cpu);
        },
        render: (total: number, record: any) => {
          if (!haveMetrics) return total;
          const parsedTotal = total * 1000;
          const used = +(getMetricsInfo(record, 'cpuUsagePercent') || 0).toFixed(4) * 1000;
          const { percent, statusClass } = countPercent(used, parsedTotal);
          return ProgressItem(percent, round(used, 2), parsedTotal, i18n.t('millicore'), statusClass);
        },
      },
      {
        title: i18n.t('memory'),
        dataIndex: 'memory',
        key: 'memory',
        width: 120,
        sorter: (a: any, b: any) => {
          if (!haveMetrics) return Number(a.memory) - Number(b.memory);
          const use_a = getMetricsInfo(a, 'memUsage') || 0;
          const use_b = getMetricsInfo(b, 'memUsage') || 0;
          return Number(use_a / 1048576 / a.memory) - Number(use_b / 1048576 / b.memory);
        },
        render: (total: number, record: any) => {
          if (!haveMetrics) return getFormatter('STORAGE', 'MB').format(total);
          const used = getMetricsInfo(record, 'memUsage') || 0;
          const { percent, statusClass } = countPercent(used, total);
          return ProgressItem(percent, used, total, '', statusClass);
        },
      },
      {
        title: i18n.t('Disk'),
        dataIndex: 'disk',
        key: 'disk',
        width: 120,
        sorter: (a: any, b: any) => Number(a.disk) - Number(b.disk),
        render: (size: number) => getFormatter('STORAGE', 'MB').format(size),
      },
      // TODO: 集群组件目前无状态,3.5暂时去除,后续提供后打开
      haveStatus
        ? {
            title: i18n.t('Status'),
            dataIndex: 'status',
            key: 'status',
            width: 100,
            align: 'center',
            render: (text: string) => {
              const stateObj = get(statusConfig, `CONTAINER.${text}`) || statusConfig.Unknown;
              return (
                <Tooltip title={text || 'Unknown'}>
                  <Badge status={stateObj.state} />
                </Tooltip>
              );
            },
          }
        : null,
      {
        title: i18n.t('operations'),
        key: 'operation',
        width: 180,
        render: renderOp,
      },
    ];
    if (depth === 3) {
      cols.splice(2, {
        title: i18n.t('Number of instances'),
        dataIndex: 'instance',
        key: 'instance',
      } as any);
    }
  }
  remove(cols as any, (item) => item === null);

  return (
    <div className="service-table">
      <ErdaTable
        slot={slot}
        rowKey={(record: any, i: number) => `${i}${record.id}`}
        pagination={false}
        columns={cols as Array<ColumnProps<any>>}
        dataSource={list}
        onReload={onReload}
        scroll={{ x: 1100 }}
      />
      <Copy selector=".for-copy-image" />
      {drawer}
    </div>
  );
}
Example #18
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
TestSet = ({
  needRecycled = false,
  needActiveKey = true,
  readOnly = false,
  needBreadcrumb = false,
  onSelect: oldOnSelect,
  mode,
  testPlanID,
  testSetRef,
  customActions = [],
}: IProps) => {
  const [hasExpand, setHasExpand] = useState(false);
  const [activeKey, setActiveKey] = useState(rootKey);
  const [copyOrClipKey, setCopyOrClipKey] = useState('');
  const [editMode, setEditMode] = useState('' as editModeEnum);
  const [expandedKeys, setExpandedKeys] = useState([] as string[]);
  const [treeData, setTreeData] = useState([] as TEST_SET.TestSetNode[]);
  const firstBuild = useRef(true);
  const query = routeInfoStore.useStore((s) => s.query);
  const [projectTestSet, modalTestSet, tempTestSet, reloadTestSetInfo, activeOuter] = testSetStore.useStore((s) => [
    s.projectTestSet,
    s.modalTestSet,
    s.tempTestSet,
    s.reloadTestSetInfo,
    s.activeOuter,
  ]);
  const { getProjectTestSets, getTestSetChildren, updateBreadcrumb, subSubmitTreeCollection } = testSetStore.effects;
  const { emptyReloadTestSet, clearActiveOuter } = testSetStore.reducers;
  const projectInfo = projectStore.useStore((s) => s.info);
  const { getCases } = testCaseStore.effects;
  const { triggerChoosenAll: resetChoosenAll } = testCaseStore.reducers;

  const testSet: { [k in TEST_CASE.PageScope]: any } = {
    testCase: projectTestSet,
    testPlan: projectTestSet,
    caseModal: modalTestSet,
    temp: tempTestSet,
  };
  const currentTestSet: TEST_SET.TestSet[] = testSet[mode];

  useMount(() => {
    getProjectTestSets({ testPlanID, mode, recycled: false, parentID: rootId, forceUpdate: true });
  });

  const loadTreeNode = (arr: string[], isInRecycleBin = false) => {
    arr.reduce(async (prev, curr) => {
      await prev;
      const o = fetchData(curr.split('-').reverse()[0], curr, isInRecycleBin);
      return o;
    }, Promise.resolve());
  };

  const addNodeFromOuter = (data: TEST_SET.TestSet) => {
    const newNode: TEST_SET.TestSetNode = {
      title: data.name,
      key: `${rootKey}-${data.id}`,
      recycled: false,
      ...data,
    };
    const parent = getNodeByPath({ treeData, eventKey: rootKey });
    parent.children = [newNode, ...(parent.children || [])];
    setTreeData([...treeData]);
  };
  const reloadLoadData = (id: number, eventKey: string, recycled: boolean) => {
    if (+id === 0) {
      getProjectTestSets({ testPlanID, mode, recycled: false, parentID: rootId, forceUpdate: true });
    } else {
      fetchData(id, eventKey, recycled);
    }
  };

  const updateTree = (tree: IExpandTree[], data: ITree[], newTreeData: TEST_SET.TestSetNode[]) => {
    tree.forEach(({ id, children }) => {
      const newTree = data.find((item) => item.testSetID === id);
      if (!isEmpty(newTree)) {
        const { key, list } = newTree as ITree;
        if (key === rootKey) {
          set(newTreeData, ['0', 'children'], list);
        } else {
          const parent = getNodeByPath({ treeData: newTreeData, eventKey: key });
          if (!isEmpty(parent)) {
            set(parent, 'children', list);
          }
        }
      }
      if (children.length) {
        updateTree(children, data, newTreeData);
      }
    });
  };

  const [expandTree, expandIds] = React.useMemo<
    [IExpandTree[], Array<{ id: number; key: string; pKey: string }>]
  >(() => {
    const result: IExpandTree[] = [];
    // 展开节点ID,需去重,防止一个节点请求多次
    const temp: Array<{ id: number; key: string; pKey: string }> = [];
    // 最深层级路径
    const deepestPath: string[] = [];
    const keys = [...expandedKeys];
    // 将expandedKeys倒序
    keys.sort((a, b) => b.split('-').length - a.split('-').length);
    // 获取不重复的最深路径
    keys.forEach((expandedKey) => {
      const flag = deepestPath.some((s) => s.includes(expandedKey));
      if (!flag) {
        deepestPath.push(expandedKey);
      }
    });
    deepestPath.forEach((str) => {
      const idArr = str.split('-').map((id) => +id);
      const keyTemp: number[] = [];
      idArr.forEach((id) => {
        const pKey = keyTemp.join('-');
        keyTemp.push(id);
        const key = keyTemp.join('-');
        temp.push({
          id,
          pKey,
          key,
        });
      });
    });
    // 最深层级转换为tree
    const tree = expandKeys2tree(deepestPath);
    // 合并tree
    tree.forEach((child) => {
      mergeTree(result, child);
    });
    return [result, uniqBy(temp, 'id')];
  }, [expandedKeys]);

  const removeMenu = (ids: number[]) => {
    if (ids.length === 0) {
      getCases();
    } else {
      let newActiveKey = rootKey;
      let tempIndex = 0;
      const promiseArr: Array<
        PromiseLike<{
          testSetID: number;
          key: string;
          pKey: string;
          list: TEST_SET.TestSet[];
        }>
      > = [];
      expandIds.forEach(({ id, key, pKey }) => {
        promiseArr.push(
          getTestSetChildren({ testPlanID, recycled: false, parentID: id, mode }).then((res) => ({
            testSetID: id,
            key,
            pKey,
            list: res || [],
          })),
        );
      });
      // 请求所有展开的节点
      Promise.all(promiseArr).then((data) => {
        const activeKeys = activeKey.split('-');
        const trees: ITree[] = [];
        data.forEach(({ list, key, pKey, testSetID }) => {
          const targetIndex = activeKeys.findIndex((t) => t === `${testSetID}`);
          if (targetIndex !== -1 && !!list.length) {
            tempIndex = Math.max(targetIndex, tempIndex);
          }
          trees.push({
            testSetID,
            pKey,
            key,
            list: (list || []).map(({ id: childId, name, recycled, parentID }) => ({
              id: childId,
              title: name,
              key: `${key}-${childId}`,
              isLeaf: false,
              recycled,
              parentID,
              children: [],
            })),
          });
        });
        const newTree = [...treeData];
        // 递归更新展开的节点
        updateTree(expandTree, trees, newTree);
        setTreeData(newTree);
        newActiveKey = activeKeys.slice(0, tempIndex + 1).join('-');
        setActiveKey(newActiveKey);
        onSelect([newActiveKey]);
      });
    }
  };

  useImperativeHandle(testSetRef, () => ({
    addNodeFromOuter,
    reloadLoadData,
    removeMenu,
  }));

  useEffect(() => {
    const expandId = (query.eventKey || '').split('-');
    if (firstBuild.current && !isEmpty(treeData)) {
      if (!(query.caseId || expandId.length > 1)) {
        // 当 query 不为空的时候,就保持当前的 query 值
        onSelect([rootKey], { keepCurrentSearch: !isEmpty(query) });
      }
      // onSelect([rootKey]);
      firstBuild.current = false;
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [treeData, query.caseId, query.eventKey]);

  useEffect(() => {
    if (activeOuter) {
      onAddNode(rootKey);
      clearActiveOuter();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [activeOuter]);

  useEffect(() => {
    const normalNodes: TEST_SET.TestSetNode[] = currentTestSet.map(({ id, name, recycled, parentID }: any) => ({
      title: name,
      key: `${rootKey}-${id}`,
      id,
      recycled,
      parentID,
      isLeaf: false,
    }));
    const nextActiveKey = firstBuild.current && query.eventKey && needActiveKey ? query.eventKey : rootKey;
    setActiveKey(nextActiveKey);
    expandedKeys.length === 0 && setExpandedKeys([rootKey]);
    setTreeData([
      {
        title: projectInfo.name,
        key: rootKey,
        id: rootId,
        iconType: 'project',
        iconClass: 'text-blue',
        parentID: rootId,
        isLeaf: false,
        recycled: false,
        children: needRecycled ? normalNodes.concat([{ ...recycledRoot }]) : normalNodes,
      },
    ]);

    if (needBreadcrumb) {
      updateBreadcrumb({
        pathName: projectInfo.name,
        testSetID: rootId,
        testPlanID,
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentTestSet, projectInfo]);
  useEffect(() => {
    const expandId: string[] = (query.eventKey || '').split('-');
    // 是否为回收站内的case
    const isInRecycleBin = expandId.includes('recycled');
    // 测试计划测试用例需要打开分享链接
    if (!['testCase', 'testPlan'].includes(mode)) {
      return;
    }
    const hasCaseId = hasExpand ? query.caseId : query.caseId || expandId.length > 1;
    if (hasCaseId && treeData[0] && treeData[0].children) {
      // 第二级节点key值
      const secondLevelKey = expandId.slice(0, 2).join('-');
      // 所有展开节点的key值
      const eventKeys: string[] = expandId.reduce((previousValue, _currentValue, index, arr) => {
        const last = [...arr].splice(0, index + 1);
        return [...previousValue, last.join('-')];
      }, [] as string[]);
      getCases({ testSetID: +query.testSetID, pageNo: 1, testPlanID, recycled: isInRecycleBin, scope: mode });
      setExpandedKeys(eventKeys || [rootKey]);
      setActiveKey(query.eventKey);
      const secondLevel = treeData[0].children.find((t) => t.key === secondLevelKey) || {};
      const firstChildren = get(secondLevel, 'children');
      if (!isEmpty(secondLevel) && !firstChildren) {
        // 逐级请求节点
        loadTreeNode(eventKeys.splice(1), isInRecycleBin);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [treeData, query.caseId, query.eventKey, mode, query.testSetID, getCases, testPlanID]);

  // 复制/移动/引入测试集
  useEffect(() => {
    if (!isEmpty(reloadTestSetInfo)) {
      const { isMove, reloadParent, parentID, testSetID } = reloadTestSetInfo;
      const currentNode =
        getNodeById({ treeData, id: testSetID as number, recycled: false }) || ({} as TEST_SET.TestSetNode);
      let parentNode = null;
      if (reloadParent && currentNode) {
        // 当前节点的父级是否存在
        parentNode = getNodeById({ treeData, id: currentNode.parentID, recycled: false });
      } else if (parentID || parentID === 0) {
        // 传入的父级是否存在
        parentNode = getNodeById({ treeData, id: parentID, recycled: false });
      }
      if (reloadParent && !parentNode) {
        // 那就根节点, 因为添加层级较深的测试集节点时parentId存在但parentNode不存在,见#139377
        parentNode = getNodeById({ treeData, id: 0, recycled: false });
      }
      if (parentNode) {
        // 目标节点存在,则更新
        fetchData(parentNode.id, parentNode.key, parentNode.recycled);
        emptyReloadTestSet();
      }
      if (isMove && currentNode) {
        // 移动后,需要清空发起节点
        onRemoveNode(currentNode.key);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [reloadTestSetInfo]);

  const renderTreeNodes = (data: any[]) =>
    data.map((item) => {
      const isRootNode = item.key === rootKey;
      const titleProps = {
        name: item.title,
        readOnly,
        id: item.id,
        editMode,
        className: isRootNode ? 'root-tree-node' : '',
        recycled: item.recycled,
        eventKey: item.key,
        copyOrClipKey,
        customActions,
        onOperateNode,
        onUpdateNode,
        onRemoveNode,
      };
      const icon = (
        <CustomIcon
          type={item.iconType || 'wjj1'}
          className={item.iconClass || (!isRootNode && item.recycled ? 'text-danger' : 'text-yellow')}
        />
      );
      const className = classnames({
        active: activeKey === item.key,
        copy: copyOrClipKey === item.key && editMode === 'copy',
        clip: copyOrClipKey === item.key && editMode === 'clip',
      });
      if (!isEmpty(item.children)) {
        return (
          <TreeNode key={item.key} className={className} icon={icon} title={<Title {...titleProps} />} dataRef={item}>
            {renderTreeNodes(item.children)}
          </TreeNode>
        );
      }
      return <TreeNode {...item} title={<Title {...titleProps} />} className={className} icon={icon} dataRef={item} />;
    });

  const getChildrenById = (id: number, parentKey: string, isRecycled = false) => {
    return getTestSetChildren({
      testPlanID,
      recycled: isRecycled,
      parentID: id,
      mode,
    }).then((children) =>
      (children || []).map(({ id: childId, name, recycled, parentID }) => ({
        id: childId,
        title: name,
        key: `${parentKey}-${childId}`,
        isLeaf: false,
        recycled,
        parentID,
        children: [],
      })),
    );
  };

  // 加载数据
  const fetchData = (id: number | string, eventKey: string, recycled = false) => {
    const isRoot = id === rootId;
    const newId = id === recycledId ? 0 : (id as number);
    return getChildrenById(newId, eventKey, recycled).then((pureChildren: any) => {
      const children = map(pureChildren, (single) => {
        // 依旧保留原有子级
        const existNode = getNodeByPath({ treeData, eventKey: single.key });
        if (existNode) {
          return {
            ...single,
            parentID: newId === 0 && recycled ? recycledId : single.parentID,
            children: existNode.children,
          };
        } else {
          return { ...single, parentID: newId === 0 && recycled ? recycledId : single.parentID };
        }
      });
      if (isRoot) {
        const recycledNode = getNodeByPath({ treeData, eventKey: recycledKey });
        if (recycledNode) {
          treeData[0].children = [...children, recycledNode];
        } else {
          treeData[0].children = children;
        }
      } else {
        const current = getNodeByPath({ treeData, eventKey });
        current.children = children;
        current.isLeaf = !children.length;
      }
      setTreeData([...treeData]);
    });
  };

  const loadData = (treeNode: any) => {
    const { id, key, recycled } = treeNode.props.dataRef;
    if (includes(id, TEMP_MARK)) {
      return Promise.resolve();
    }
    return fetchData(id, key, recycled);
  };

  // 新建测试集
  const onAddNode = (eventKey: string) => {
    const parent = getNodeByPath({ treeData, eventKey });
    const newId: number =
      (max(
        map(
          filter(parent.children, ({ id }) => includes(id, TEMP_MARK)),
          ({ id }) => parseInt(id.substring(3, id.length), 10),
        ),
      ) as number) || 0;
    const idPrefix = `${TEMP_MARK}${newId + 1}`;
    const tempNode = {
      title: i18n.t('dop:new test set'),
      id: idPrefix,
      key: `${parent.key}-${idPrefix}`,
      recycled: false,
      parentID: parent.id,
      isLeaf: false,
    };
    if (includes(expandedKeys, eventKey)) {
      // 展开过
      parent.children = [tempNode, ...(parent.children || [])];
      setTreeData([...treeData]);
      return;
    }
    getChildrenById(parent.id, parent.key).then((children: any) => {
      parent.children = [tempNode, ...(children || [])];
      setExpandedKeys([eventKey, ...expandedKeys]);
    });
  };

  const onOperateNode = (eventKey: string, action: string, data?: Record<string, any>) => {
    switch (action) {
      case TestOperation.add:
        onAddNode(eventKey);
        break;
      case TestOperation.copy:
        setCopyOrClipKey(eventKey);
        setEditMode(TestOperation.copy);
        break;
      case TestOperation.clip:
        setCopyOrClipKey(eventKey);
        setEditMode(TestOperation.clip);
        break;
      case TestOperation.paste:
        subSubmitTreeCollection({
          parentID: getEventKeyId(eventKey),
          action: editMode,
          testSetID: getEventKeyId(copyOrClipKey),
        });
        setCopyOrClipKey('');
        setEditMode('');
        break;
      case TestOperation.delete:
        onMoveToRecycled(eventKey);
        break;
      case TestOperation.recover:
        onRecoverFromRecycled(eventKey, data as TEST_SET.RecoverQuery);
        break;
      case TestOperation.deleteEntirely:
        onRemoveNode(eventKey);
        break;
      default:
        break;
    }
  };

  // 移除测试集
  const onRemoveNode = (eventKey: string) => {
    const id = eventKey.split('-').reverse()[0];
    const parent = getNodeByPath({ treeData, eventKey: eventKey.replace(`-${id}`, '') });
    remove(parent.children, ({ key }) => key === eventKey);
    parent.children = [...parent.children];
    setTreeData([...treeData]);
    if (parent) {
      // 节点移动/还原/删除/彻底删除时,选中父级节点,以解决面包屑/用例列表的更新问题
      onSelect([parent.key]);
    }
  };

  // 更新测试集信息,比如新建测试集后
  const onUpdateNode = (eventKey: string, newId: number, newName: string) => {
    const id = eventKey.split('-').reverse()[0];
    const current = getNodeByPath({ treeData, eventKey });
    current.id = newId;
    current.key = eventKey.replace(`-${id}`, `-${newId}`);
    current.title = newName;
    current.recycled = false;
    setTreeData([...treeData]);
  };

  // 移动到回收站
  const onMoveToRecycled = (eventKey: string) => {
    fetchData(recycledId, recycledKey, true);
    remove(expandedKeys, (key) => key === eventKey);
    onRemoveNode(eventKey);
  };

  // 从回收站还原
  const onRecoverFromRecycled = (eventKey: string, { recoverToTestSetID }: TEST_SET.RecoverQuery) => {
    // 获取恢复至的节点
    const targetNode = getNodeById({ treeData, id: recoverToTestSetID, recycled: false }) as TEST_SET.TestSetNode;
    if (isEmpty(targetNode) || !includes(expandedKeys, targetNode.key)) {
      // 如果父级没有展示出来,或者没有展开过,那么此时无需更新父级节点
      onRemoveNode(eventKey);
      return;
    }
    // 还原后前端自动插入改为请求后端数据
    fetchData(recoverToTestSetID, targetNode.key, false);
    onRemoveNode(eventKey);
  };

  const onExpand = (nextExpandedKeys: string[], { nativeEvent }: any) => {
    setHasExpand(true);
    let eventPath = nativeEvent.path;
    // In Firefox, MouseEvent hasn't path and needs to be hacked, bubbling from the target to the root node
    if (!eventPath) {
      eventPath = [];
      let currentEle = nativeEvent.target;
      while (currentEle) {
        eventPath.push(currentEle);
        currentEle = currentEle.parentElement;
      }
      if (!(eventPath.includes(window) || eventPath.includes(document))) {
        eventPath.push(document);
      }
      if (!eventPath.includes(window)) {
        eventPath.push(window);
      }
    }

    nativeEvent.stopPropagation();
    remove(nextExpandedKeys, (key) => includes(key, TEMP_MARK));
    setExpandedKeys(nextExpandedKeys);
  };

  const onSelect = (selectedKeys: string[], _extra?: any) => {
    // extra && extra.nativeEvent.stopPropagation();
    const eventKey = selectedKeys[selectedKeys.length - 1];

    const isRecycledNode = eventKey === recycledKey;
    const current = getNodeByPath({ treeData, eventKey }) as TEST_SET.TestSetNode;
    const recycled = current && (current.recycled as boolean);
    const testSetID = (isRecycledNode ? 0 : current && current.id) || rootId;
    if (oldOnSelect) {
      // 复制、移动时,弹框的选中
      if (eventKey) {
        oldOnSelect({ testSetID, parentID: current.parentID, recycled });
      } else {
        oldOnSelect();
      }
    }
    if (eventKey) {
      setActiveKey(eventKey);
    }
    resetChoosenAll({ isAll: false, scope: mode });
    // 1、取消选中时 2、无需面包屑时 3、点击新建的测试集时
    if (!eventKey || !needBreadcrumb || includes(eventKey, TEMP_MARK)) {
      return;
    }
    const list = eventKey.split('-');
    let pathName = '';
    reduce(
      list,
      (oldKey: string, tempKey: string) => {
        const newKey = oldKey ? `${oldKey}-${tempKey}` : tempKey;
        const currentTitle = getNodeByPath({ treeData, eventKey: newKey, valueKey: 'title' });
        pathName += pathName ? `/${currentTitle}` : currentTitle;
        return newKey;
      },
      '',
    );
    // 新建case时用到
    updateBreadcrumb({
      pathName,
      testSetID,
      testPlanID,
    });

    // 页面刚刚进来时保持当前 query 不进行更新
    if (!_extra?.keepCurrentSearch) {
      updateSearch({ pageNo: 1, recycled, testSetID, eventKey });
    }

    getCases({ testSetID, pageNo: 1, testPlanID, recycled, scope: mode });
  };

  return (
    <DirectoryTree
      className="case-tree"
      blockNode
      loadData={loadData}
      onExpand={onExpand}
      expandedKeys={expandedKeys}
      onSelect={onSelect}
      loadedKeys={expandedKeys}
      selectedKeys={[activeKey]}
    >
      {renderTreeNodes(treeData)}
    </DirectoryTree>
  );
}
Example #19
Source File: test-case.ts    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
testCaseStore = createStore({
  name: 'testCase',
  state: initState,
  effects: {
    async getCaseDetail(
      { call, update, getParams },
      payload: Merge<TEST_CASE.QueryCaseDetail, { scope: 'testPlan' | 'testCase' }>,
    ) {
      const { testPlanId } = getParams();
      let issueBugs: TEST_CASE.RelatedBug[] = [];
      let caseDetail = {} as TEST_CASE.CaseDetail;
      if (payload.scope === 'testPlan') {
        const {
          issueBugs: relationBugs,
          execStatus,
          apiCount,
          testCaseID,
          id,
        } = await call(getDetailRelations, {
          ...payload,
          testPlanID: testPlanId,
        });
        const detail = await call(getDetail, { id: testCaseID, testPlanID: testPlanId });
        caseDetail = {
          ...detail,
          id,
          planRelationCaseID: id,
          testCaseID,
          execStatus,
          apiCount,
        };
        issueBugs = relationBugs;
      } else {
        caseDetail = await call(getDetail, { ...payload, testPlanID: testPlanId });
        caseDetail = {
          testCaseID: caseDetail.id,
          ...caseDetail,
        };
      }
      update({ caseDetail, issueBugs });
      return caseDetail;
    },
    async getDetailRelations({ call, update, getParams }, payload: TEST_CASE.QueryCaseDetail) {
      const { testPlanId } = getParams();
      const { issueBugs } = await call(getDetailRelations, { ...payload, testPlanID: testPlanId });
      update({ issueBugs });
    },
    async editPartial({ call, getParams }, detailCase: TEST_CASE.CaseBody) {
      const { projectId: projectID } = getParams();
      await call(editPartial, { ...detailCase, projectID });
    },
    async getFields({ call }) {
      const fields = await call(getFields);
      testCaseStore.reducers.dealFields(fields);
    },
    async exportFile({ call }, fileType: TEST_CASE.CaseFileType) {
      const { testCaseIDs, ...rest } = await testCaseStore.effects.getSelectedCaseIds();
      const query = routeInfoStore.getState((s) => s.query);
      const temp = { recycled: query.recycled === 'true' };
      const exportQuery = formatQuery({
        ...rest,
        ...temp,
        fileType,
        testCaseID: testCaseIDs,
      }) as any as TEST_CASE.ExportFileQuery;
      return call(exportFileInTestCase, exportQuery);
    },
    async importTestCase({ call, getParams, getQuery }, payload: { file: any }) {
      const { projectId: projectID } = getParams();
      const { testSetID } = getQuery();
      const { file } = payload;
      let fileType: TEST_CASE.CaseFileType = 'excel';
      let reloadTestSetInfo = { isMove: false, testSetID: 0, parentID: 0, projectID } as IReloadTestSetInfo;
      if (regRules.xmind.pattern.test(file.name)) {
        reloadTestSetInfo = { ...reloadTestSetInfo, parentID: testSetID, testSetID };
        fileType = 'xmind';
      }
      const formData = convertToFormData({ file });
      const res = await call(importFileInTestCase, { payload: formData, query: { testSetID, projectID, fileType } });
      testCaseStore.effects.getCases();
      testSetStore.reducers.updateReloadTestSet(reloadTestSetInfo);
      return res;
    },
    async importAutoTestCase({ call, getParams }, payload: { file: File }) {
      const { projectId: projectID } = getParams();
      const { file } = payload;
      const fileType: TEST_CASE.CaseFileType = 'excel';
      const query = { projectID, fileType };
      const formData = convertToFormData({ file });
      const res = await call(importFileInAutoTestCase, { payload: formData, query });
      return res;
    },
    async importAutoTestCaseSet({ call, getParams }, payload: { file: File }) {
      const { projectId: projectID, spaceId: spaceID } = getParams();
      const { file } = payload;
      const fileType: TEST_CASE.CaseFileType = 'excel';
      const query = { projectID, fileType, spaceID };
      const formData = convertToFormData({ file });
      const res = await call(importFileInAutoTestCaseSet, { payload: formData, query });
      return res;
    },
    async create({ call, getParams }, payload: any) {
      const { projectId: strProjectId } = getParams();
      const {
        breadcrumbInfo: { testSetID, testPlanID },
      } = testSetStore.getState((s) => s);
      const projectID = parseInt(strProjectId, 10);
      const res = await call(
        create,
        {
          ...payload,
          testSetID: testSetID || 0,
          projectID,
          recycled: false,
        },
        testPlanID,
        { successMsg: i18n.t('created successfully') },
      );
      return res;
    },
    async getSelectedCaseIds({ getParams, select }, mode?: TEST_CASE.PageScope): Promise<ISelected> {
      // 用例列表中选中的测试用例
      const { projectId: projectID } = getParams();
      const testCase = select((s) => s);
      const testSet = testSetStore.getState((s) => s);
      const routeInfo = routeInfoStore.getState((s) => s);
      const { isAll, exclude, primaryKeys } = testCase[getChoosenName(mode)];
      const { testSetID } = testSet.breadcrumbInfo;
      if (mode === 'caseModal') {
        if (isAll) {
          return { exclude, testCaseIDs: primaryKeys };
        }
        return { testCaseIDs: primaryKeys };
      }
      if (isAll) {
        return { ...routeInfo.query, testSetID, projectID, testCaseIDs: primaryKeys, exclude };
      }
      return { ...routeInfo.query, projectID, testCaseIDs: primaryKeys };
    },
    async changeExecutionResult(_, status: string) {
      const { testCaseIDs: relationIDs } = await testCaseStore.effects.getSelectedCaseIds();
      const payload: Omit<TEST_PLAN.PlanBatch, 'testPlanID'> = { execStatus: status, relationIDs };
      testPlanStore.effects.updateCasesStatus(payload);
    },
    async moveCase({ call }, payload: Omit<TEST_CASE.BatchUpdate, 'priority'>) {
      const res = await call(batchUpdateCase, payload);
      testCaseStore.reducers.removeChoosenIds(payload.testCaseIDs);
      message.success(i18n.t('dop:update completed'));
      testCaseStore.effects.getCases();
      return res;
    },
    // 更新优先级
    async updatePriority({ call, select }, priority: TEST_CASE.Priority) {
      const { primaryKeys } = select((s) => s.choosenInfo);
      const payload: Omit<TEST_CASE.BatchUpdate, 'recycled' | 'moveToTestSetID'> = {
        testCaseIDs: primaryKeys,
        priority,
      };
      const res = await call(batchUpdateCase, payload);
      testCaseStore.reducers.removeChoosenIds(payload.testCaseIDs);
      message.success(i18n.t('dop:update completed'));
      testCaseStore.effects.getCases();
      return res;
    },
    // 移至回收站
    async toggleToRecycle({ call }, payload: Omit<TEST_CASE.BatchUpdate, 'priority'>) {
      const res = await call(batchUpdateCase, payload);
      testCaseStore.reducers.removeChoosenIds(payload.testCaseIDs);
      message.success(i18n.t('deleted successfully'));
      testCaseStore.effects.getCases();
      return res;
    },
    async deleteEntirely({ call }, id?: number) {
      let tempIds = [];
      if (id) {
        // 单条
        tempIds = [id];
        await call(deleteEntirely, { testCaseIDs: tempIds });
      } else {
        // 批量
        const newQuery = await testCaseStore.effects.getSelectedCaseIds();
        tempIds = newQuery.testCaseIDs || [];
        await call(deleteEntirely, { testCaseIDs: tempIds });
      }
      testCaseStore.reducers.removeChoosenIds(tempIds);
      message.success(i18n.t('deleted successfully'));
      testCaseStore.effects.getCases();
    },
    async emptyListByTestSetId({ update }, targetTestSetId: number) {
      const { testSetID } = testSetStore.getState((s) => s.breadcrumbInfo);
      if (targetTestSetId !== testSetID) {
        return;
      }
      update({ caseList: [], caseTotal: 0 });
      testCaseStore.reducers.triggerChoosenAll({ isAll: false, scope: 'testCase' });
    },
    async updateCases({ call }, { query, payload }: { query: TEST_CASE.CaseFilter; payload: TEST_CASE.CaseBodyPart }) {
      await call(updateCases, { query, payload });
      message.success(i18n.t('updated successfully'));
      testCaseStore.effects.getCases();
    },
    async copyCases({ call, getParams }, payload: Omit<TEST_CASE.BatchCopy, 'projectID'>) {
      const { projectId } = getParams();
      await call(copyCases, { ...payload, projectID: +projectId });
      message.success(i18n.t('copied successfully'));
      testCaseStore.effects.getCases();
    },
    async getCases(
      { call, select, update, getParams },
      payload?: Merge<TEST_CASE.QueryCase, { scope: TEST_CASE.PageScope }>,
    ) {
      const { scope, ...rest } = payload || ({} as Merge<TEST_CASE.QueryCase, { scope: TEST_CASE.PageScope }>);
      const breadcrumbInfo = testSetStore.getState((s) => s.breadcrumbInfo);
      const oldQuery = select((s) => s.oldQuery);
      const query = routeInfoStore.getState((s) => s.query);
      const { projectId: projectID, testPlanId } = getParams();
      let { testSetID } = breadcrumbInfo;
      // 先取传过来的,再取url上的
      if (rest.testSetID !== undefined) {
        testSetID = rest.testSetID;
      } else if (query.testSetID !== undefined) {
        testSetID = query.testSetID;
      }
      // 1、当筛选器、表格的page、sorter发生变更时
      // 2、及时性的筛选信息
      if (scope === 'caseModal') {
        // 如果是在计划详情中的用例弹框时,传入的参数覆盖url上的参数
        const newQuery = { ...query, ...rest, testSetID, projectID, query: undefined }; // Set the query outside the modal to undefined, prevent to filter modal data
        const { testSets, total } = await call(getCases, formatQuery({ pageSize: 15, ...newQuery }));
        update({ modalCaseList: testSets, modalCaseTotal: total });
        testCaseStore.reducers.triggerChoosenAll({ isAll: false, scope });
        return;
      }
      const fetchData = testPlanId ? getCasesRelations : getCases;
      const newQuery = { ...rest, testSetID, ...query, projectID, testPlanID: testPlanId };
      const { testSets, total } = await call(fetchData, formatQuery({ pageSize: 15, ...newQuery }));
      update({ caseList: testSets, caseTotal: total, oldQuery: newQuery });
      if (checkNeedEmptyChoosenIds(newQuery, oldQuery)) {
        testCaseStore.reducers.triggerChoosenAll({ isAll: false, scope });
      }
    },
    async attemptTestApi({ call }, payload: TEST_CASE.TestApi) {
      const result = await call(attemptTestApi, payload);
      return result;
    },
    async removeRelation({ call, getParams }, payload: Omit<TEST_CASE.RemoveRelation, 'testPlanID'>) {
      const { testPlanId } = getParams();
      const res = await call(
        removeRelation,
        { ...payload, testPlanID: testPlanId },
        { successMsg: i18n.t('dop:disassociated successfully') },
      );
      return res;
    },
    async addRelation({ call, getParams }, payload: Omit<TEST_CASE.AddRelation, 'testPlanID'>) {
      const { testPlanId } = getParams();
      const res = await call(
        addRelation,
        { ...payload, testPlanID: testPlanId },
        { successMsg: i18n.t('dop:associated successfully') },
      );
      return res;
    },
    async getImportExportRecords({ call, getParams }, payload: { types: TEST_CASE.ImportOrExport[] }) {
      const { projectId } = getParams();
      const res = await call(getImportExportRecords, { projectId, ...payload });
      return res;
    },
  },
  reducers: {
    openNormalModal(state, caseAction) {
      state.caseAction = caseAction;
    },
    closeNormalModal(state) {
      state.caseAction = '';
    },
    triggerChoosenAll(state, { isAll, scope }: { isAll: boolean; scope: TEST_CASE.PageScope }) {
      if (scope === 'temp') {
        return;
      }
      const keyName = getChoosenName(scope);
      const listName = getCaseListName(scope);
      let nextIsAll = isAll;
      if (!isBoolean(isAll)) {
        nextIsAll = !state[keyName].isAll;
      }
      let testCaseIDs: number[] = [];
      if (nextIsAll) {
        testCaseIDs = flatMapDeep(state[listName], ({ testCases }) => flatMapDeep(testCases, 'id'));
      }
      state[keyName] = { isAll: nextIsAll, exclude: [], primaryKeys: nextIsAll ? testCaseIDs : [] };
    },
    removeChoosenIds(state, ids) {
      if (!ids) {
        return;
      }
      const { primaryKeys } = state.choosenInfo;
      remove(primaryKeys, (caseId) => includes(ids, caseId));
      state.choosenInfo = { ...state.choosenInfo, isAll: false, primaryKeys };
    },
    clearChoosenInfo(state, { mode }: { mode: TEST_CASE.PageScope }) {
      const keyName = getChoosenName(mode);
      state[keyName] = {
        isAll: false,
        primaryKeys: [],
        exclude: [],
      };
    },
    updateChoosenInfo(state, { id, mode, checked }) {
      const keyName = getChoosenName(mode);
      const { isAll, primaryKeys } = state[keyName];
      const copy = [...primaryKeys];
      let nextIsAll = isAll;
      if (checked) {
        const listName = getCaseListName(mode);
        const caseCount = flatMapDeep(state[listName], ({ testCases }) => flatMapDeep(testCases, 'id')).length;
        copy.push(id);
        nextIsAll = copy.length === caseCount;
      } else {
        nextIsAll = false;
        if (includes(copy, id)) {
          // 已经选中时
          remove(copy, (caseId) => caseId === id);
        }
      }
      state[keyName] = { isAll: nextIsAll, exclude: [], primaryKeys: [...copy] };
    },
    resetStore() {
      return initState;
    },
    toggleCasePanel(state, visible) {
      state.currCase = { ...state.currCase, visible };
      state.currDetailCase = { ...state.currDetailCase, visible: false };
    },
    toggleDetailPanel(state, { visible, detailCase }) {
      state.currDetailCase = { visible, case: { ...state.currDetailCase.case, ...detailCase } };
      state.currCase = { ...state.currCase, visible: false };
    },
    setCurrCase(state, currCase) {
      state.currCase = { ...state.currCase, case: { ...currCase } };
    },
    setDetailCase(state, detailCase) {
      state.currDetailCase = { ...state.currDetailCase, case: { ...detailCase } };
    },
    dealFields(state, fields) {
      let fieldsVal = [...DEFAULT_FIELDS];
      fieldsVal = map(fieldsVal, (defaultField) => {
        const metaField = find(fields, (filedItem) => filedItem.uniqueName === defaultField.uniqueName);
        if (metaField) {
          return metaField;
        }
        return defaultField;
      });
      state.fields = fieldsVal;
      state.metaFields = fields;
    },
    dealDetail(state, { payload }) {
      const { detailCase } = payload;
      const { attachmentObjects: attachments } = detailCase;
      attachments.map((value: any) => {
        if (!isImage(value.name)) {
          // eslint-disable-next-line no-param-reassign
          value.thumbUrl = defaultFileTypeImg;
        }
        extend(value, {
          uid: value.id,
          name: value.name,
          status: 'done',
          url: value.url,
          response: [{ ...value }],
          thumbUrl: value.thumbUrl || undefined,
        });
        return value;
      });
      detailCase.attachments = attachments;
      const desc = detailCase.desc && detailCase.desc !== '<p></p>' ? detailCase.desc : '';
      const preCondition =
        detailCase.preCondition && detailCase.preCondition !== '<p></p>' ? detailCase.preCondition : '';
      detailCase.desc = desc;
      detailCase.preCondition = preCondition;
      state.currDetailCase = {
        ...state.currDetailCase,
        case: { ...detailCase, descIssues: detailCase.descIssues || [] },
      };
      state.oldBugIds = detailCase.bugs && detailCase.bugs.map(({ id }: any) => id);
    },
    clearCurrCase(state) {
      state.currCase = tempNewCase;
    },
    clearCurrDetailCase(state) {
      state.currDetailCase = tempNewCase;
    },
    clearCaseDetail(state) {
      state.caseDetail = {} as TEST_CASE.CaseDetail;
    },
  },
})
Example #20
Source File: contextMenuItems.ts    From gant-design with MIT License 4 votes vote down vote up
gantGetcontextMenuItems = function(
  params: GetContextMenuItemsParams,
  config: ContextMenuItemsConfig,
) {
  const {
    downShift,
    locale,
    onRowsCut,
    onRowsPaste,
    getContextMenuItems,
    defaultJsonParams = {},
    hideMenuItemExport,
    hideMenuItemExpand,
    hiddenMenuItemNames,
    suppressRightClickSelected,
    showCutChild,
  } = config;
  const {
    context: {
      globalEditable,
      treeData,
      createConfig,
      getRowNodeId,
      gridManager,
      showCut,
      rowSelection,
    },
    node,
    api,
  } = params;
  const exportJson = !isEmpty(defaultJsonParams);
  const rowIndex = get(node, 'rowIndex', 0);
  let selectedRowNodes: RowNode[] = api.getSelectedNodes();
  //右键选中⌚️
  if (node && !suppressRightClickSelected) {
    const rowNodes = api.getSelectedNodes();
    if (!downShift || rowNodes.length == 0) {
      node.setSelected(true, true);
      selectedRowNodes = [node];
    } else {
      const rowNodeIndexs = rowNodes.map(rowNode => rowNode.rowIndex);
      const maxIndex = max(rowNodeIndexs);
      const minIndex = min(rowNodeIndexs);
      if (rowIndex >= minIndex && rowIndex <= maxIndex) {
        node.setSelected(true, true);
        selectedRowNodes = [node];
      } else {
        const isMin = rowIndex < minIndex;
        const nodesCount = isMin ? minIndex - rowIndex : rowIndex - maxIndex;
        const startIndex = isMin ? rowIndex : maxIndex + 1;
        const extraNodes = Array(nodesCount)
          .fill('')
          .map((item, index) => {
            const startNode = api.getDisplayedRowAtIndex(index + startIndex);
            startNode.setSelected(true);
            return startNode;
          });
        selectedRowNodes = isMin ? [...extraNodes, ...rowNodes] : [...rowNodes, ...extraNodes];
      }
    }
  }
  const gridSelectedKeys: string[] = [];
  const gridSelectedRows = selectedRowNodes.map(item => {
    gridSelectedKeys.push(getRowNodeId(get(item, 'data', {})));
    return item.data;
  }, []);
  const disabledCut = selectedRowNodes.length <= 0 || (treeData && isEmpty(createConfig));
  const hasPaste =
    selectedRowNodes.length > 1 ||
    (treeData && isEmpty(createConfig)) ||
    isEmpty(gridManager.cutRows);
  let items = getContextMenuItems
    ? getContextMenuItems({
        selectedRows: gridSelectedRows,
        selectedKeys: gridSelectedKeys,
        selectedRowKeys: gridSelectedKeys,
        ...params,
      } as any)
    : [];

  if (hiddenMenuItemNames && hiddenMenuItemNames.length) {
    remove(items, menuItem => hiddenMenuItemNames.some(menuName => menuName === menuItem.name));
  }

  let defultMenu = [];
  if (treeData && !hideMenuItemExpand) {
    defultMenu = ['expandAll', 'contractAll'];
  }
  defultMenu =
    defultMenu.length > 0
      ? items.length > 0
        ? [...defultMenu, ...items]
        : defultMenu
      : [...items];
  if (!hideMenuItemExport) {
    defultMenu = defultMenu.length > 0 ? [...defultMenu, 'export'] : ['export'];
    if (suppressRightClickSelected) {
      defultMenu.push({
        name: locale.exportSelected,
        icon: '<span class="ag-icon ag-icon-save" unselectable="on" role="presentation"></span>',
        action: () => {
          api.exportDataAsExcel({
            onlySelected: true,
          });
        },
      });
    }
  }

  defultMenu = exportJson
    ? [
        ...defultMenu,
        {
          name: locale.exportJson,
          action: () => {
            const { title = 'gantdGrid', onlySelected } = defaultJsonParams;
            let data = [];
            if (onlySelected) {
              data = api.getSelectedRows();
            } else {
              api.forEachNode(node => {
                if (node.data) data.push(node.data);
              });
            }
            const jsonBlob = new Blob([JSON.stringify(data)], {
              type: 'text/plain;charset=utf-8',
            });
            FileSaver.saveAs(jsonBlob, `${title}.json`);
          },
        },
      ]
    : defultMenu;

  if (!globalEditable) return defultMenu;

  defultMenu = exportJson
    ? [
        ...defultMenu,
        {
          name: locale.importJson,
          action: () => {
            const { coverData } = defaultJsonParams;
            const input = document.createElement('input');
            input.type = 'file';
            input.accept = 'application/json';
            input.onchange = (event: any) => {
              const [file] = event.target.files;
              const reader = new FileReader();
              reader.readAsText(file);
              reader.onload = function(event: any) {
                try {
                  const update = [],
                    add = [];
                  const json = JSON.parse(event.target.result);
                  if (coverData) {
                    api.setRowData(json);
                    gridManager.reset();
                    return;
                  }
                  json.map((itemData: any) => {
                    const rowNode = api.getRowNode(getRowNodeId(itemData));
                    if (rowNode && rowNode.data) {
                      update.push({ ...itemData, ...rowNode.data });
                    } else add.push(itemData);
                  });
                  api.applyTransactionAsync({ update }, () => {
                    gridManager.create(add);
                  });
                } catch (error) {}
              };
            };
            input.click();
          },
        },
      ]
    : defultMenu;
  const showCutBtns = typeof showCut === 'function' ? showCut(params) : showCut;

  const editMenu = [...defultMenu];
  if (showCutBtns) {
    editMenu.push(
      ...[
        {
          name: locale.cutRows,
          disabled: disabledCut,
          action: params => {
            try {
              const canPut = onRowsCut ? onRowsCut(selectedRowNodes) : true;
              return canPut && gridManager.cut(selectedRowNodes);
            } catch (error) {}
          },
        },
        {
          name: locale.cancelCut,
          disabled: isEmpty(gridManager.cutRows),
          action: params => {
            try {
              gridManager.cancelCut();
            } catch (error) {}
          },
        },
        {
          name: locale.pasteTop,
          disabled: hasPaste,
          action: params => {
            const [rowNode] = selectedRowNodes;
            const canPaste = onRowsPaste ? onRowsPaste(gridManager.cutRows, rowNode) : true;
            canPaste && gridManager.paste(rowNode);
          },
        },
        {
          name: locale.pasteBottom,
          disabled: hasPaste,
          action: params => {
            const [rowNode] = selectedRowNodes;
            const canPaste = onRowsPaste ? onRowsPaste(gridManager.cutRows, rowNode) : true;
            canPaste && gridManager.paste(rowNode, false);
          },
        },
      ],
    );
    if (showCutChild)
      editMenu.push({
        name: locale.pasteChild,
        disabled: hasPaste,
        action: params => {
          const [rowNode] = selectedRowNodes;
          const canPaste = onRowsPaste ? onRowsPaste(gridManager.cutRows, rowNode) : true;
          canPaste && gridManager.paste(rowNode, false, true);
        },
      });
  }
  return editMenu;
}
Example #21
Source File: EntityPermissionFields.tsx    From amplication with Apache License 2.0 4 votes vote down vote up
EntityPermissionFields = ({
  actionName,
  actionDisplayName,
  entityId,
  permission,
}: Props) => {
  const pendingChangesContext = useContext(PendingChangesContext);

  const selectedFieldIds = useMemo((): Set<string> => {
    return new Set(permission.permissionFields?.map((field) => field.field.id));
  }, [permission.permissionFields]);

  const { data } = useQuery<TData>(GET_FIELDS, {
    variables: {
      id: entityId,
      orderBy: undefined /**@todo: implement orderBy position */,
      whereName: undefined,
    },
  });

  /**@todo: handle  errors */
  const [addField] = useMutation(ADD_FIELD, {
    onCompleted: (data) => {
      pendingChangesContext.addEntity(entityId);
    },
    update(cache, { data: { addEntityPermissionField } }) {
      const queryData = cache.readQuery<{
        entity: models.Entity;
      }>({
        query: GET_ENTITY_PERMISSIONS,
        variables: {
          id: entityId,
        },
      });
      if (queryData === null || !queryData.entity.permissions) {
        return;
      }
      const clonedQueryData = {
        entity: cloneDeep(queryData.entity),
      };

      const actionData = clonedQueryData.entity.permissions?.find(
        (p) => p.action === actionName
      );
      if (!actionData) {
        return;
      }

      actionData.permissionFields = actionData?.permissionFields?.concat([
        addEntityPermissionField,
      ]);

      cache.writeQuery({
        query: GET_ENTITY_PERMISSIONS,
        variables: {
          id: entityId,
        },
        data: {
          entity: {
            ...clonedQueryData.entity,
          },
        },
      });
    },
  });

  /**@todo: handle  errors */
  const [deleteField] = useMutation(DELETE_FIELD, {
    onCompleted: (data) => {
      pendingChangesContext.addEntity(entityId);
    },
    update(cache, { data: { deleteEntityPermissionField } }) {
      const queryData = cache.readQuery<{
        entity: models.Entity;
      }>({
        query: GET_ENTITY_PERMISSIONS,
        variables: {
          id: entityId,
        },
      });
      if (queryData === null || !queryData.entity.permissions) {
        return;
      }
      const clonedQueryData = {
        entity: cloneDeep(queryData.entity),
      };

      const actionData = clonedQueryData.entity.permissions?.find(
        (p) => p.action === actionName
      );
      if (!actionData || !actionData.permissionFields) {
        return;
      }

      remove(
        actionData.permissionFields,
        (field) =>
          field.fieldPermanentId ===
          deleteEntityPermissionField.fieldPermanentId
      );

      cache.writeQuery({
        query: GET_ENTITY_PERMISSIONS,
        variables: {
          id: entityId,
        },
        data: {
          entity: {
            ...clonedQueryData.entity,
          },
        },
      });
    },
  });

  const handleFieldSelected = useCallback(
    ({ fieldName }) => {
      addField({
        variables: {
          fieldName: fieldName,
          entityId: entityId,
          action: actionName,
        },
      }).catch(console.error);
    },
    [addField, entityId, actionName]
  );

  const handleDeleteField = useCallback(
    (fieldPermanentId) => {
      deleteField({
        variables: {
          fieldPermanentId: fieldPermanentId,
          entityId: entityId,
          action: actionName,
        },
      }).catch(console.error);
    },
    [deleteField, entityId, actionName]
  );

  return (
    <div className={CLASS_NAME}>
      <div className={`${CLASS_NAME}__add-field`}>
        Set specific permissions to special fields
        <SelectMenu
          title="Add Field"
          icon="plus"
          buttonStyle={EnumButtonStyle.Secondary}
        >
          <SelectMenuModal>
            <SelectMenuList>
              {data?.entity?.fields?.map((field) => (
                <SelectMenuItem
                  key={field.id}
                  selected={selectedFieldIds.has(field.id)}
                  onSelectionChange={handleFieldSelected}
                  itemData={{
                    fieldName: field.name,
                  }}
                >
                  {field.displayName}
                </SelectMenuItem>
              ))}
            </SelectMenuList>
          </SelectMenuModal>
        </SelectMenu>
      </div>
      {permission.permissionFields?.map((field) => (
        <EntityPermissionField
          key={field.id}
          entityId={entityId}
          permission={permission}
          actionDisplayName={actionDisplayName}
          permissionField={field}
          onDeleteField={handleDeleteField}
        />
      ))}
    </div>
  );
}
Example #22
Source File: preset.ts    From ovineherd with Apache License 2.0 4 votes vote down vote up
getAppPageApis = () => {
  let cacheListNav = []

  const appId = getAppId()

  const listNav = {
    url: 'GET /v1/option/category',
    domain: 'api',
    data: {
      type: relation.app.nav.type,
      parent_id: 0,
      q_relation1: appId,
      '&': '$$',
    },
    onSuccess: (source) => {
      const { option } = source.data
      source.data = get(option, '0') || { items: [] }
      cacheListNav = source.data.items
      return source
    },
  }

  const navParent = {
    url: 'fakeNavParent',
    domain: 'api',
    // cache: 500,
    data: {
      nav_id: '$id',
    },
    onFakeRequest: (option) => {
      const { nav_id } = option.data
      let appNav = cacheListNav
      if (option.api !== 'fakeNavParent') {
        appNav = cloneDeep(cacheListNav)
        appNav.unshift({
          label: '主目录',
          id: '0',
        })
      }

      const options = !nav_id ? appNav : filterTree(appNav, (i) => i.id !== nav_id)
      const source = {
        status: 0,
        data: {
          options,
        },
      }

      return source
    },
  }

  const addNav = getReqOption(
    {
      ...relation.app.nav,
      apiName: ApiName.add,
      relation1: appId,
      '&': '$$',
    },
    {
      onPreRequest: async (option) => {
        const { parent_id, page_type } = option.data
        const { id } = await requestByOption({
          ...relation.app.page,
          apiName: ApiName.add,
        })
        option.data.page_id = id
        option.data.parent_id = parent_id || '0'

        if (page_type === '1') {
          option.data.limit_str = JSON.stringify(defLimitInfo)
        }

        return option
      },
    }
  )

  const getHome = {
    url: 'fakeGetHome',
    onFakeRequest: () => {
      return {
        data: {
          page_id: getHomePageId(),
        },
      }
    },
  }

  const setHome = getReqOption(
    {
      apiType: relation.app.appInfo.apiType,
      apiName: ApiName.edit,
      id: getAppInfo().id,
      app_home_page_id: '$page_id',
    },
    {
      onSuccess: (source, option) => {
        const { app_home_page_id = '' } = option.data
        setHomePageId(app_home_page_id)
        return source
      },
    }
  )

  const editNav = getReqOption({
    apiType: relation.app.nav.apiType,
    apiName: ApiName.edit,
  })

  const delNav = getReqOption({
    apiType: relation.app.nav.apiType,
    apiName: ApiName.del,
    with_root: true,
    '&': '$$',
  })

  const orderNav = {
    url: 'PUT fakeListLimit',
    onFakeRequest: () => {
      return {
        status: 0,
        msg: '成功',
      }
    },
  }

  const checkLimitData = (data: any): any => {
    if (!data.key || !data.label) {
      return {
        status: -1,
        msg: '请填写KEY与名称',
      }
    }
    if (!data.id) {
      if (limitStore.list.find((i) => i.key === data.key)) {
        return {
          status: -1,
          msg: '权限 KEY 不能重复',
        }
      }
    }

    return ''
  }

  const limitCtrl = async (type: string): Promise<any> => {
    const reqOpt = {
      id: limitStore.id,
      apiType: ApiType.category,
    }
    const getLimitStr = () => {
      const limit = pick(defLimitInfo, ['$page'])
      limitStore.list.forEach((i) => {
        const { label, needs, desc } = i
        const item: any = { label, desc }
        if (isArray(needs)) {
          item.needs = needs
            .map(({ value: id }) => {
              const { key } = find(limitStore.list, { id }) || {}
              return key
            })
            .filter(isString)
        }
        limit[i.key] = item
      })
      return JSON.stringify(limit)
    }

    switch (type) {
      case 'get': {
        const { limit_str } = await requestByOption({
          ...reqOpt,
          apiName: ApiName.one,
          onlyData: true,
        })
        return limit_str
      }
      case 'save': {
        const saveRes = await requestByOption({
          ...reqOpt,
          apiName: ApiName.edit,
          limit_str: getLimitStr(),
        })
        return saveRes
      }

      default:
        return ''
    }
  }

  const listLimit = {
    url: 'fakeListLimit',
    onFakeRequest: async (option) => {
      const { id } = option.rawData
      limitStore.id = id
      limitStore.list = []
      const limitStr = await limitCtrl('get')
      if (limitStr) {
        const limitObj = JSON.parse(limitStr)
        map(limitObj, (val, key) => {
          if (key !== '$page') {
            limitStore.list.push({
              id: uniqueId(),
              key,
              ...val,
            })
          }
        })
        limitStore.list.forEach((i) => {
          const { needs = [] } = i
          i.needs = !isArray(needs)
            ? []
            : needs.map((n) => {
                const { label, id: limitId } = find(limitStore.list, { key: n }) || {}
                return { label, value: limitId }
              })
        })
      }
      return {
        data: {
          limitList: limitStore.list,
        },
      }
    },
  }

  const editLimit = {
    url: 'PUT fakeEditLimit',
    onFakeRequest: async (option) => {
      const { id } = option.data
      const checkRes = checkLimitData(option.data)
      if (checkRes) {
        return checkRes
      }
      const idx = findIndex(limitStore.list, { id })
      if (idx !== -1) {
        limitStore.list[idx] = option.data
      }
      await limitCtrl('save')
      return {
        data: find(limitStore.list, { id }),
      }
    },
  }

  const delLimit = {
    url: 'PUT fakeDelLimit',
    onFakeRequest: async (option) => {
      const { id } = option.data
      remove(limitStore.list, { id })
      await limitCtrl('save')
      return {
        status: 0,
        msg: '已删除',
      }
    },
  }

  const addLimit = {
    url: 'POST fakeAddLimit',
    onFakeRequest: async (option) => {
      const checkRes = checkLimitData(option.data)
      if (checkRes) {
        return checkRes
      }
      limitStore.list.push(option.data)
      await limitCtrl('save')
      return {
        status: 0,
        msg: '保存成功',
      }
    },
  }

  const needsOptions = {
    url: 'GET fakeNeedsList',
    data: {
      id: '$id',
    },
    onFakeRequest: (option) => {
      const { id } = option.data

      const checkNeeded = (checkId: string, needs: any[]) => {
        let isNeeded = false

        const check = (currNeeds: any[]) => {
          if (!currNeeds || !currNeeds.length) {
            return
          }
          if (find(currNeeds, { value: checkId })) {
            isNeeded = true
            return
          }
          currNeeds.forEach((i) => {
            const itemNeeds = get(find(limitStore.list, { id: i.value }), 'needs') || []
            check(itemNeeds)
          })
        }

        check(needs)

        return isNeeded
      }

      const options = limitStore.list
        .filter((i) => {
          if (i.id === id) {
            return false
          }
          return !checkNeeded(id, i.needs)
        })
        .map((i) => {
          return {
            label: i.label,
            value: i.id,
          }
        })

      return {
        data: {
          options,
        },
      }
    },
  }

  const onOrderChange = {
    onChange: (curr, prev) => {
      if (curr.length && curr.length === prev.length) {
        if (curr.map((i) => i.id).join(',') !== prev.map((i) => i.id).join(',')) {
          limitStore.list = curr
          limitCtrl('save')
        }
      }
    },
  }

  const appPageApis = {
    navParent,
    listNav,
    orderNav,
    getHome,
    setHome,
    addNav,
    editNav,
    delNav,
    listLimit,
    editLimit,
    delLimit,
    addLimit,
    onOrderChange,
    needsOptions,
  }

  return appPageApis
}
Example #23
Source File: preset.ts    From ovineherd with Apache License 2.0 4 votes vote down vote up
getSettingApis = () => {
  const appConfId = getAppInfo().id
  let envList = []

  const appInfo = getReqOption({
    apiType: relation.app.appInfo.apiType,
    apiName: ApiName.one,
    id: appConfId,
  })

  const editAppInfo = getReqOption(
    {
      apiType: relation.app.appInfo.apiType,
      apiName: ApiName.edit,
    },
    {
      onSuccess: (source, option) => {
        syncAppInfo(option.data)
        return source
      },
    }
  )

  const checkEnvData = (data: any): any => {
    if (!data.name || !data.value) {
      return {
        status: -1,
        msg: '请填写变量名与变量值',
      }
    }

    if (!data.id) {
      if (envList.find((i) => i.name === data.name)) {
        return {
          status: -1,
          msg: '权限 KEY 不能重复',
        }
      }
    }
    if (!/[_A-Z0-9]/.test(data.name)) {
      return {
        status: -1,
        msg: '[变量名格式不正确] 请使用大写字母作为变量名',
      }
    }

    return ''
  }

  const envCtrl = async (type: string): Promise<any> => {
    const reqOpt = {
      id: appConfId,
      apiType: relation.app.appInfo.apiType,
    }

    switch (type) {
      case 'get': {
        const { app_env_constants } = await requestByOption({
          ...reqOpt,
          apiName: ApiName.one,
          onlyData: true,
        })
        return app_env_constants
      }
      case 'save': {
        setOvineConstants(envList)
        const saveRes = await requestByOption({
          ...reqOpt,
          apiName: ApiName.edit,
          app_env_constants: JSON.stringify(
            envList.map((i) => {
              return pick(i, ['name', 'value', 'desc'])
            })
          ),
        })
        return saveRes
      }

      default:
        return ''
    }
  }

  const listEnv = {
    url: 'fakeListEnv',
    onFakeRequest: async () => {
      envList = []
      const envArr = await envCtrl('get')
      if (envArr) {
        envList = JSON.parse(envArr) || []
        envList.map((i) => {
          i.id = uniqueId()
          return i
        })
      }
      return {
        data: {
          constants: envList,
        },
      }
    },
  }

  const editEnv = {
    url: 'PUT fakeEditEnv',
    onFakeRequest: async (option) => {
      const { id } = option.data
      const checkRes = checkEnvData(option.data)
      if (checkRes) {
        return checkRes
      }
      const idx = findIndex(envList, { id })
      if (idx !== -1) {
        envList[idx] = option.data
      }
      await envCtrl('save')
      return {
        status: 0,
        msg: '修改成功',
      }
    },
  }

  const delEnv = {
    url: 'PUT fakeDelEnv',
    onFakeRequest: async (option) => {
      const { id } = option.data
      remove(envList, { id })
      await envCtrl('save')
      return {
        status: 0,
        msg: '已删除',
      }
    },
  }

  const addEnv = {
    url: 'POST fakeAddEnv',
    onFakeRequest: async (option) => {
      const checkRes = checkEnvData(option.data)
      if (checkRes) {
        return checkRes
      }
      envList.push(option.data)
      await envCtrl('save')
      return {
        status: 0,
        msg: '保存成功',
      }
    },
  }

  const onOrderChange = {
    onChange: (curr, prev) => {
      if (curr.length && curr.length === prev.length) {
        if (curr.map((i) => i.id).join(',') !== prev.map((i) => i.id).join(',')) {
          envList = curr
          envCtrl('save')
        }
      }
    },
  }

  return {
    appInfo,
    editAppInfo,
    listEnv,
    editEnv,
    delEnv,
    addEnv,
    onOrderChange,
  }
}
Example #24
Source File: Optimize.tsx    From sc2-planner with MIT License 4 votes vote down vote up
onAddConstraint(index: number, action: ConstraintType): void {
        let didChangeConstraints = false
        index = index === -1 ? this.props.gamelogic.bo.length - 1 : index
        const name = this.props.gamelogic.bo[index].name
        const pos = filter(this.props.gamelogic.bo.slice(0, index), { name }).length
        const eventLog = filter(this.props.gamelogic.eventLog, { name })[pos]
        const itemEndFrame = eventLog?.end || eventLog?.start
        const endTime = Math.floor(itemEndFrame / 22.4)
        if (endTime === undefined) {
            return
        }
        const list: Constraint[] = getConstraintList(this.props.optimizeSettings)

        if (action === "remove") {
            didChangeConstraints = remove(list, { name, pos }).length > 0
        } else {
            const toRemoveList: Constraint[] = []
            let foundName = false
            for (const constraint of list) {
                if (
                    constraint.type === "time" &&
                    constraint.name === name &&
                    constraint.pos === pos
                ) {
                    if (action === "after" || action === "at") {
                        foundName = true
                        if (constraint.after !== endTime) {
                            didChangeConstraints = true
                            constraint.after = endTime
                        }
                    }
                    if (action === "after" && constraint.before <= endTime) {
                        foundName = true
                        didChangeConstraints = true
                        constraint.before = Infinity
                    }
                    if (action === "before" || action === "at") {
                        foundName = true
                        if (constraint.before !== endTime) {
                            didChangeConstraints = true
                            constraint.before = endTime
                        }
                    }
                    if (action === "before" && constraint.after >= endTime) {
                        foundName = true
                        didChangeConstraints = true
                        constraint.after = -Infinity
                    }
                }
            }
            for (const toRemove of toRemoveList) {
                remove(list, toRemove)
            }
            if (!foundName) {
                didChangeConstraints = true
                const newConstraint: TimeConstraint = {
                    type: "time",
                    name,
                    pos,
                    after: action === "after" || action === "at" ? endTime : -Infinity,
                    before: action === "before" || action === "at" ? endTime : Infinity,
                }
                const whereToInsert = sortedIndexBy(
                    list,
                    newConstraint,
                    (constraint) => `${constraint.name}#${constraint.pos}`
                )
                list.splice(whereToInsert, 0, newConstraint)
            }
        }

        if (didChangeConstraints) {
            const constraints: string = setConstraintList(list)
            this.props.log({
                autoClose: true,
                notice: `New optimize constraints: ${constraints ? constraints : "none!"}`,
                temporary: true,
            })
            this.props.updateOptimize("c", constraints, false)
            this.setState({
                constraintsChangeCount: this.state.constraintsChangeCount + 1,
            })
        }
    }