antd#RadioChangeEvent TypeScript Examples

The following examples show how to use antd#RadioChangeEvent. 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: ExampleCustomScreen.tsx    From jmix-frontend with Apache License 2.0 6 votes vote down vote up
ExampleCustomScreen = () => {
    const mainStore = useMainStore();

    const [contentDisplayMode, setContentDisplayMode] = useState(mainStore.contentDisplayMode);

    const handleContentDisplayModeChange = useCallback((e: RadioChangeEvent) => {
        if (Object.values(ContentDisplayMode).includes(e.target.value)) {
            mainStore.contentDisplayMode = e.target.value;
            setContentDisplayMode(mainStore.contentDisplayMode);
        }
    }, [mainStore, setContentDisplayMode]);

    return (
        <>
            <h2>
                Example Custom Screen
            </h2>
            <Divider/>
            <div>
                <div><code>ContentDisplayMode:</code></div>
                <Radio.Group onChange={handleContentDisplayModeChange} value={contentDisplayMode}>
                    <Space direction='vertical'>
                        <Radio value={ContentDisplayMode.ActivateExistingTab}>
                            <code>ContentDisplayMode.ActivateExistingTab</code>
                        </Radio>
                        <Radio value={ContentDisplayMode.AlwaysNewTab}>
                            <code>ContentDisplayMode.AlwaysNewTab</code>
                        </Radio>
                        <Radio value={ContentDisplayMode.NoTabs}>
                            <code>ContentDisplayMode.NoTabs</code>
                        </Radio>
                    </Space>
                </Radio.Group>
            </div>
        </>
    )
}
Example #2
Source File: ActionStep.tsx    From posthog-foss with MIT License 6 votes vote down vote up
function URLMatching({
    step,
    sendStep,
}: {
    step: ActionStepType
    sendStep: (stepToSend: ActionStepType) => void
}): JSX.Element {
    const handleURLMatchChange = (e: RadioChangeEvent): void => {
        sendStep({ ...step, url_matching: e.target.value })
    }
    return (
        <Radio.Group
            buttonStyle="solid"
            onChange={handleURLMatchChange}
            value={step.url_matching || 'contains'}
            size="small"
        >
            <Radio.Button value="contains">contains</Radio.Button>
            <Radio.Button value="regex">matches regex</Radio.Button>
            <Radio.Button value="exact">matches exactly</Radio.Button>
        </Radio.Group>
    )
}
Example #3
Source File: ActionStep.tsx    From posthog-foss with MIT License 5 votes vote down vote up
function TypeSwitcher({
    step,
    sendStep,
}: {
    step: ActionStepType
    sendStep: (stepToSend: ActionStepType) => void
}): JSX.Element {
    const handleChange = (e: RadioChangeEvent): void => {
        const type = e.target.value
        if (type === '$autocapture') {
            sendStep({ ...step, event: '$autocapture' })
        } else if (type === 'event') {
            sendStep({ ...step, event: '' })
        } else if (type === '$pageview') {
            sendStep({
                ...step,
                event: '$pageview',
                url: step.url,
            })
        }
    }

    return (
        <div className={`type-switcher${step.event === undefined ? ' unselected' : ''}`}>
            <Radio.Group
                buttonStyle="solid"
                onChange={handleChange}
                value={
                    step.event === '$autocapture' || step.event === '$pageview' || step.event === undefined
                        ? step.event
                        : 'event'
                }
            >
                <Radio.Button value="$autocapture">Autocapture</Radio.Button>
                <Radio.Button value="event">Custom event</Radio.Button>
                <Radio.Button value="$pageview">Page view</Radio.Button>
            </Radio.Group>
        </div>
    )
}
Example #4
Source File: index.tsx    From S2 with MIT License 4 votes vote down vote up
function MainLayout() {
  const [render, setRender] = React.useState(true);
  const [sheetType, setSheetType] = React.useState<SheetType>('pivot');
  const [showPagination, setShowPagination] = React.useState(false);
  const [showTotals, setShowTotals] = React.useState(false);
  const [themeCfg, setThemeCfg] = React.useState<ThemeCfg>({
    name: 'default',
  });
  const [themeColor, setThemeColor] = React.useState<string>('#FFF');
  const [showCustomTooltip, setShowCustomTooltip] = React.useState(false);
  const [adaptive, setAdaptive] = React.useState<Adaptive>(false);
  const [options, setOptions] =
    React.useState<Partial<S2Options<React.ReactNode>>>(defaultOptions);
  const [dataCfg, setDataCfg] =
    React.useState<Partial<S2DataConfig>>(pivotSheetDataCfg);
  const [strategyDataCfg, setStrategyDataCfg] =
    React.useState<S2DataConfig>(customTree);
  const [strategyOptions, setStrategyOptions] =
    React.useState<S2Options>(mockStrategyOptions);
  const s2Ref = React.useRef<SpreadSheet>();
  const [columnOptions, setColumnOptions] = React.useState([]);
  const scrollTimer = React.useRef<NodeJS.Timer>();

  //  ================== Callback ========================
  const updateOptions = (newOptions: Partial<S2Options<React.ReactNode>>) => {
    setOptions(customMerge({}, options, newOptions));
  };

  const updateDataCfg = (newDataCfg: Partial<S2DataConfig>) => {
    const currentDataCfg =
      sheetType === 'pivot' ? pivotSheetDataCfg : tableSheetDataCfg;

    setDataCfg(customMerge({}, currentDataCfg, newDataCfg));
  };

  const onAutoAdjustBoundary = (value: TooltipAutoAdjustBoundary) => {
    updateOptions({
      tooltip: {
        autoAdjustBoundary: value || null,
      },
    });
  };

  const onLayoutWidthTypeChange = (e: RadioChangeEvent) => {
    updateOptions({
      style: {
        layoutWidthType: e.target.value,
      },
    });
  };

  const onSizeChange = (type: 'width' | 'height') =>
    debounce((e) => {
      updateOptions({
        [type]: Number(e.target.value),
      });
    }, 300);

  const onScrollSpeedRatioChange =
    (type: 'horizontal' | 'vertical') => (value: number) => {
      updateOptions({
        interaction: {
          scrollSpeedRatio: {
            [type]: value,
          },
        },
      });
    };

  const onToggleRender = () => {
    setRender(!render);
  };

  const onThemeChange = (e: RadioChangeEvent) => {
    setThemeCfg({
      name: e.target.value,
    });
  };

  const onSheetTypeChange = (e: RadioChangeEvent) => {
    setSheetType(e.target.value);
  };

  const logHandler =
    (name: string) =>
    (...args: unknown[]) => {
      console.log(name, ...args);
    };

  const onColCellClick = (cellInfo: TargetCellInfo) => {
    logHandler('onColCellClick')(cellInfo);
    if (!options.showDefaultHeaderActionIcon) {
      const { event } = cellInfo;
      s2Ref.current.showTooltip({
        position: { x: event.clientX, y: event.clientY },
        content: <CustomColTooltip />,
      });
    }
  };

  const getColumnOptions = (sheetType: SheetType) => {
    if (sheetType === 'table') {
      return dataCfg.fields.columns;
    }
    return s2Ref.current?.getInitColumnLeafNodes().map(({ id }) => id) || [];
  };

  //  ================== Hooks ========================

  React.useEffect(() => {
    s2Ref.current?.on(S2Event.DATA_CELL_TREND_ICON_CLICK, (meta) => {
      console.log('趋势图icon点击', meta);
    });
  }, [sheetType]);

  useUpdateEffect(() => {
    switch (sheetType) {
      case 'table':
        setDataCfg(tableSheetDataCfg);
        updateOptions(defaultOptions);
        break;
      default:
        setDataCfg(pivotSheetDataCfg);
        updateOptions(defaultOptions);
        break;
    }
    setColumnOptions(getColumnOptions(sheetType));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sheetType]);

  //  ================== Config ========================

  const mergedOptions: Partial<S2Options<React.ReactNode>> = customMerge(
    {},
    {
      pagination: showPagination && {
        pageSize: 10,
        current: 1,
      },
      tooltip: {
        content: showCustomTooltip ? <CustomTooltip /> : null,
      },
      totals: showTotals && {
        row: {
          showGrandTotals: true,
          showSubTotals: true,
          subTotalsDimensions: ['province'],
        },
        col: {
          showGrandTotals: true,
          showSubTotals: true,
          subTotalsDimensions: ['type'],
        },
      },
      customSVGIcons: !options.showDefaultHeaderActionIcon && [
        {
          name: 'Filter',
          svg: 'https://gw.alipayobjects.com/zos/antfincdn/gu1Fsz3fw0/filter%26sort_filter.svg',
        },
        {
          name: 'FilterAsc',
          svg: 'https://gw.alipayobjects.com/zos/antfincdn/UxDm6TCYP3/filter%26sort_asc%2Bfilter.svg',
        },
      ],
      headerActionIcons: !options.showDefaultHeaderActionIcon && [
        {
          iconNames: ['Filter'],
          belongsCell: 'colCell',
          displayCondition: (node: Node) =>
            node.id !== 'root[&]家具[&]桌子[&]number',
          action: ({ event }: HeaderActionIconProps) => {
            s2Ref.current?.showTooltip({
              position: { x: event.clientX, y: event.clientY },
              content: <ActionIconTooltip name="Filter colCell" />,
            });
          },
        },
        {
          iconNames: ['SortDown'],
          belongsCell: 'colCell',
          displayCondition: (node: Node) =>
            node.id === 'root[&]家具[&]桌子[&]number',
          action: ({ event }: HeaderActionIconProps) => {
            s2Ref.current?.showTooltip({
              position: { x: event.clientX, y: event.clientY },
              content: <ActionIconTooltip name="SortDown colCell" />,
            });
          },
        },
        {
          iconNames: ['FilterAsc'],
          belongsCell: 'cornerCell',
          action: ({ event }: HeaderActionIconProps) => {
            s2Ref.current?.showTooltip({
              position: { x: event.clientX, y: event.clientY },
              content: <ActionIconTooltip name="FilterAsc cornerCell" />,
            });
          },
        },
        {
          iconNames: ['SortDown', 'Filter'],
          belongsCell: 'rowCell',
          action: ({ event }: HeaderActionIconProps) => {
            s2Ref.current?.showTooltip({
              position: { x: event.clientX, y: event.clientY },
              content: <ActionIconTooltip name="SortDown & Filter rowCell" />,
            });
          },
        },
      ],
    },
    options,
  );

  return (
    <div className="playground">
      <Tabs defaultActiveKey="basic" type="card" destroyInactiveTabPane>
        <TabPane tab="基础表" key="basic">
          <Collapse defaultActiveKey={['filter', 'interaction']}>
            <Collapse.Panel header="筛选器" key="filter">
              <Space>
                <Tooltip title="表格类型">
                  <Radio.Group
                    onChange={onSheetTypeChange}
                    defaultValue={sheetType}
                  >
                    <Radio.Button value="pivot">透视表</Radio.Button>
                    <Radio.Button value="table">明细表</Radio.Button>
                  </Radio.Group>
                </Tooltip>
                <Tooltip title="布局类型">
                  <Radio.Group
                    onChange={onLayoutWidthTypeChange}
                    defaultValue="adaptive"
                  >
                    <Radio.Button value="adaptive">行列等宽</Radio.Button>
                    <Radio.Button value="colAdaptive">列等宽</Radio.Button>
                    <Radio.Button value="compact">紧凑</Radio.Button>
                  </Radio.Group>
                </Tooltip>
                <Tooltip title="主题">
                  <Radio.Group onChange={onThemeChange} defaultValue="default">
                    <Radio.Button value="default">默认</Radio.Button>
                    <Radio.Button value="gray">简约灰</Radio.Button>
                    <Radio.Button value="colorful">多彩蓝</Radio.Button>
                  </Radio.Group>
                </Tooltip>
              </Space>
              <Space>
                <Popover
                  placement="bottomRight"
                  content={
                    <>
                      <ChromePicker
                        color={themeColor}
                        onChangeComplete={(color) => {
                          setThemeColor(color.hex);
                          const palette = getPalette(themeCfg.name);
                          const newPalette = generatePalette({
                            ...palette,
                            brandColor: color.hex,
                          });
                          setThemeCfg({
                            name: themeCfg.name,
                            palette: newPalette,
                          });
                        }}
                      />
                    </>
                  }
                >
                  <Button size="small" style={{ marginLeft: 20 }}>
                    主题色调整
                  </Button>
                </Popover>
                <Button
                  danger
                  size="small"
                  onClick={() => {
                    s2Ref.current?.destroy();
                    s2Ref.current?.render();
                  }}
                >
                  卸载组件 (s2.destroy)
                </Button>
              </Space>
              <Space style={{ margin: '20px 0', display: 'flex' }}>
                <Tooltip title="tooltip 自动调整: 显示的tooltip超过指定区域时自动调整, 使其不遮挡">
                  <Select
                    defaultValue={mergedOptions.tooltip.autoAdjustBoundary}
                    onChange={onAutoAdjustBoundary}
                    style={{ width: 230 }}
                    size="small"
                  >
                    <Select.Option value="container">
                      container (表格区域)
                    </Select.Option>
                    <Select.Option value="body">
                      body (浏览器可视区域)
                    </Select.Option>
                    <Select.Option value="">关闭</Select.Option>
                  </Select>
                </Tooltip>
                <Input
                  style={{ width: 150 }}
                  onChange={onSizeChange('width')}
                  defaultValue={mergedOptions.width}
                  suffix="px"
                  prefix="宽度"
                  size="small"
                />
                <Input
                  style={{ width: 150 }}
                  onChange={onSizeChange('height')}
                  defaultValue={mergedOptions.height}
                  suffix="px"
                  prefix="高度"
                  size="small"
                />
                <Button
                  size="small"
                  onClick={() => {
                    s2Ref.current?.changeSheetSize(400, 400);
                    s2Ref.current?.render(false);
                  }}
                >
                  改变表格大小 (s2.changeSheetSize)
                </Button>
                <Popover
                  placement="bottomRight"
                  content={
                    <>
                      <div style={{ width: '600px' }}>
                        水平滚动速率 :
                        <Slider
                          {...sliderOptions}
                          defaultValue={
                            mergedOptions.interaction.scrollSpeedRatio
                              .horizontal
                          }
                          onChange={onScrollSpeedRatioChange('horizontal')}
                        />
                        垂直滚动速率 :
                        <Slider
                          {...sliderOptions}
                          defaultValue={
                            mergedOptions.interaction.scrollSpeedRatio.vertical
                          }
                          onChange={onScrollSpeedRatioChange('vertical')}
                        />
                      </div>
                    </>
                  }
                >
                  <Button size="small">滚动速率调整</Button>
                </Popover>
                <Button
                  size="small"
                  onClick={() => {
                    const rowNode = s2Ref.current
                      ?.getRowNodes()
                      .find(({ id }) => id === 'root[&]四川省[&]成都市');

                    clearInterval(scrollTimer.current);
                    s2Ref.current.updateScrollOffset({
                      offsetY: {
                        value: rowNode?.y,
                        animate: true,
                      },
                    });
                  }}
                >
                  滚动至 [成都市]
                </Button>
                <Button
                  size="small"
                  onClick={() => {
                    clearInterval(scrollTimer.current);
                    s2Ref.current.updateScrollOffset({
                      offsetY: {
                        value: 0,
                        animate: true,
                      },
                    });
                  }}
                >
                  滚动到顶部
                </Button>
                <Button
                  size="small"
                  danger
                  onClick={() => {
                    if (
                      scrollTimer.current ||
                      !s2Ref.current.facet.vScrollBar
                    ) {
                      clearInterval(scrollTimer.current);
                      return;
                    }
                    scrollTimer.current = setInterval(() => {
                      const { scrollY } = s2Ref.current.facet.getScrollOffset();
                      if (s2Ref.current.facet.isScrollToBottom(scrollY)) {
                        console.log('滚动到底部');
                        s2Ref.current.updateScrollOffset({
                          offsetY: {
                            value: 0,
                            animate: false,
                          },
                        });
                        return;
                      }
                      s2Ref.current.updateScrollOffset({
                        offsetY: {
                          value: scrollY + 50,
                          animate: true,
                        },
                      });
                    }, 500);
                  }}
                >
                  {scrollTimer.current ? '停止滚动' : '循环滚动'}
                </Button>
              </Space>
              <Space
                style={{ marginTop: 20, display: 'flex', flexWrap: 'wrap' }}
              >
                <Switch
                  checkedChildren="渲染组件"
                  unCheckedChildren="卸载组件"
                  defaultChecked={render}
                  onChange={onToggleRender}
                />
                <Switch
                  checkedChildren="调试模式开"
                  unCheckedChildren="调试模式关"
                  defaultChecked={mergedOptions.debug}
                  onChange={(checked) => {
                    updateOptions({ debug: checked });
                  }}
                />
                <Switch
                  checkedChildren="树形"
                  unCheckedChildren="平铺"
                  checked={mergedOptions.hierarchyType === 'tree'}
                  onChange={(checked) => {
                    updateOptions({
                      hierarchyType: checked ? 'tree' : 'grid',
                    });
                  }}
                  disabled={sheetType === 'table'}
                />
                <Tooltip title="树状模式生效">
                  <Switch
                    checkedChildren="收起子节点"
                    unCheckedChildren="展开子节点"
                    disabled={mergedOptions.hierarchyType !== 'tree'}
                    checked={mergedOptions.hierarchyCollapse}
                    onChange={(checked) => {
                      updateOptions({
                        hierarchyCollapse: checked,
                      });
                    }}
                  />
                </Tooltip>
                <Switch
                  checkedChildren="数值挂列头"
                  unCheckedChildren="数值挂行头"
                  defaultChecked={dataCfg.fields?.valueInCols}
                  onChange={(checked) => {
                    updateDataCfg({
                      fields: {
                        valueInCols: checked,
                      },
                    });
                  }}
                  disabled={sheetType === 'table'}
                />
                <Switch
                  checkedChildren="隐藏数值"
                  unCheckedChildren="显示数值"
                  defaultChecked={mergedOptions.style.colCfg.hideMeasureColumn}
                  onChange={(checked) => {
                    updateOptions({
                      style: {
                        colCfg: {
                          hideMeasureColumn: checked,
                        },
                      },
                    });
                  }}
                  disabled={sheetType === 'table'}
                />
                <Switch
                  checkedChildren="显示行小计/总计"
                  unCheckedChildren="隐藏行小计/总计"
                  defaultChecked={
                    mergedOptions.totals?.row?.showSubTotals as boolean
                  }
                  onChange={(checked) => {
                    updateOptions({
                      totals: {
                        row: {
                          showGrandTotals: checked,
                          showSubTotals: checked,
                          reverseLayout: true,
                          reverseSubLayout: true,
                          subTotalsDimensions: ['province'],
                        },
                      },
                    });
                  }}
                  disabled={sheetType === 'table'}
                />
                <Switch
                  checkedChildren="显示列小计/总计"
                  unCheckedChildren="隐藏列小计/总计"
                  defaultChecked={
                    mergedOptions.totals?.col?.showSubTotals as boolean
                  }
                  onChange={(checked) => {
                    updateOptions({
                      totals: {
                        col: {
                          showGrandTotals: checked,
                          showSubTotals: checked,
                          reverseLayout: true,
                          reverseSubLayout: true,
                          subTotalsDimensions: ['type'],
                        },
                      },
                    });
                  }}
                  disabled={sheetType === 'table'}
                />
                <Switch
                  checkedChildren="冻结行头开"
                  unCheckedChildren="冻结行头关"
                  defaultChecked={mergedOptions.frozenRowHeader}
                  onChange={(checked) => {
                    updateOptions({
                      frozenRowHeader: checked,
                    });
                  }}
                  disabled={sheetType === 'table'}
                />
                <Switch
                  checkedChildren="容器宽高自适应开"
                  unCheckedChildren="容器宽高自适应关"
                  defaultChecked={Boolean(adaptive)}
                  onChange={setAdaptive}
                />
                <Switch
                  checkedChildren="显示序号"
                  unCheckedChildren="不显示序号"
                  checked={mergedOptions.showSeriesNumber}
                  onChange={(checked) => {
                    updateOptions({
                      showSeriesNumber: checked,
                    });
                  }}
                />
                <Switch
                  checkedChildren="分页"
                  unCheckedChildren="不分页"
                  checked={showPagination}
                  onChange={setShowPagination}
                />
                <Switch
                  checkedChildren="汇总"
                  unCheckedChildren="无汇总"
                  checked={showTotals}
                  onChange={setShowTotals}
                />
                <Switch
                  checkedChildren="默认actionIcons"
                  unCheckedChildren="自定义actionIcons"
                  checked={mergedOptions.showDefaultHeaderActionIcon}
                  onChange={(checked) => {
                    updateOptions({
                      showDefaultHeaderActionIcon: checked,
                    });
                  }}
                />
                <Switch
                  checkedChildren="开启Tooltip"
                  unCheckedChildren="关闭Tooltip"
                  checked={mergedOptions.tooltip.showTooltip}
                  onChange={(checked) => {
                    updateOptions({
                      tooltip: {
                        showTooltip: checked,
                      },
                    });
                  }}
                />
                <Switch
                  checkedChildren="自定义Tooltip"
                  unCheckedChildren="默认Tooltip"
                  checked={showCustomTooltip}
                  onChange={setShowCustomTooltip}
                />
              </Space>
            </Collapse.Panel>
            <Collapse.Panel header="交互配置" key="interaction">
              <Space>
                <Tooltip title="高亮选中单元格">
                  <Switch
                    checkedChildren="选中聚光灯开"
                    unCheckedChildren="选中聚光灯关"
                    checked={mergedOptions.interaction.selectedCellsSpotlight}
                    onChange={(checked) => {
                      updateOptions({
                        interaction: {
                          selectedCellsSpotlight: checked,
                        },
                      });
                    }}
                  />
                </Tooltip>
                <Tooltip title="高亮当前行列单元格">
                  <Switch
                    checkedChildren="hover十字器开"
                    unCheckedChildren="hover十字器关"
                    checked={mergedOptions.interaction.hoverHighlight}
                    onChange={(checked) => {
                      updateOptions({
                        interaction: {
                          hoverHighlight: checked,
                        },
                      });
                    }}
                  />
                </Tooltip>
                <Tooltip title="在数值单元格悬停800ms,显示tooltip">
                  <Switch
                    checkedChildren="hover聚焦开"
                    unCheckedChildren="hover聚焦关"
                    checked={mergedOptions.interaction.hoverFocus as boolean}
                    onChange={(checked) => {
                      updateOptions({
                        interaction: {
                          hoverFocus: checked,
                        },
                      });
                    }}
                  />
                </Tooltip>
                <Tooltip title="开启后,点击空白处,按下ESC键, 取消高亮, 清空选中单元格, 等交互样式">
                  <Switch
                    checkedChildren="自动重置交互样式开"
                    unCheckedChildren="自动重置交互样式关"
                    defaultChecked={
                      mergedOptions?.interaction?.autoResetSheetStyle
                    }
                    onChange={(checked) => {
                      updateOptions({
                        interaction: {
                          autoResetSheetStyle: checked,
                        },
                      });
                    }}
                  />
                </Tooltip>
                <Tooltip
                  title={
                    <>
                      <p>默认隐藏列 </p>
                      <p>明细表: 列头指定 field: number</p>
                      <p>透视表: 列头指定id: root[&]家具[&]沙发[&]number</p>
                    </>
                  }
                >
                  <Select
                    style={{ width: 300 }}
                    defaultValue={mergedOptions.interaction.hiddenColumnFields}
                    mode="multiple"
                    placeholder="默认隐藏列"
                    onChange={(fields) => {
                      updateOptions({
                        interaction: {
                          hiddenColumnFields: fields,
                        },
                      });
                    }}
                  >
                    {columnOptions.map((column) => {
                      return (
                        <Select.Option value={column} key={column}>
                          {column}
                        </Select.Option>
                      );
                    })}
                  </Select>
                </Tooltip>
              </Space>
            </Collapse.Panel>
            <Collapse.Panel header="宽高调整热区配置" key="resize">
              <ResizeConfig setOptions={setOptions} setThemeCfg={setThemeCfg} />
            </Collapse.Panel>
          </Collapse>
          {render && (
            <SheetComponent
              dataCfg={dataCfg as S2DataConfig}
              options={mergedOptions as S2Options}
              sheetType={sheetType}
              adaptive={adaptive}
              ref={s2Ref}
              themeCfg={themeCfg}
              partDrillDown={partDrillDown}
              header={{
                title: (
                  <a href="https://github.com/antvis/S2">
                    {reactPkg.name} playground
                  </a>
                ),
                description: (
                  <Space>
                    <span>
                      {reactPkg.name}: <Tag>{reactPkg.version}</Tag>
                    </span>
                    <span>
                      {corePkg.name}: <Tag>{corePkg.version}</Tag>
                    </span>
                    <span>
                      lang: <Tag>{getLang()}</Tag>
                    </span>
                  </Space>
                ),
                switcherCfg: { open: true },
                exportCfg: { open: true },
                advancedSortCfg: { open: true },
              }}
              onDataCellTrendIconClick={logHandler('onDataCellTrendIconClick')}
              onAfterRender={logHandler('onAfterRender')}
              onDestroy={logHandler('onDestroy')}
              onColCellClick={onColCellClick}
              onRowCellClick={logHandler('onRowCellClick')}
              onCornerCellClick={(cellInfo) => {
                s2Ref.current.showTooltip({
                  position: {
                    x: cellInfo.event.clientX,
                    y: cellInfo.event.clientY,
                  },
                  content: 'click',
                });
              }}
              onCornerCellHover={(cellInfo) => {
                s2Ref.current.showTooltip({
                  position: {
                    x: cellInfo.event.clientX,
                    y: cellInfo.event.clientY,
                  },
                  content: 'hover',
                });
              }}
              onDataCellClick={logHandler('onDataCellClick')}
              onLayoutResizeMouseDown={logHandler('onLayoutResizeMouseDown')}
              onLayoutResizeMouseUp={logHandler('onLayoutResizeMouseUp')}
              onCopied={logHandler('onCopied')}
              onLayoutColsHidden={logHandler('onLayoutColsHidden')}
              onLayoutColsExpanded={logHandler('onLayoutColsExpanded')}
              onSelected={logHandler('onSelected')}
            />
          )}
        </TabPane>
        <TabPane tab="自定义目录树" key="customTree">
          <SheetComponent
            dataCfg={{ data: dataCustomTrees, fields: customTreeFields }}
            options={{ width: 600, height: 480, hierarchyType: 'customTree' }}
          />
        </TabPane>
        <TabPane tab="趋势分析表" key="strategy">
          <Switch
            checkedChildren="单列头"
            unCheckedChildren="多列头"
            checked={strategyDataCfg.fields.columns.length === 1}
            onChange={(checked) => {
              setStrategyDataCfg(
                customMerge(customTree, {
                  fields: {
                    columns: customTree.fields.columns.slice(
                      0,
                      checked ? 1 : 2,
                    ),
                  },
                }),
              );
            }}
          />
          <SheetComponent
            sheetType="strategy"
            dataCfg={strategyDataCfg}
            options={strategyOptions}
            onRowCellClick={logHandler('onRowCellClick')}
            header={{ exportCfg: { open: true } }}
            themeCfg={{
              theme: strategyTheme,
              name: 'gray',
            }}
          />
        </TabPane>
        <TabPane tab="网格分析表" key="gridAnalysis">
          <SheetComponent
            sheetType="gridAnalysis"
            dataCfg={mockGridAnalysisDataCfg}
            options={mockGridAnalysisOptions}
          />
        </TabPane>
      </Tabs>
    </div>
  );
}
Example #5
Source File: ObjectAttrStruct.spec.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
describe("ObjectAttrStruct", () => {
  it("should work", async () => {
    const props = {
      value: defaultValue,
      onChange: jest.fn(),
    };

    const wrapper = mount(<ObjectAttrStruct {...props} />);
    expect(wrapper.find(Radio.Group).at(0).props().value).toBe("new");
    expect(wrapper.find(Row).at(1).children(0).text()).toBe(
      i18n.t(`${NS_FORMS}:${K.ADD_STRUCTURE_ITEM}`)
    );
    await act(async () => {
      wrapper.find(Radio.Group).at(0).invoke("onChange")({
        target: {
          value: "import",
        },
      } as unknown as RadioChangeEvent);

      await (global as any).flushPromises();
    });
    wrapper.update();
    expect(wrapper.find(Row).at(1).children(0).text()).toBe(
      i18n.t(`${NS_FORMS}:${K.SELECT_MODEL}`)
    );
  });

  it.each([
    ["enum", "", ""],
    ["enums", "", ""],
    ["str", "/a/", "/a/"],
    ["int", "/9/", "/9/"],
    ["arr", "/ab/", "/ab/"],
    ["str", null, ""],
    ["ip", "", IPRegex],
    ["json", `{"type":"string"}`, `{"type":"string"}`],
    ["enum", ["enum1", "enum2"], "enum1,enum2"],
  ])(
    "should work with props has struct_define (%s %s)",
    (type, regex, expected) => {
      const wrapper = mount(
        <ObjectAttrStruct
          {...{
            value: {
              ...defaultValue,
              struct_define: [
                {
                  id: "struId",
                  name: "struName",
                  type,
                  regex,
                },
              ],
            },
          }}
        />
      );
      wrapper.find(".struct-option-btn-group").childAt(0).simulate("click");
      expect(wrapper.find("tbody tr").childAt(3).text()).toBe(expected);
    }
  );

  it("test add new struct", async () => {
    const props = {
      value: defaultValue,
      onChange: jest.fn(),
    };

    const wrapper = mount(<ObjectAttrStruct {...props} />);
    wrapper.find(Button).at(0).invoke("onClick")(null);
    expect(wrapper.find(Modal).at(0).props().visible).toBeTruthy();
    wrapper.update();
    wrapper.find(Input).at(0).invoke("onChange")(
      "structId" as unknown as ChangeEvent<HTMLInputElement>
    );
    wrapper.find(Input).at(1).invoke("onChange")(
      "structName" as unknown as ChangeEvent<HTMLInputElement>
    );
    wrapper.find(Select).at(0).invoke("onChange")("date", null);

    wrapper.find(Modal).at(0).invoke("onOk")(null); // 点击弹窗确认按钮

    expect(props.onChange).toBeCalledWith({
      default: "",
      struct_define: [
        {
          id: "structId",
          name: "structName",
          type: "date",
          isNew: true,
        },
      ],
    });

    wrapper.update();
    expect(wrapper.find(Modal).at(0).props().visible).toBeFalsy();
    expect(wrapper.find(Table).at(0).props().dataSource.length).toBe(1);
    const optionBtnDiv = wrapper.find(".struct-option-btn-group").at(0);
    expect(optionBtnDiv.props().children.length).toBe(2);
    optionBtnDiv.childAt(0).invoke("onClick")();

    await jest.runAllTimers();
    wrapper.update();
    expect(wrapper.find(Modal).at(0).props().title).toBe(
      i18n.t(`${NS_FORMS}:${K.TITLE_EDIT_STRUCTURE_ITEM}`)
    );

    wrapper.find(Modal).at(0).invoke("onCancel")(null); // 点击弹窗确认按钮

    await jest.runAllTimers();
    wrapper.update();
    expect(wrapper.find(Modal).at(0).props().visible).toBeFalsy();
    optionBtnDiv.childAt(1).invoke("onClick")();
    expect(spyOnModalConfirm).toBeCalledWith(
      expect.objectContaining({
        title: i18n.t(`${NS_FORMS}:${K.NOTICE}`),
      })
    );

    act(() => {
      spyOnModalConfirm.mock.calls[
        spyOnModalConfirm.mock.calls.length - 1
      ][0].onOk(); // 点击弹窗确认按钮
    });

    expect(props.onChange).toBeCalledWith({
      default: "",
      struct_define: [],
    });
  });
  it("test add new struct and enum or enums", async () => {
    const props = {
      value: defaultValue,
      onChange: jest.fn(),
    };

    const wrapper = mount(<ObjectAttrStruct {...props} />);
    wrapper.find(Button).at(0).invoke("onClick")(null);
    expect(wrapper.find(Modal).at(0).props().visible).toBeTruthy();
    wrapper.update();
    wrapper.find(Input).at(0).invoke("onChange")(
      "structId" as unknown as ChangeEvent<HTMLInputElement>
    );
    wrapper.find(Input).at(1).invoke("onChange")(
      "structName" as unknown as ChangeEvent<HTMLInputElement>
    );
    wrapper.find(Select).at(0).invoke("onChange")("enum", null);
    wrapper.find(Modal).at(0).invoke("onOk")(null); // 点击弹窗确认按钮
    expect(props.onChange).toBeCalledWith({
      default: "",
      struct_define: [
        {
          id: "structId",
          name: "structName",
          type: "enum",
          isNew: true,
          regex: [],
        },
      ],
    });
  });

  it("test import struct", async () => {
    const props = {
      value: defaultValue,
      onChange: jest.fn(),
    };

    const wrapper = mount(<ObjectAttrStruct {...props} />);
    wrapper.find(Radio.Group).at(0).invoke("onChange")({
      target: {
        value: "import",
      },
    } as unknown as RadioChangeEvent);

    wrapper.update();
    wrapper.find(Button).at(0).invoke("onClick")(null);
    await (global as any).flushPromises();
    expect(wrapper.find(Modal).at(1).props().visible).toBeTruthy();
    wrapper.update();
    wrapper.find(Select).at(0).invoke("onChange")("object2", null);
    wrapper.update();
    expect(wrapper.find("Table").at(2).props().dataSource.length).toBe(1);
    wrapper.find(Modal).at(1).invoke("onOk")(null); // 点击弹窗确认按钮

    expect(props.onChange).toBeCalledWith({
      default: "",
      struct_define: [
        {
          id: "id2",
          name: "name2",
          type: "date",
        },
      ],
    });

    wrapper.find(Modal).at(1).invoke("onCancel")(null);
  });
});
Example #6
Source File: BackgroundItem.tsx    From yugong with MIT License 4 votes vote down vote up
Backgrounditem: React.FC<Props> = ({
  onChange,
  defaultData,
  onMinus,
}) => {
  const [data, setData] = useState<BackgroundGroupListTypesOfStyleItems>({
    ...defaultData,
  });
  const {
    gradient,
    gradientDirections,
    imageUrl,
    positionX,
    positionY,
    sizeX,
    sizeY,
    repeat,
  } = data;
  
  // 确定当前类型
  const [imageType, setImageType] = useState(gradient ? "gradient" : "image");
  const onChangeTab = useCallback(
    (e: RadioChangeEvent) => {
      const operateData = { ...data };
      const type = e.target.value;
      if (type === "gradient") {
        delete operateData.imageUrl;
      }
      if (type === "image") {
        delete operateData.gradient;
        delete operateData.gradientDirections;
      }
      setImageType(e.target.value);
      setData(operateData);
      onChange(operateData);
    },
    [data, onChange]
  );

  useEffect(() => {
    setData(defaultData);
    if (!!defaultData.imageUrl) {
      setImageType('image');
    }
    if (!!defaultData.gradient) {
      setImageType('gradient');
    }
  }, [defaultData])

  const onChangeBackgroundImage = useCallback(
    (url) => {
      const operateData = { ...data };
      operateData.imageUrl = url;
      setData(operateData);
      onChange(operateData);
    },
    [data, onChange]
  );

  const onChangeGradient = useCallback(
    (result: BackgroundGradientTypesOfStyleItems) => {
      const operateData = { ...data };
      const { gradient, gradientDirections } = result || {};
      operateData.gradient = gradient;
      operateData.gradientDirections = gradientDirections;
      setData(operateData);
      onChange(operateData);
    },
    [data, onChange]
  );

  const onChangeRepeat = useCallback(
    (e) => {
      const operateData = { ...data };
      operateData.repeat = e;
      setData(operateData);
      onChange(operateData);
    },
    [data, onChange]
  );

  const onChangeUnitInput = useCallback(
    (key: "positionX" | "positionY" | "sizeX" | "sizeY") =>
      ([value, unit]: UnitType) => {
        const operateData = { ...data };
        operateData[key] = [value, unit || ""];
        setData(operateData);
        onChange(operateData);
      },
    [data, onChange]
  );

  const onChangeQuickPosition = useCallback(
    ([positionX, positionY]) => {
      const operateData = { ...data };
      operateData.positionX = positionX;
      operateData.positionY = positionY;
      setData(operateData);
      onChange(operateData);
    },
    [data, onChange]
  );

  /**
   * 渐变渲染
   * @returns
   */
  const renderGradient = () => (
    <GradientSlider
      onChange={onChangeGradient}
      defaultData={{ gradient, gradientDirections }}
    />
  );

  /**
   * 图片渲染
   * @returns
   */
  const renderImage = () => (
    <Row className={s.row} style={{ marginBottom: "15px" }}>
      <Col span={12}>
        <Upload
          label="背景图片"
          onChange={onChangeBackgroundImage}
          defaultImg={imageUrl}
        />
      </Col>
    </Row>
  );

  return (
    <div className={classNames(s.backgroundwrap, 'hocdragwrap')}>
      <DragHandle />
      <div className={s.divide}>
        <div className={s.title} />
        <div className={s.menu}>
          <Button size="small" onClick={onMinus} icon={<MinusOutlined />}>
            删除
          </Button>
        </div>
      </div>
      <div className={s.backgrounditem}>
        <Row className={s.row}>
          <Col span={24}>
            <Radio.Group
              defaultValue={imageType}
              className={s.tab}
              onChange={onChangeTab}
            >
              <Radio.Button value="image">图片背景</Radio.Button>
              <Radio.Button value="gradient">渐变背景</Radio.Button>
            </Radio.Group>
          </Col>
        </Row>
        {imageType === "image" ? renderImage() : null}
        {imageType === "gradient" ? renderGradient() : null}
        <Row className={s.row}>
          <Col span={12}>
            <Select
              label="平铺方式"
              value={repeat}
              optionsData={{
                "no-repeat": "不平铺",
                repeat: "平铺",
                "repeat-x": "横向平铺",
                "repeat-y": "纵向平铺",
              }}
              onChange={onChangeRepeat}
            />
          </Col>
          <Col span={12}>
            <UnitInput
              label="背景高度"
              min={0}
              max={100000}
              onChange={onChangeUnitInput("sizeY")}
              defaultValue={sizeY}
            />
          </Col>
        </Row>
        <Row className={s.row}>
          <Col span={12}>
            <QuadrangularSelect
              label="背景位置"
              defaultData={[positionX, positionY]}
              onChange={onChangeQuickPosition}
            />
          </Col>
          <Col span={12}>
            <Row className={s.row}>
              <Col span={24}>
                <UnitInput
                  label="背景宽度"
                  min={0}
                  max={100000}
                  onChange={onChangeUnitInput("sizeX")}
                  defaultValue={sizeX}
                />
              </Col>
            </Row>
            <Row className={s.row}>
              <Col span={24}>
                <UnitInput
                  label="横向位置"
                  min={0}
                  max={100000}
                  onChange={onChangeUnitInput("positionX")}
                  defaultValue={positionX}
                />
              </Col>
            </Row>
            <Row className={s.row}>
              <Col span={24}>
                <UnitInput
                  label="纵向位置"
                  min={0}
                  max={100000}
                  onChange={onChangeUnitInput("positionY")}
                  defaultValue={positionY}
                />
              </Col>
            </Row>
          </Col>
        </Row>
      </div>
    </div>
  );
}
Example #7
Source File: relation.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
RelationModal = ({ visible, onCancel, versionInfo, mode }: IProps) => {
  const formRef = useRef({}) as MutableRefObject<FormInstance>;
  const [assetDetail, instance] = apiMarketStore.useStore((s) => [s.assetDetail.asset, s.instance]);
  const defaultAppID = mode === 'asset' ? assetDetail.appID : instance.appID || assetDetail.appID;
  const defaultProjectID = mode === 'asset' ? assetDetail.projectID : instance.projectID || assetDetail.projectID;
  const params = routeInfoStore.useStore((s) => s.params);
  const instanceType = get(instance, 'type', 'dice');
  const relationRef = useRef<{ projectList: PROJECT.Detail[]; appList: IApplication[] }>({
    projectList: [],
    appList: [],
  });
  const [state, updater, update] = useUpdate<IState>({
    serviceList: {},
    branchList: [],
    instanceList: [],
    instanceType: 'dice',
    chooseProjectID: undefined,
  });
  const { getAssetDetail, editAsset, editInstance } = apiMarketStore.effects;
  const getProjects = (query: { pageSize: number; pageNo: number; q?: string }) => {
    return getMyProject<Promise<API_MARKET.CommonResList<PROJECT.Detail[]>>>({ ...query }).then((res) => {
      if (res.success) {
        const { projectList } = relationRef.current;
        const list = res.data?.list || [];
        relationRef.current.projectList = uniqBy([...projectList, ...list], 'id');
        return { list, total: res.data.total };
      } else {
        return { list: [], total: 0 };
      }
    });
  };
  const chosenItemConvertProject = (selectItem: { label: string }) => {
    if (isEmpty(selectItem)) {
      return [];
    }
    if (!selectItem.label) {
      if (mode === 'instance' && instance.projectID) {
        return [{ value: instance.projectID, label: instance.projectName }];
      } else {
        return [{ value: assetDetail.projectID, label: assetDetail.projectName }];
      }
    }
    return selectItem;
  };
  const getApplications = React.useCallback(
    (query: { pageSize: number; pageNo: number; q?: string }) => {
      if (!state.chooseProjectID) {
        relationRef.current.appList = [];
        return;
      }
      return getApps<Promise<API_MARKET.CommonResList<IApplication[]>>>({
        ...query,
        projectId: state.chooseProjectID,
      }).then((res) => {
        if (res.success) {
          const list = res.data?.list || [];
          const { appList } = relationRef.current;
          relationRef.current.appList = uniqBy([...appList, ...list], 'id');
          return { list, total: res.data.total };
        } else {
          return { list: [], total: 0 };
        }
      });
    },
    [state.chooseProjectID],
  );
  const chosenItemConvertApp = (selectItem: { label: string }) => {
    if (isEmpty(selectItem)) {
      return [];
    }
    if (!selectItem.label) {
      if (mode === 'instance' && instance.appID) {
        return getAppDetail(instance.appID).then(({ data }) => {
          return [{ value: data.id, label: data.name }];
        });
      } else {
        return [{ value: assetDetail.appID, label: assetDetail.appName }];
      }
    }
    return selectItem;
  };
  const getAppInstances = React.useCallback(
    (appID: number) => {
      if (!appID) {
        update({
          serviceList: {},
          branchList: [],
          instanceList: [],
        });
        return;
      }
      getAppInstance<Promise<{ success: boolean; data: API_MARKET.AppInstanceItem[] }>>({ appID }).then((res) => {
        if (res.success) {
          const serviceList = groupBy(res.data || [], 'serviceName');
          let branchList: API_MARKET.AppInstanceItem[] = [];
          let instanceList: API_MARKET.AppInstanceItem[] = [];
          if (instance.serviceName) {
            branchList = serviceList[instance.serviceName] || [];
          }
          if (instance.runtimeID) {
            instanceList = branchList.filter((item) => item.runtimeID === instance.runtimeID);
          }
          update({
            serviceList,
            branchList,
            instanceList,
          });
        }
      });
    },
    [instance.runtimeID, instance.serviceName, update],
  );
  React.useEffect(() => {
    if (visible) {
      update({
        instanceType,
        chooseProjectID: defaultProjectID,
      });
      if (instanceType === 'dice' && defaultAppID && mode === 'instance') {
        getAppInstances(defaultAppID);
      }
    } else {
      relationRef.current = {
        projectList: [],
        appList: [],
      };
    }
  }, [assetDetail.appID, instanceType, defaultProjectID, mode, visible, update, defaultAppID, getAppInstances]);
  const handleChange = (name: string, value: number | API_MARKET.InstanceType, clearFields: string[]) => {
    const temp = {};
    clearFields.forEach((item) => {
      temp[item] = undefined;
    });
    switch (name) {
      case 'type':
        updater.instanceType(value as API_MARKET.InstanceType);
        temp.type = value;
        temp.url = instanceType !== value ? undefined : instance.url;
        if (value === 'dice') {
          if (defaultAppID) {
            getAppInstances(defaultAppID);
          }
        }
        break;
      case 'projectID':
        relationRef.current.appList = [];
        update({
          serviceList: {},
          branchList: [],
          instanceList: [],
          chooseProjectID: +value,
        });
        break;
      case 'appID':
        if (mode === 'instance') {
          getAppInstances(+value);
        }
        break;
      case 'serviceName':
        update({
          branchList: state.serviceList[value] || [],
          instanceList: [],
        });
        break;
      case 'runtimeID':
        update({
          instanceList: state.branchList.filter((item) => item.runtimeID === +value),
        });
        break;
      default:
        break;
    }
    formRef.current.setFieldsValue(temp);
  };
  const handleOk = async (data: any) => {
    if (mode === 'instance') {
      const instantiationID = instance.id;
      const { minor, swaggerVersion } = versionInfo;
      const { projectID, appID, type, url, serviceName, runtimeID } = data;
      const { workspace } = state.branchList.find((item) => item.runtimeID === +runtimeID) || {};
      const payload = {
        minor,
        swaggerVersion,
        assetID: params.assetID,
        projectID: type === 'dice' ? +projectID : undefined,
        appID: type === 'dice' ? +appID : undefined,
        type,
        url,
        serviceName,
        runtimeID: +runtimeID,
        workspace,
      } as API_MARKET.UpdateInstance;
      if (instantiationID) {
        payload.instantiationID = instantiationID;
      }
      await editInstance(payload);
      onCancel();
    } else if (mode === 'asset') {
      const { appList, projectList } = relationRef.current;
      const asset = pick(assetDetail, ['assetName', 'desc', 'logo', 'assetID']);
      let projectName: string | undefined;
      let appName: string | undefined;
      if (data.projectID) {
        projectName = get(
          projectList.find((item) => item.id === +data.projectID),
          'name',
        );
      }
      if (data.appID) {
        appName = get(
          appList.find((item) => item.id === +data.appID),
          'name',
        );
      }
      await editAsset({
        ...asset,
        projectID: +data.projectID,
        appID: +data.appID,
        assetID: params.assetID,
        projectName,
        appName,
      });
      onCancel();
      getAssetDetail({ assetID: params.assetID }, true);
    }
  };
  const fieldsList: IFormItem[] = [
    ...insertWhen(mode === 'instance', [
      {
        label: i18n.t('instance source'),
        name: 'type',
        type: 'radioGroup',
        required: true,
        initialValue: instanceType,
        itemProps: {
          onChange: (e: RadioChangeEvent) => {
            handleChange('type', e.target.value, []);
          },
        },
        options: [
          {
            name: i18n.t('internal'),
            value: 'dice',
          },
          {
            name: i18n.t('external'),
            value: 'external',
          },
        ],
      },
    ]),
    ...insertWhen(mode === 'asset' || (mode === 'instance' && state.instanceType === 'dice'), [
      {
        label: i18n.t('Project name'),
        name: 'projectID',
        required: false,
        initialValue: defaultProjectID,
        getComp: () => {
          return (
            <LoadMoreSelector
              chosenItemConvert={chosenItemConvertProject}
              getData={getProjects}
              dataFormatter={({ list, total }: { list: any[]; total: number }) => ({
                total,
                list: map(list, ({ id, name }) => {
                  return {
                    label: name,
                    value: id,
                  };
                }),
              })}
            />
          );
        },
        itemProps: {
          allowClear: true,
          placeholder: i18n.t('Please Select'),
          onChange: (v: number) => {
            handleChange('projectID', v, ['appID', 'serviceName', 'runtimeID', 'url']);
          },
        },
      },
      {
        label: i18n.t('dop:app name'),
        name: 'appID',
        initialValue: defaultAppID,
        required: false,
        getComp: () => {
          return (
            <LoadMoreSelector
              chosenItemConvert={chosenItemConvertApp}
              extraQuery={{ projectId: state.chooseProjectID }}
              getData={getApplications}
              dataFormatter={({ list, total }: { list: any[]; total: number }) => ({
                total,
                list: map(list, ({ id, name }) => {
                  return {
                    label: name,
                    value: id,
                  };
                }),
              })}
            />
          );
        },
        itemProps: {
          allowClear: true,
          placeholder: i18n.t('Please Select'),
          onChange: (v) => {
            handleChange('appID', v, ['serviceName', 'runtimeID', 'url']);
          },
        },
      },
    ]),
    ...insertWhen(mode === 'instance', [
      ...(state.instanceType === 'dice'
        ? [
            {
              label: i18n.t('Service name'),
              required: false,
              type: 'select',
              initialValue: get(instance, 'serviceName'),
              options: map(state.serviceList, (_v, k) => ({ name: k, value: k })),
              name: 'serviceName',
              itemProps: {
                allowClear: true,
                placeholder: i18n.t('Please Select'),
                onChange: (v) => {
                  handleChange('serviceName', v, ['runtimeID', 'url']);
                },
              },
            },
            {
              label: i18n.t('msp:deployment branch'),
              required: false,
              type: 'select',
              name: 'runtimeID',
              initialValue: get(instance, 'runtimeID'),
              options: map(state.branchList, ({ runtimeID, runtimeName }) => ({ name: runtimeName, value: runtimeID })),
              itemProps: {
                allowClear: true,
                placeholder: i18n.t('Please Select'),
                onChange: (v: number) => {
                  handleChange('runtimeID', v, ['url']);
                },
              },
            },
            {
              label: i18n.t('instance'),
              type: 'select',
              name: 'url',
              initialValue: instanceType === 'dice' ? get(instance, 'url') : undefined,
              required: false,
              itemProps: {
                allowClear: true,
              },
              getComp: () => (
                <Select placeholder={i18n.t('Please Select')}>
                  {state.instanceList.map(({ serviceAddr }) => {
                    return (serviceAddr || []).map((url) => (
                      <Select.Option key={url} value={url}>
                        {url}
                      </Select.Option>
                    ));
                  })}
                </Select>
              ),
            },
          ]
        : [
            {
              label: i18n.t('instance'),
              name: 'url',
              required: false,
              initialValue: instanceType === 'dice' ? undefined : get(instance, 'url'),
              rules: [{ pattern: regRules.url, message: i18n.t('Please enter address started with http') }],
            },
          ]),
    ]),
  ];
  return (
    <FormModal
      title={mode === 'asset' ? i18n.t('connection relation') : i18n.t('related instance')}
      fieldsList={fieldsList}
      ref={formRef}
      visible={visible}
      onCancel={onCancel}
      onOk={handleOk}
      modalProps={{
        destroyOnClose: true,
      }}
    />
  );
}
Example #8
Source File: cluster-form.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
ClusterBasicForm = ({
  form,
  editMode = false,
  clusterList,
  clusterType,
  formData,
}: {
  form: FormInstance;
  editMode: boolean;
  formData: any;
  clusterList: ORG_CLUSTER.ICluster[];
  clusterType: string;
}) => {
  const [credentialType, setCredentialType] = React.useState(get(formData, 'credentialType', 'kubeConfig'));
  const { getClusterNewDetail } = clusterStore.effects;

  const debounceCheckName = React.useCallback(
    debounce((nameStr: string, callback: Function) => {
      if (editMode) return callback();
      nameStr &&
        getClusterNewDetail({ clusterName: nameStr }).then(() => {
          callback();
        });
    }, 200),
    [],
  );

  const fieldsList = [
    {
      label: firstCharToUpper(i18n.t('{name} identifier', { name: i18n.t('cluster') })),
      name: 'name',
      config: {
        getValueFromEvent(e: any) {
          const { value } = e.target;
          return value.replace(/\s/g, '').toLowerCase();
        },
      },
      itemProps: {
        disabled: editMode,
        placeholder: i18n.t('cmp:Characters and numbers, separated by a hyphen and cannot be modified after creation'),
      },
      rules: [
        regRules.clusterName,
        {
          validator: (_rule: unknown, v: string, callback: Function) => {
            const curCluster = find(clusterList, { name: v });
            if (!editMode && curCluster) {
              callback(i18n.t('cmp:cluster already existed'));
            } else if (v) {
              debounceCheckName(v, callback);
            } else {
              callback();
            }
          },
        },
      ],
    },
    {
      label: i18n.t('Cluster name'),
      name: 'displayName',
      pattern: /^.{1,50}$/,
      required: false,
      itemProps: {
        maxLength: 50,
      },
    },
    {
      label: i18n.t('cmp:Extensive domain'),
      name: 'wildcardDomain',
      pattern: /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/,
      config: {
        getValueFromEvent(e: any) {
          const { value } = e.target;
          const newValue = value.replace(/\s/g, '');
          return newValue;
        },
      },
    },
    {
      label: i18n.t('Description'),
      name: 'description',
      type: 'textArea',
      itemProps: { autoSize: { minRows: 3, maxRows: 6 } },
      pattern: /^[\s\S]{0,100}$/,
      message: '100个字符以内',
      required: false,
    },
    {
      name: 'id',
      itemProps: { type: 'hidden' },
    },
    ...insertWhen(clusterType === 'edas', [
      { label: i18n.t('cmp:EDAS address'), name: ['scheduler', 'edasConsoleAddr'] },
      { label: 'AK', name: ['scheduler', 'accessKey'] },
      { label: 'AS', name: ['scheduler', 'accessSecret'] },
      { label: i18n.t('cmp:cluster ID'), name: ['scheduler', 'clusterID'] },
      { label: 'Region ID', name: ['scheduler', 'regionID'] },
      { label: i18n.t('cmp:Namespace'), name: ['scheduler', 'logicalRegionID'] },
    ]),
    ...insertWhen(clusterType === 'k8s' || clusterType === 'edas', [
      {
        label: i18n.t('cmp:Authentication method'),
        name: 'credentialType',
        type: 'radioGroup',
        options: [
          {
            value: 'kubeConfig',
            name: 'KubeConfig',
          },
          {
            value: 'serviceAccount',
            name: 'Service Account',
          },
          {
            value: 'proxy',
            name: 'Cluster Agent',
          },
        ],
        initialValue: 'kubeConfig',
        formLayout: 'horizontal',
        itemProps: {
          onChange: (e: RadioChangeEvent) => {
            form.setFieldsValue({ credential: { content: undefined, address: undefined } });
            setCredentialType(e.target.value);
          },
        },
      },
      ...insertWhen(credentialType === 'kubeConfig', [
        {
          label: 'KubeConfig',
          name: ['credential', 'content'],
          type: 'textArea',
          initialValue: editMode ? '********' : '',
          itemProps: {
            onClick: () => {
              if (!form.isFieldTouched(['credential', 'content'])) {
                form.setFieldsValue({ credential: { content: undefined } });
              }
            },
          },
          required: !editMode,
        },
      ]),
      ...insertWhen(credentialType === 'serviceAccount', [
        {
          label: 'API Server URL',
          name: 'credential.address',
        },
        {
          label: (
            <div className="flex items-center">
              <div className="mr-1">Secret</div>
              <Popover
                title={`${i18n.t(
                  'cmp:please use the following command to get the Secret information corresponding to the Service Account',
                )}:`}
                content={
                  <div className="flex flex-col">
                    <div># Copy the secret name from the output of the get secret command</div>
                    <div id="command-script" className="whitespace-pre">
                      {`~/$ kubectl get serviceaccounts <service-account-name> -o yaml\n~/$ kubectl get secret <service-account-secret-name> -o yaml`}
                    </div>
                    <div className="flex justify-end mt-2">
                      <Copy selector=".btn-to-copy" />
                      <Button type="ghost" className="btn-to-copy" data-clipboard-target="#command-script">
                        {i18n.t('cmp:copy to clipboard')}
                      </Button>
                    </div>
                  </div>
                }
              >
                <ErdaIcon size="14" type="help" className="text-icon cursor-pointer" />
              </Popover>
            </div>
          ),
          name: ['credential', 'content'],
          type: 'textArea',
          initialValue: editMode ? '********' : '',
          itemProps: {
            onClick: () => {
              if (!form.isFieldTouched(['credential', 'content'])) {
                form.setFieldsValue({ credential: { content: undefined } });
              }
            },
          },
          required: !editMode,
        },
      ]),
    ]),
  ];

  return <RenderPureForm list={fieldsList} form={form} />;
}
Example #9
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
export function TraceGraph(props: IProps) {
  const { dataSource } = props;
  const width = dataSource?.depth <= 12 ? 300 : dataSource?.depth * 24 + 100;
  const bg = ['#4E6097', '#498E9E', '#6CB38B', 'purple', '#F7A76B'];
  const errorColor = '#CE4324';
  // const bg = ['#5872C0', '#ADDD8B', '#DE6E6A', '#84BFDB', '#599F76', '#ED895D', '#9165AF','#DC84C8','#F3C96B'];
  const [expandedKeys, setExpandedKeys] = React.useState([] as string[]);
  const [selectedTimeRange, setSelectedTimeRange] = React.useState(null! as ITimeRange);
  const [proportion, setProportion] = React.useState([24, 0]);
  const [loading, setLoading] = React.useState(false);
  const [spanDetailData, setSpanDetailData] = React.useState({});
  const { roots, min, max } = listToTree(dataSource?.spans);
  const [tags, setTags] = React.useState(null! as MONITOR_TRACE.ITag);
  const [spanStartTime, setSpanStartTime] = React.useState(null! as number);
  const [timeRange, setTimeRange] = React.useState([null!, null!] as number[]);
  const [selectedSpanId, setSelectedSpanId] = React.useState(null! as string);
  const [view, setView] = React.useState('waterfall');
  const [spanData, spanDataLoading] = getSpanEvents.useState();
  const spanDataSource = spanData?.spanEvents || [];
  const duration = max - min;
  const allKeys: string[] = [];
  const { serviceAnalysis } = (spanDetailData as MONITOR_TRACE.ISpanRelationChart) || {};
  const [flameRef, { width: flameWidth }] = useMeasure();

  const containerRef = React.useRef(null);
  const [tooltipState, setTooltipState] = React.useState(
    null as { content: MONITOR_TRACE.FlameChartData; mouseX: number; mouseY: number } | null,
  );

  function getMousePos(
    relativeContainer: { getBoundingClientRect: () => DOMRect } | null,
    mouseEvent: { clientX: number; clientY: number },
  ) {
    if (relativeContainer !== null) {
      const rect = relativeContainer.getBoundingClientRect();
      const mouseX = mouseEvent.clientX - rect.left;
      const mouseY = mouseEvent.clientY - rect.top;
      return { mouseX, mouseY };
    } else {
      return { mouseX: 0, mouseY: 0 };
    }
  }

  const onMouseOver = (event: { clientX: number; clientY: number }, data: MONITOR_TRACE.FlameChartData) => {
    const { name, value, children, serviceName, selfDuration, spanKind, component } = data;
    setTooltipState({
      content: { name, value, children, serviceName, selfDuration, spanKind, component },
      ...getMousePos(containerRef.current, event),
    });
  };

  const onMouseOut = () => {
    setTooltipState(null);
  };

  const tooltipRef = useSmartTooltip({
    mouseX: tooltipState === null ? 0 : tooltipState.mouseX,
    mouseY: tooltipState === null ? 0 : tooltipState.mouseY,
  });

  const columns = [
    {
      title: i18n.t('Time'),
      dataIndex: 'timestamp',
      render: (time: number) => moment(time / 1000 / 1000).format('YYYY-MM-DD HH:mm:ss'),
    },
    {
      title: i18n.t('msp:Event'),
      dataIndex: 'events',
      ellipsis: true,
      render: (events: object) => (
        <div>
          {Object.keys(events).map((k) => (
            <Ellipsis title={`${k}: ${events[k]}`} key={k} />
          ))}
        </div>
      ),
    },
  ];

  const getMetaData = React.useCallback(async () => {
    setLoading(true);
    try {
      const { span_layer, span_kind, terminus_key, service_instance_id } = tags;
      const type = `${span_layer}_${span_kind}`;
      if (!service_instance_id) {
        return null;
      }
      const { success, data } = await getSpanAnalysis({
        type,
        startTime: timeRange[0],
        endTime: timeRange[1],
        serviceInstanceId: service_instance_id,
        tenantId: terminus_key,
      });

      if (success) {
        setSpanDetailData(data);
      }
    } finally {
      setLoading(false);
    }
  }, [tags, timeRange]);

  React.useEffect(() => {
    if (tags) {
      getMetaData();
    }
  }, [getMetaData, tags]);

  React.useEffect(() => {
    handleTableChange();
  }, [selectedSpanId, spanStartTime]);

  const handleTableChange = () => {
    if (selectedSpanId && spanStartTime) {
      getSpanEvents.fetch({
        startTime: Math.floor(spanStartTime),
        spanId: selectedSpanId,
      });
    }
  };

  const traverseData = (data: MONITOR_TRACE.ITraceSpan[]) => {
    for (let i = 0; i < data.length; i++) {
      data[i] = format(data[i], 0, handleClickTimeSpan);
    }

    return data;
  };
  const handleChangeView = (e: RadioChangeEvent) => {
    setView(e.target.value);
  };

  const treeData = traverseData(roots);
  const formatDashboardVariable = (conditions: string[]) => {
    const dashboardVariable = {};
    for (let i = 0; i < conditions?.length; i++) {
      dashboardVariable[conditions[i]] = tags?.[conditions[i]];
    }
    return dashboardVariable;
  };

  function handleClickTimeSpan(startTime: number, selectedTag: MONITOR_TRACE.ITag, id: string) {
    const r1 = moment(startTime / 1000 / 1000)
      .subtract(15, 'minute')
      .valueOf();
    const r2 = Math.min(
      moment(startTime / 1000 / 1000)
        .add(15, 'minute')
        .valueOf(),
      moment().valueOf(),
    );
    setSelectedTimeRange({
      mode: 'customize',
      customize: {
        start: moment(r1),
        end: moment(r2),
      },
    });
    setTimeRange([r1, r2]);
    setTags(selectedTag);
    setSpanStartTime(startTime / 1000 / 1000);
    setProportion([14, 10]);
    setSelectedSpanId(id);
  }

  function format(
    item: MONITOR_TRACE.ISpanItem,
    depth = 0,
    _handleClickTimeSpan: (startTime: number, selectedTag: MONITOR_TRACE.ITag, id: string) => void,
  ) {
    item.depth = depth;
    item.key = item.id;
    allKeys.push(item.id);
    const { startTime, endTime, duration: totalDuration, selfDuration, operationName, tags: _tags, id } = item;
    const { span_kind: spanKind, component, error, service_name: serviceName } = _tags;
    const leftRatio = (startTime - min) / duration;
    const centerRatio = (endTime - startTime) / duration;
    const rightRatio = (max - endTime) / duration;
    const showTextOnLeft = leftRatio > 0.2;
    const showTextOnRight = !showTextOnLeft && rightRatio > 0.2;
    const displayTotalDuration = mkDurationStr(totalDuration / 1000);
    item.title = (
      <div
        className="wrapper flex items-center"
        onClick={() => {
          _handleClickTimeSpan(startTime, _tags, id);
        }}
      >
        <Tooltip
          title={
            <SpanTitleInfo
              operationName={operationName}
              spanKind={spanKind}
              component={component}
              serviceName={serviceName}
            />
          }
        >
          <div className="left flex items-center" style={{ width: width - 24 * depth }}>
            <div className="w-1 h-4 relative mr-1" style={{ background: error ? errorColor : bg[depth % 5] }} />
            <div className="flex items-center w-full">
              <span className="font-semibold text-ms mr-2 whitespace-nowrap">{serviceName}</span>
              <span className="truncate text-xs">{operationName}</span>
            </div>
          </div>
        </Tooltip>
        <div className="right text-gray">
          <div style={{ flex: leftRatio }} className="text-right text-xs self-center">
            {showTextOnLeft && displayTotalDuration}
          </div>
          <Tooltip title={<SpanTimeInfo totalSpanTime={totalDuration} selfSpanTime={selfDuration} />}>
            <div
              style={{ flex: centerRatio < 0.01 ? 0.01 : centerRatio, background: error ? errorColor : bg[depth % 5] }}
              className="rounded-sm mx-1"
            />
          </Tooltip>
          <div style={{ flex: rightRatio }} className="self-center text-left text-xs">
            {showTextOnRight && displayTotalDuration}
          </div>
        </div>
      </div>
    );
    if (item.children) {
      item.children = item.children.map((x) => format(x, depth + 1, _handleClickTimeSpan));
    }
    return item;
  }

  const formatFlameData = () => {
    let flameData = {} as MONITOR_TRACE.FlameChartData;
    if (roots?.length === 1) {
      const { operationName, duration: totalDuration, tags: _tags, selfDuration } = roots[0];
      const { service_name, span_kind, component } = _tags;
      flameData = {
        name: operationName,
        value: totalDuration,
        children: [],
        serviceName: service_name,
        selfDuration,
        spanKind: span_kind,
        component,
      };
      forEach(roots[0].children, (span) => flameData.children.push(formatFlameDataChild(span)));
    } else {
      flameData = {
        name: 'root',
        value: dataSource?.duration,
        children: [],
        serviceName: '',
        selfDuration: dataSource?.duration,
        spanKind: '',
        component: '',
      };
      forEach(roots, (span) => flameData.children.push(formatFlameDataChild(span)));
    }
    return flameData;
  };

  const formatFlameDataChild = (span: MONITOR_TRACE.ISpanItem) => {
    let node = {} as MONITOR_TRACE.FlameChartData;
    const { operationName, duration: totalDuration, tags: _tags, selfDuration } = span;
    const { service_name, span_kind, component } = _tags;
    node = {
      name: operationName,
      value: totalDuration,
      children: [],
      serviceName: service_name,
      selfDuration,
      spanKind: span_kind,
      component,
    };
    if (span && span.children) {
      for (const item of span.children) {
        const child = formatFlameDataChild(item);
        node.children.push(child);
      }
    }
    return node;
  };

  const onExpand = (keys: string[]) => {
    setExpandedKeys(keys);
  };

  return (
    <>
      <TraceDetailInfo dataSource={dataSource} />
      <RadioGroup defaultValue="waterfall" value={view} onChange={handleChangeView} className="flex justify-end">
        <RadioButton value="waterfall">
          <span className="flex items-center">
            <ErdaIcon className="mr-1" type="pubutu" color="currentColor" />
            {i18n.t('msp:Waterfall Chart')}
          </span>
        </RadioButton>
        <RadioButton value="flame">
          <span className="flex items-center">
            <ErdaIcon className="mr-1" type="huoyantu" color="currentColor" />
            {i18n.t('msp:Flame Graph')}
          </span>
        </RadioButton>
      </RadioGroup>
      <div className="mt-4 trace-span-detail" ref={flameRef}>
        {view === 'waterfall' && (
          <Row gutter={20}>
            <Col span={proportion[0]} className={`${proportion[0] !== 24 ? 'pr-0' : ''}`}>
              <TraceHeader
                duration={duration}
                width={width}
                setExpandedKeys={setExpandedKeys}
                allKeys={allKeys}
                expandedKeys={expandedKeys}
              />
              <div className="trace-graph">
                {treeData.length > 0 && (
                  <Tree
                    showLine={{ showLeafIcon: false }}
                    defaultExpandAll
                    height={window.innerHeight - 200}
                    // switcherIcon={<DownOutlined />}
                    // switcherIcon={<CustomIcon type="caret-down" />}
                    expandedKeys={expandedKeys}
                    treeData={treeData}
                    onExpand={onExpand}
                  />
                )}
              </div>
            </Col>
            <Col span={proportion[1]} className={`${proportion[0] !== 24 ? 'pl-0' : ''}`}>
              <div className="flex justify-between items-center my-2 px-3 py-1">
                <div className="text-sub text-sm font-semibold w-5/6">
                  <Ellipsis title={tags?.operation_name}>{tags?.operation_name}</Ellipsis>
                </div>
                <Tooltip title={i18n.t('close')}>
                  <span onClick={() => setProportion([24, 0])} className="cursor-pointer">
                    <CustomIcon type="gb" className="text-holder" />
                  </span>
                </Tooltip>
              </div>
              <div className="px-3">
                {selectedTimeRange && (
                  <TimeSelect
                    // defaultValue={globalTimeSelectSpan.data}
                    // className={className}
                    onChange={(data, range) => {
                      if (Object.keys(data)?.length !== 0) {
                        setSelectedTimeRange(data);
                      }
                      const { quick = '' } = data;
                      let range1 = range?.[0]?.valueOf() || selectedTimeRange?.customize?.start?.valueOf();
                      let range2 = range?.[1]?.valueOf() || selectedTimeRange?.customize?.end?.valueOf();
                      if (quick) {
                        const [unit, count] = quick.split(':');
                        const [start, end] = translateRelativeTime(unit, Number(count));
                        range1 = start?.valueOf();
                        range2 = Math.min(end?.valueOf(), moment().valueOf());
                      }
                      setTimeRange([range1, range2]);
                    }}
                    value={selectedTimeRange}
                  />
                )}
              </div>
              {(serviceAnalysis || proportion[0] === 14) && (
                <div className="px-3 trace-detail-chart" style={{ height: window.innerHeight - 200 }}>
                  <Tabs>
                    <TabPane tab={i18n.t('msp:Attributes')} key={1}>
                      <KeyValueList data={tags} />
                    </TabPane>
                    <TabPane tab={i18n.t('msp:Events')} key={2}>
                      <Spin spinning={spanDataLoading}>
                        <ErdaTable columns={columns} dataSource={spanDataSource} onChange={handleTableChange} />
                      </Spin>
                    </TabPane>
                    <TabPane tab={i18n.t('msp:Related Services')} key={3}>
                      {!serviceAnalysis?.dashboardId && <EmptyHolder relative />}
                      {serviceAnalysis?.dashboardId && (
                        <ServiceListDashboard
                          timeSpan={{ startTimeMs: timeRange[0], endTimeMs: timeRange[1] }}
                          dashboardId={serviceAnalysis?.dashboardId}
                          extraGlobalVariable={formatDashboardVariable(serviceAnalysis?.conditions)}
                        />
                      )}
                    </TabPane>
                  </Tabs>
                </div>
              )}
            </Col>
          </Row>
        )}

        {view === 'flame' && (
          <div ref={containerRef} className="relative graph-flame overflow-y-auto overflow-x-hidden">
            <FlameGraph
              data={formatFlameData()}
              height={dataSource ? 20 * dataSource.depth + 1 : 200}
              width={flameWidth}
              onMouseOver={onMouseOver}
              onMouseOut={onMouseOut}
              disableDefaultTooltips
            />
            {tooltipState !== null && (
              <div ref={tooltipRef} className="absolute bg-default p-2 shadow-lg break-words rounded-[3px]">
                <SpanTitleInfo
                  operationName={tooltipState?.content.name}
                  spanKind={tooltipState?.content.spanKind}
                  component={tooltipState?.content.component}
                  serviceName={tooltipState?.content.serviceName}
                />
                <div className="text-white">
                  {i18n.t('current')} span {mkDurationStr(tooltipState?.content.selfDuration / 1000)} -{' '}
                  {i18n.t('total')} span {mkDurationStr(tooltipState?.content.value / 1000)}
                </div>
              </div>
            )}
          </div>
        )}
      </div>
    </>
  );
}
Example #10
Source File: issue-field-modal.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
IssueFieldModal = ({ visible, closeModal, formData, onOk }: IProps) => {
  const { addFieldItem, updateFieldItem, updateSpecialFieldOptions } = issueFieldStore.effects;
  const { id: orgID } = orgStore.useStore((s) => s.currentOrg);

  const [{ selectedRequired, optionListVisible }, updater, update] = useUpdate({
    selectedRequired: 'false',
    optionListVisible: false,
  });

  React.useEffect(() => {
    const { propertyType } = formData;
    if (FIELD_WITH_OPTION[propertyType]) {
      update({
        optionListVisible: true,
      });
    }
  }, [formData, update, updater, visible]);

  const handleSubmit = async (values: IFieldForm) => {
    const { enumeratedValues, propertyName } = values;
    if (specialFieldNameList.includes(propertyName) && isEmpty(formData)) {
      message.warning(i18n.t('the same {key} exists', { key: i18n.t('dop:Field name') }));
      return;
    }
    const enumeratedList = isEmpty(enumeratedValues) ? [] : enumeratedValues.slice(0, enumeratedValues.length - 1);

    if (formData.isSpecialField) {
      await updateSpecialFieldOptions({
        orgID,
        issueType: formData.propertyIssueType,
        list: enumeratedList,
      });
    } else {
      const params = {
        ...values,
        orgID,
        scopeID: orgID,
        scopeType: 'org',
        propertyIssueType: 'COMMON' as ISSUE_FIELD.IIssueType,
        relation: 0,
        required: selectedRequired === 'true',
        enumeratedValues: enumeratedList,
      };

      if (isEmpty(formData)) {
        await addFieldItem(params);
      } else {
        await updateFieldItem(params);
      }
    }
    updater.optionListVisible(false);
    onOk();
  };

  const fieldList = () => [
    {
      name: 'propertyID',
      required: false,
      itemProps: {
        type: 'hidden',
      },
    },
    {
      label: i18n.t('dop:Field name'),
      name: 'propertyName',
      itemProps: {
        disabled: formData?.isSpecialField,
        placeholder: i18n.t('Please enter the {name}', { name: i18n.t('dop:Field name').toLowerCase() }),
      },
    },
    {
      label: i18n.t('Required'),
      name: 'required',
      type: 'radioGroup',
      initialValue: 'false',
      options: [
        { name: i18n.t('common:Yes'), value: 'true' },
        { name: i18n.t('common:No'), value: 'false' },
      ],
      itemProps: {
        disabled: formData?.isSpecialField,
        onChange: (e: RadioChangeEvent) => {
          updater.selectedRequired(e.target.value);
        },
      },
    },
    {
      label: i18n.t('Type'),
      name: 'propertyType',
      type: 'select',
      options: getFieldTypeOption,
      itemProps: {
        disabled: formData?.isSpecialField,
        placeholder: i18n.t('please select the {name}', { name: i18n.t('Type').toLowerCase() }),
        onChange: (e: ISSUE_FIELD.IPropertyType) => {
          if (FIELD_WITH_OPTION[e]) {
            update({
              optionListVisible: true,
            });
          } else {
            updater.optionListVisible(false);
          }
        },
      },
    },
    ...insertWhen(optionListVisible, [
      {
        label: i18n.t('dop:Enumerated value'),
        name: 'enumeratedValues',
        required: true,
        type: 'custom',
        getComp: () => <FieldOptionsSetting />,
        rules: [
          {
            validator: (_rule: any, values: ISSUE_FIELD.IEnumData[], callback: Function) => {
              const list = isEmpty(values) ? [] : values.slice(0, values.length - 1);
              const sameNameDic = {};
              let isSameName = false;

              const valid = list.every(({ name }) => {
                if (!sameNameDic[name]) {
                  sameNameDic[name] = true;
                } else {
                  isSameName = true;
                }

                return name;
              });

              if (!list.length || !valid) {
                return callback(i18n.t('dop:enumerated value can not be empty'));
              }
              if (isSameName) {
                return callback(i18n.t('dop:enumerated value can not be the same'));
              }
              callback();
            },
          },
        ],
      },
    ]),
  ];

  const onCloseModal = () => {
    updater.optionListVisible(false);
    closeModal();
  };

  return (
    <FormModal
      name={i18n.t('dop:Issue field').toLowerCase()}
      fieldsList={fieldList}
      visible={visible}
      onOk={handleSubmit}
      formData={formData}
      onCancel={onCloseModal}
      modalProps={{
        destroyOnClose: true,
        maskClosable: false,
      }}
    />
  );
}
Example #11
Source File: test-env-detail.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
TestEnvDetail = (props: IProps) => {
  const { data, disabled, visible, onCancel, envID, envType, testType } = props;
  const [{ headerMode, globalMode, headerJsonValid, globalJsonValid }, updater, update] = useUpdate({
    headerMode: 'form',
    globalMode: 'form',
    headerJsonValid: true,
    globalJsonValid: true,
  });

  React.useEffect(() => {
    if (!visible) {
      update({
        headerMode: 'form',
        globalMode: 'form',
        headerJsonValid: true,
        globalJsonValid: true,
      });
    }
  }, [update, visible]);

  const formRef = React.useRef({}) as React.MutableRefObject<FormInstance>;
  const HeaderKeyComp = ({ record, update: _update, ...rest }: any) => {
    return <InputSelect options={headerListOption} value={record.key} disabled={disabled} onChange={_update} />;
  };

  const GlobalKeyComp = ({ record, update: _update, ...rest }: any) => (
    <Input value={record.key} onChange={(e) => _update(e.target.value.trim())} maxLength={500} {...rest} />
  );

  const GlobalDescComp = ({ record, update: _update, ...rest }: any) => (
    <Select value={record.type || typeMap[testType].string} allowClear onChange={_update} {...rest}>
      {map(typeMap[testType], (value, key) => (
        <Option key={key} value={value}>
          {value}
        </Option>
      ))}
    </Select>
  );

  const KeyDescComp = ({ record, keyDesc, update: _update, ...rest }: any) => (
    <Input value={record[keyDesc]} onChange={(e) => _update(e.target.value)} {...rest} />
  );

  const ValueComp = ({ record, valueName, update: _update, ...rest }: any) => (
    <Input value={record[valueName]} onChange={(e) => _update(e.target.value)} {...rest} />
  );

  const getFieldsList = (_type: string, _headMode: string, _globalMode: string) => {
    const headFieldsStatus = _headMode === 'form' ? ['', 'hidden'] : ['hidden', ''];
    const globalFieldsStatus = _globalMode === 'form' ? ['', 'hidden'] : ['hidden', ''];

    const fieldMap = {
      auto: [
        {
          label: i18n.t('Name'),
          name: 'displayName',
          itemProps: {
            maxLength: 191,
            disabled,
          },
        },
        {
          label: i18n.t('Description'),
          name: 'desc',
          type: 'textArea',
          itemProps: {
            maxLength: 512,
            disabled,
          },
        },
      ],
      manual: [
        {
          label: i18n.t('dop:Environment name'),
          name: 'name',
          itemProps: {
            maxLength: 50,
            disabled,
          },
        },
      ],
    };

    return [
      ...fieldMap[_type],
      {
        label: i18n.t('dop:Environmental domain'),
        name: 'domain',
        getComp: () => <ProtocolInput disabled={disabled} />,
        required: false,
      },
      {
        getComp: () => (
          <div className="flex justify-between items-center">
            <div>
              <span className="font-bold">Header</span>
            </div>
            <Radio.Group
              value={headerMode}
              onChange={(e: RadioChangeEvent) => {
                const _mode = e.target.value;
                const curForm = formRef.current;
                const curFormData = curForm.getFieldsValue();
                if (_mode === 'form') {
                  formRef.current.setFieldsValue({
                    header: JSON.parse(curFormData.headerStr),
                  });
                } else {
                  const isObj = isPlainObject(curFormData.header);
                  formRef.current.setFieldsValue({
                    headerStr: JSON.stringify(
                      filter(
                        map(curFormData.header, (item, k) => {
                          let reItem = {};
                          if (isObj) {
                            reItem = { value: item, key: k };
                          } else {
                            reItem = { key: item.key, value: item.value };
                          }
                          return reItem;
                        }),
                        (item) => item.key,
                      ),
                      null,
                      2,
                    ),
                  });
                }
                updater.headerMode(e.target.value);
              }}
            >
              <Radio.Button disabled={!headerJsonValid} value="form">
                {disabled ? firstCharToUpper(i18n.t('common:form')) : i18n.t('common:Form Edit')}
              </Radio.Button>
              <Radio.Button value="code">
                {disabled ? firstCharToUpper(i18n.t('common:text')) : i18n.t('common:Text Edit')}
              </Radio.Button>
            </Radio.Group>
          </div>
        ),
        extraProps: {
          className: 'mb-2',
        },
      },
      {
        name: 'header',
        required: false,
        getComp: () => <KVPairTable disabled={disabled} KeyComp={HeaderKeyComp} ValueComp={ValueComp} />,
        itemProps: {
          type: headFieldsStatus[0],
        },
      },
      {
        name: 'headerStr',
        required: false,
        getComp: () => <JsonFileEditor readOnly={disabled} />,
        itemProps: {
          type: headFieldsStatus[1],
        },
      },
      {
        getComp: () => (
          <div className="flex justify-between items-center">
            <span className="font-bold">Global</span>
            <Radio.Group
              value={globalMode}
              onChange={(e: RadioChangeEvent) => {
                const _mode = e.target.value;
                const curForm = formRef.current;
                const curFormData = curForm.getFieldsValue();
                if (_mode === 'form') {
                  formRef.current.setFieldsValue({
                    global: JSON.parse(curFormData.globalStr),
                  });
                } else {
                  const isObj = isPlainObject(curFormData.global);
                  formRef.current.setFieldsValue({
                    globalStr: JSON.stringify(
                      filter(
                        map(curFormData.global, (item, k) => {
                          const { desc = '', value = '', type = '' } = item;
                          const reItem = { desc, value, type, key: isObj ? k : item.key };
                          if (!reItem.type) reItem.type = typeMap.auto.string;
                          return reItem;
                        }),
                        (item) => item.key,
                      ),
                      null,
                      2,
                    ),
                  });
                }
                updater.globalMode(e.target.value);
              }}
            >
              <Radio.Button disabled={!globalJsonValid} value="form">
                {disabled ? firstCharToUpper(i18n.t('common:form')) : i18n.t('common:Form Edit')}
              </Radio.Button>
              <Radio.Button value="code">
                {disabled ? firstCharToUpper(i18n.t('common:text')) : i18n.t('common:Text Edit')}
              </Radio.Button>
            </Radio.Group>
          </div>
        ),
        extraProps: {
          className: 'mb-2',
        },
      },
      {
        name: 'global',
        required: false,
        getComp: () => (
          <KVPairTable
            disabled={disabled}
            KeyComp={GlobalKeyComp}
            DescComp={GlobalDescComp}
            descName="type"
            KeyDescComp={KeyDescComp}
            keyDesc="desc"
          />
        ),
        itemProps: {
          type: globalFieldsStatus[0],
        },
      },
      {
        name: 'globalStr',
        required: false,
        getComp: () => <JsonFileEditor readOnly={disabled} />,
        itemProps: {
          type: globalFieldsStatus[1],
        },
      },
    ];
  };

  const fieldsList = getFieldsList(testType, headerMode, globalMode);

  const onUpdateHandle = React.useCallback(
    (values, header, global) => {
      if (testType === 'manual') {
        testEnvStore.updateTestEnv({ ...values, id: data.id, header, global, envType, envID }, { envType, envID });
      } else {
        testEnvStore.updateAutoTestEnv({
          apiConfig: {
            domain: values.domain,
            name: values.name,
            header,
            global,
          },
          scope: scopeMap.autoTest.scope,
          scopeID: String(envID),
          ns: data.ns,
          displayName: values.displayName,
          desc: values.desc,
        });
      }
    },
    [data, envID, envType, testType],
  );

  const onCreateHandle = React.useCallback(
    (values, header, global) => {
      if (testType === 'manual') {
        testEnvStore.createTestEnv({ ...values, header, global, envType, envID }, { envType, envID });
      } else {
        testEnvStore.createAutoTestEnv(
          {
            apiConfig: {
              ...values,
              header,
              global,
            },
            scope: scopeMap.autoTest.scope,
            scopeID: String(envID),
            displayName: values.displayName,
            desc: values.desc,
          },
          { scope: scopeMap.autoTest.scope, scopeID: envID },
        );
      }
    },
    [envID, envType, testType],
  );

  const handleSubmit = (values: any) => {
    if (!globalJsonValid || !headerJsonValid) {
      return Promise.reject();
    }
    if (disabled) {
      onCancel();
      return;
    }
    const { headerStr, globalStr, ..._rest } = values;
    const curHeader = headerMode === 'form' ? values.header : JSON.parse(headerStr || '[]');
    const curGlobal = globalMode === 'form' ? values.global : JSON.parse(globalStr || '[]');
    const header = transHeader(curHeader);
    const global = transGlobal(curGlobal);

    if (!isEmpty(data)) {
      onUpdateHandle(_rest, header, global);
    } else {
      onCreateHandle(_rest, header, global);
    }
    onCancel();
  };

  const onValuesChange = React.useCallback(
    debounce((_formRef: { form: FormInstance }, changeValues: Obj, allValues: Obj) => {
      if (changeValues.headerStr) {
        const curHeaderValid = isValidJsonStr(changeValues.headerStr);
        updater.headerJsonValid(curHeaderValid);
      }
      if (changeValues.globalStr) {
        const curGlobalValid = isValidJsonStr(changeValues.globalStr);
        updater.globalJsonValid(curGlobalValid);
      }
    }, 300),
    [],
  );

  return (
    <FormModal
      name={i18n.t('dop:parameter configuration')}
      visible={visible}
      width={900}
      modalProps={{
        destroyOnClose: true,
      }}
      formOption={{ onValuesChange }}
      formData={data}
      ref={formRef}
      fieldsList={fieldsList}
      onOk={handleSubmit}
      onCancel={onCancel}
      formProps={{ layout: 'vertical' }}
    />
  );
}
Example #12
Source File: gray-form-modal.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
GrayFormModal = (props: IProps) => {
  const { visible, onOk, onCancel, formData } = props;
  const { setGrayAndPublish } = publisherStore.effects;
  const { publisherItemId } = routeInfoStore.useStore((s) => s.params);

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

  React.useEffect(() => {
    updater.selectedVersionType(formData.versionStates || 'beta');
  }, [formData, updater]);

  const setGrayThenPublish = (query: IQuery) => {
    const { id, versionStates, grayLevelPercent } = query;

    setGrayAndPublish({
      versionStates,
      grayLevelPercent: grayLevelPercent ? +grayLevelPercent : undefined,
      action: 'publish',
      publishItemID: +publisherItemId,
      publishItemVersionID: +id,
    })
      .then(() => {
        onOk();
      })
      .finally(() => {
        onCancel();
      });
  };

  const fieldList = [
    {
      name: 'id',
      itemProps: {
        type: 'hidden',
      },
      required: false,
    },
    {
      label: i18n.t('publisher:version type'),
      name: 'versionStates',
      type: 'radioGroup',
      options: [
        { name: i18n.t('publisher:Release version'), value: 'release' },
        { name: i18n.t('publisher:preview version'), value: 'beta' },
      ],
      itemProps: {
        onChange: (e: RadioChangeEvent) => {
          updater.selectedVersionType(e.target.value);
        },
      },
    },
    ...insertWhen(selectedVersionType === 'beta', [
      {
        label: `${i18n.t('publisher:set gray release')} (%)`,
        name: 'grayLevelPercent',
        type: 'inputNumber',
        required: false,
        rules: [
          {
            validator: validators.validateNumberRange({ min: 0, max: 100 }),
          },
        ],
        initialValue: (formData.versionStates === 'beta' && formData.grayLevelPercent) || 0,
        itemProps: {
          placeholder: i18n.t('please enter a number between {min} ~ {max}', { min: 0, max: 100 }),
          min: 0,
          max: 100,
        },
      },
    ]),
  ];

  return (
    <FormModal
      title={i18n.t('publisher:version publish configuration')}
      fieldsList={fieldList}
      formData={formData}
      visible={visible}
      onOk={setGrayThenPublish}
      onCancel={onCancel}
      modalProps={{
        destroyOnClose: true,
        maskClosable: false,
      }}
    />
  );
}
Example #13
Source File: RelatedViewForm.tsx    From datart with Apache License 2.0 4 votes vote down vote up
RelatedViewForm: React.FC<RelatedViewFormProps> = memo(
  ({ viewMap, form, queryVariables, controllerType, getFormRelatedViews }) => {
    const t = useI18NPrefix(`viz.associate`);
    const isMultiple = useCallback(
      index => {
        const relatedViews = getFormRelatedViews();
        const isVariable =
          relatedViews[index].relatedCategory ===
          ChartDataViewFieldCategory.Variable;
        const isRange = isRangeTypeController(controllerType);
        const isMultiple = isVariable && isRange;
        return isMultiple;
      },
      [controllerType, getFormRelatedViews],
    );
    const fieldValueValidator = async (opt, fieldValue: string[]) => {
      if (!fieldValue) {
        return Promise.reject(new Error(t('noValueErr')));
      }
      if (Array.isArray(fieldValue)) {
        if (fieldValue.length !== 2) {
          return Promise.reject(new Error(t('valueErr')));
        }
      }

      return Promise.resolve(fieldValue);
    };
    const filterFieldCategoryChange = useCallback(
      (index: number) => (e: RadioChangeEvent) => {
        const relatedViews = getFormRelatedViews();
        relatedViews[index].relatedCategory = e.target.value;
        relatedViews[index].fieldValue = undefined;
        form?.setFieldsValue({ relatedViews: relatedViews });
      },
      [form, getFormRelatedViews],
    );
    const fieldValueChange = useCallback(
      (index: number) => (value, option) => {
        const relatedViews = getFormRelatedViews();

        const fieldValueType = Array.isArray(option)
          ? option[0]?.fieldvaluetype
          : option?.fieldvaluetype;

        relatedViews[index].fieldValue = value;
        relatedViews[index].fieldValueType = fieldValueType;

        form?.setFieldsValue({ relatedViews: relatedViews });
      },
      [getFormRelatedViews, form],
    );

    const renderOptions = useCallback(
      (index: number) => {
        const relatedViews = getFormRelatedViews();
        if (!relatedViews) {
          return null;
        }
        if (
          relatedViews[index].relatedCategory ===
          ChartDataViewFieldCategory.Variable
        ) {
          // 变量
          return queryVariables
            .filter(v => {
              return v.viewId === relatedViews[index].viewId || !v.viewId;
            })
            .filter(v => {
              return filterValueTypeByControl(controllerType, v.valueType);
            })
            .map(item => (
              <Option
                key={item.id}
                fieldvaluetype={item.valueType}
                value={item.name}
              >
                <div
                  style={{ display: 'flex', justifyContent: 'space-between' }}
                >
                  <span>{item.name}</span>
                  <FieldType>{item.valueType}</FieldType>
                </div>
              </Option>
            ));
        } else {
          // 字段
          return viewMap?.[relatedViews[index].viewId]?.meta
            ?.filter(v => {
              return filterValueTypeByControl(controllerType, v.type);
            })
            .map(item => (
              <Option key={item.id} fieldvaluetype={item.type} value={item.id}>
                <div
                  style={{ display: 'flex', justifyContent: 'space-between' }}
                >
                  <span>{item.id}</span>
                  <FieldType>{item.type}</FieldType>
                </div>
              </Option>
            ));
        }
      },
      [controllerType, getFormRelatedViews, queryVariables, viewMap],
    );

    const getViewName = useCallback(
      (index: number) => {
        const relatedViews = getFormRelatedViews();
        const name = viewMap[relatedViews[index]?.viewId]?.name || '';
        return name;
      },
      [getFormRelatedViews, viewMap],
    );

    return (
      <Wrapper>
        <h3>{t('title')}</h3>
        <Form.List
          name="relatedViews"
          rules={[
            {
              validator: async (_, relatedViews: RelatedView[]) => {
                return Promise.resolve(relatedViews);
              },
            },
          ]}
        >
          {(fields, _, { errors }) => {
            return (
              <>
                {fields.map((field, index) => (
                  <Form.Item noStyle key={index} shouldUpdate>
                    <div className="relatedView">
                      <h4>{getViewName(index)}</h4>
                      <div style={{ width: '140px', textAlign: 'right' }}>
                        <Form.Item
                          {...field}
                          validateTrigger={['onChange', 'onClick', 'onBlur']}
                          name={[field.name, 'relatedCategory']}
                          fieldKey={[field.fieldKey, 'id']}
                        >
                          <RadioGroup
                            value
                            size="small"
                            onChange={filterFieldCategoryChange(index)}
                          >
                            <RadioButton
                              value={ChartDataViewFieldCategory.Field}
                            >
                              {t('field')}
                            </RadioButton>
                            <RadioButton
                              value={ChartDataViewFieldCategory.Variable}
                            >
                              {t('variable')}
                            </RadioButton>
                          </RadioGroup>
                        </Form.Item>
                      </div>
                    </div>

                    <Form.Item
                      {...field}
                      shouldUpdate
                      validateTrigger={['onChange', 'onClick', 'onBlur']}
                      name={[field.name, 'fieldValue']}
                      fieldKey={[field.fieldKey, 'id']}
                      wrapperCol={{ span: 24 }}
                      rules={[{ validator: fieldValueValidator }]}
                    >
                      <Select
                        showSearch
                        placeholder="请选择"
                        allowClear
                        {...(isMultiple(index) && { mode: 'multiple' })}
                        onChange={fieldValueChange(index)}
                      >
                        {renderOptions(index)}
                      </Select>
                    </Form.Item>
                  </Form.Item>
                ))}
                <Form.Item>
                  <Form.ErrorList errors={errors} />
                </Form.Item>
                {!fields.length && <Empty key="empty" />}
              </>
            );
          }}
        </Form.List>
      </Wrapper>
    );
  },
)