@ant-design/icons#InfoCircleOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#InfoCircleOutlined. 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: algo-node.tsx    From XFlow with MIT License 6 votes vote down vote up
AlgoIcon: React.FC<IProps> = props => {
  if (props.hide) {
    return null
  }
  switch (props.status) {
    case NsGraphStatusCommand.StatusEnum.PROCESSING:
      return <RedoOutlined spin style={{ color: '#c1cdf7', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.ERROR:
      return <CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.SUCCESS:
      return <CheckCircleOutlined style={{ color: '#39ca74cc', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.WARNING:
      return <ExclamationCircleOutlined style={{ color: '#faad14', fontSize: '16px' }} />
    case NsGraphStatusCommand.StatusEnum.DEFAULT:
      return <InfoCircleOutlined style={{ color: '#d9d9d9', fontSize: '16px' }} />
    default:
      return null
  }
}
Example #2
Source File: StickerWidget.tsx    From leek with Apache License 2.0 6 votes vote down vote up
export default function ({ icon, number, text, tooltip }) {
  return (
    <StickerWidgetWrapper className="leekStickerWidget">
      <div className="leekIconWrapper">{icon}</div>

      <div className="leekContentWrapper">
        <Statistic title={text} value={number} />
      </div>

      <div style={{ fontSize: "18px", padding: "7px" }}>
        <Tooltip title={tooltip}>
          <InfoCircleOutlined />
        </Tooltip>
      </div>
    </StickerWidgetWrapper>
  );
}
Example #3
Source File: info.tsx    From metaplex with Apache License 2.0 6 votes vote down vote up
Info = (props: {
  text: React.ReactElement;
  style?: React.CSSProperties;
}) => {
  return (
    <Popover
      trigger="hover"
      content={<div style={{ width: 300 }}>{props.text}</div>}
    >
      <Button type="text" shape="circle">
        <InfoCircleOutlined style={props.style} />
      </Button>
    </Popover>
  );
}
Example #4
Source File: index.tsx    From fe-v5 with Apache License 2.0 6 votes vote down vote up
export default function BlankBusinessPlaceholder(props: Props) {
  const { t } = useTranslation();
  const { text } = props;

  return (
    <div className='blank-busi-holder'>
      <p style={{ textAlign: 'left', fontWeight: 'bold' }}>
        <InfoCircleOutlined style={{ color: '#1473ff' }} /> {t('提示信息')}
      </p>
      <p>
        {text}需要归属某个业务组,请先 &nbsp;
        <Link to='/busi-groups'>创建业务组</Link>
      </p>
    </div>
  );
}
Example #5
Source File: common.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function GlobalFiltersTitle({
    unit = 'series',
    title = 'Filters',
}: {
    unit?: string
    title?: string
}): JSX.Element {
    return (
        <h4 className="secondary">
            {title}{' '}
            <Tooltip
                title={
                    <>
                        These filters will apply to <b>all</b> the {unit} in this graph.
                    </>
                }
            >
                <InfoCircleOutlined className="info-indicator" />
            </Tooltip>
        </h4>
    )
}
Example #6
Source File: index.tsx    From imove with MIT License 6 votes vote down vote up
hijackMap: { [key: string]: any } = {
  log: {
    bgColor: '#272823',
    textColor: '#ffffff',
    borderColor: 'rgba(128, 128, 128, 0.35)',
    icon: <div className={styles.logIcon} />,
    originMethod: console.log,
  },
  info: {
    bgColor: '#272823',
    textColor: '#ffffff',
    borderColor: 'rgba(128, 128, 128, 0.35)',
    icon: <InfoCircleOutlined className={styles.logIcon} />,
    originMethod: console.info,
  },
  warn: {
    bgColor: 'rgb(51, 42, 0)',
    textColor: 'rgb(245, 211, 150)',
    borderColor: 'rgb(102, 85, 0)',
    icon: <WarningOutlined className={styles.logIcon} />,
    originMethod: console.warn,
  },
  debug: {
    bgColor: '#272823',
    textColor: 'rgb(77, 136, 255)',
    borderColor: 'rgba(128, 128, 128, 0.35)',
    icon: <BugOutlined className={styles.logIcon} />,
    originMethod: console.debug,
  },
  error: {
    bgColor: 'rgb(40, 0, 0)',
    textColor: 'rgb(254, 127, 127)',
    borderColor: 'rgb(91, 0, 0)',
    icon: <CloseCircleOutlined className={styles.logIcon} />,
    originMethod: console.error,
  },
}
Example #7
Source File: PluginCard.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function PluginAboutButton({ url, disabled = false }: { url: string; disabled?: boolean }): JSX.Element {
    return (
        <Space>
            <Tooltip title="About">
                <LinkButton to={url} target="_blank" rel="noopener noreferrer" disabled={disabled}>
                    <InfoCircleOutlined />
                </LinkButton>
            </Tooltip>
        </Space>
    )
}
Example #8
Source File: index.tsx    From RareCamp with Apache License 2.0 6 votes vote down vote up
export default function PageHeading({
  title,
  description,
  renderEdit,
}: {
  title?: string
  description?: string
  renderEdit?: any
}) {
  return (
    <Space>
      <PageTitle title={title} />
      {description ? (
        <Tooltip placement="bottom" title={description}>
          <InfoCircleOutlined />
        </Tooltip>
      ) : null}
      {renderEdit ? renderEdit() : null}
    </Space>
  )
}
Example #9
Source File: FunnelBarGraph.tsx    From posthog-foss with MIT License 6 votes vote down vote up
function DuplicateStepIndicator(): JSX.Element {
    return (
        <span style={{ marginLeft: 4 }}>
            <Tooltip
                title={
                    <>
                        <b>Sequential &amp; Repeated Events</b>
                        <p>
                            When an event is repeated across funnel steps, it is interpreted as a sequence. For example,
                            a three-step funnel consisting of pageview events is interpretted as first pageview,
                            followed by second pageview, followed by a third pageview.
                        </p>
                    </>
                }
            >
                <InfoCircleOutlined className="info-indicator" />
            </Tooltip>
        </span>
    )
}
Example #10
Source File: TableComponents.tsx    From datart with Apache License 2.0 6 votes vote down vote up
TableColumnTitle = props => {
  const { desc, title, uid } = props;
  return (
    <TableColumnTitleStyle key={uid}>
      <span className="titleStyle" key={uid + 'title'}>
        {title}
      </span>
      {desc && (
        <Tooltip placement="top" key={uid + 'desc'} title={desc}>
          <InfoCircleOutlined />
        </Tooltip>
      )}
    </TableColumnTitleStyle>
  );
}
Example #11
Source File: ToolboxPane.tsx    From next-basics with GNU General Public License v3.0 6 votes vote down vote up
export function ToolboxPane({
  title,
  tooltips,
  children,
}: React.PropsWithChildren<ToolboxPaneProps>): React.ReactElement {
  const { t } = useTranslation(NS_NEXT_BUILDER);
  const getPopupContainer = React.useCallback(
    (triggerNode: HTMLElement) => triggerNode.parentElement,
    []
  );

  return (
    <div className={styles.toolboxPane}>
      <div className={styles.toolboxPaneHeading}>
        <div className={styles.toolboxPaneTitle}>{title}</div>
        {tooltips && (
          <div className={styles.toolboxPaneTooltips}>
            <Popover
              content={tooltips}
              title={t(K.TIPS)}
              getPopupContainer={getPopupContainer}
              overlayStyle={{ maxWidth: 320 }}
            >
              <InfoCircleOutlined />
            </Popover>
          </div>
        )}
      </div>
      <div className={styles.toolboxPaneBody}>{children}</div>
    </div>
  );
}
Example #12
Source File: index.tsx    From XFlow with MIT License 6 votes vote down vote up
AlgoIcon: React.FC<IProps> = props => {
  if (props.hide) {
    return null
  }
  switch (props.status) {
    case StatusEnum.PROCESSING:
      return <RedoOutlined spin style={{ color: '#c1cdf7', fontSize: '16px' }} />
    case StatusEnum.ERROR:
      return <CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: '16px' }} />
    case StatusEnum.SUCCESS:
      return <CheckCircleOutlined style={{ color: '#39ca74cc', fontSize: '16px' }} />
    case StatusEnum.WARNING:
      return <ExclamationCircleOutlined style={{ color: '#faad14', fontSize: '16px' }} />
    case StatusEnum.DEFAULT:
      return <InfoCircleOutlined style={{ color: '#d9d9d9', fontSize: '16px' }} />
    default:
      return null
  }
}
Example #13
Source File: InfoMessage.tsx    From posthog-foss with MIT License 6 votes vote down vote up
/** An informative message. */
export function InfoMessage({
    children,
    style,
}: {
    children: string | JSX.Element
    style?: React.CSSProperties
}): JSX.Element {
    return (
        <div className="info-message" style={style}>
            <InfoCircleOutlined />
            <div>{children}</div>
        </div>
    )
}
Example #14
Source File: Tile.tsx    From ant-extensions with MIT License 5 votes vote down vote up
Tile: React.FC<ITileConfig> = React.memo((item) => {
  const { isEditing, editWidget, renderWidget, findWidget } = useContext(Context);
  const style = useMemo(
    () => ({
      color: item.color || "inherit"
    }),
    [item.color]
  );

  const [expanded, setExpanded] = useState(false);

  const widget = useMemo(() => findWidget(item.widgetId), [findWidget, item.widgetId]);

  return (
    <Item item={item} expanded={!isEditing && expanded}>
      <div className="ant-ext-pm__tileHead">
        <span style={style}>{item.iconCls && <i className={item.iconCls} />}</span>
        <label style={style}>{item.title}</label>
        <div>
          {item.info && (
            <Tooltip
              overlay={<pre dangerouslySetInnerHTML={{ __html: item.info }} />}
              overlayClassName="ant-ext-pm__tileInfo"
            >
              <button>
                <InfoCircleOutlined />
              </button>
            </Tooltip>
          )}
          {!isEditing && item.expandable && (
            <button className="ant-ext-pm__tileExpander" onClick={() => setExpanded(!expanded)}>
              {expanded ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
            </button>
          )}
          {isEditing && (
            <button onClick={() => editWidget(item.widgetId)}>
              <EditOutlined />
            </button>
          )}
        </div>
      </div>
      <div className="ant-ext-pm__tileBody">
        {!isEditing && renderWidget(item.widgetId)}
        {isEditing && widget && (
          <div style={{ placeSelf: "center", textAlign: "center" }}>
            {widget.icon}
            <div>{widget.title}</div>
          </div>
        )}
      </div>
    </Item>
  );
})
Example #15
Source File: BooleanArguments.tsx    From yugong with MIT License 5 votes vote down vote up
BooleanArguments: React.FC<Props> = ({
  typeArguments,
  flexible,
  onChange,
}) => {
  const [argumentsState, setArgumentsState] = useState<ArgumentsItem>();
  useEffect(() => {
    setArgumentsState(typeArguments);
  }, [typeArguments]);

  const onChangeValue = useCallback(
    (key: string) => (e: any) => {
      const result: anyObj = { ...argumentsState };
      result.data[key] = e.target.value;
      if (onChange instanceof Function) {
        onChange(result as ArgumentsItem);
      }
      setArgumentsState(result as ArgumentsItem);
    },
    [argumentsState, onChange]
  );

  const onChangeCondition = useCallback((e) => {
    const result: anyObj = { ...argumentsState };
    result.data.method = e
    if (onChange instanceof Function) {
      onChange(result as ArgumentsItem);
    }
    setArgumentsState(result as ArgumentsItem);
  }, [argumentsState, onChange]);

  const data: any = argumentsState?.data || {
    comparableAverageA: null,
    comparableAverageB: null,
    method: "===",
  };
  return (
    <>
      <Row gutter={4}>
        <Col span={8}>
          <Input
            onChange={onChangeValue("comparableAverageA")}
            placeholder="条件A"
            type="text"
            value={data.comparableAverageA}
          />
        </Col>
        <Col span={6}>
          <Select className={s.selected} value={data.method} onChange={onChangeCondition}>
            {methodOptions.map((item) => (
              <Select.Option key={item.value} value={item.value}>{item.name}</Select.Option>
            ))}
          </Select>
        </Col>
        <Col span={8} className={s.btn}>
          <Input
            onChange={onChangeValue("comparableAverageB")}
            placeholder="条件B"
            type="text"
            value={data.comparableAverageB}
          />
        </Col>
        <Col span={2} style={{lineHeight: '30px'}}>
        <Tooltip
            placement="topRight"
            title={<span>{`${argumentsState && getBooleanData(argumentsState?.data) ? '当前状态:开启' : '当前状态:关闭'}`}<br />(条件成立时开启)</span>}
        >
            <InfoCircleOutlined />
        </Tooltip>
        </Col>
      </Row>
    </>
  );
}
Example #16
Source File: index.tsx    From posthog-foss with MIT License 5 votes vote down vote up
columns = [
    {
        title: 'Active',
        render: function renderActive(license: any) {
            return isLicenseExpired(license) ? 'expired' : 'active'
        },
    },
    {
        title: 'Valid until',
        render: function renderActive(license: any) {
            return humanFriendlyDetailedTime(license.valid_until)
        },
    },
    {
        title: 'Plan',
        dataIndex: 'plan',
    },
    {
        title: function Render() {
            return (
                <Tooltip
                    placement="right"
                    title="Maximum number of team members that you can have across all organizations with your current license."
                >
                    Max # of team members
                    <InfoCircleOutlined className="info-indicator" />
                </Tooltip>
            )
        },
        render: function renderMaxUsers(license: any) {
            return license.max_users === null ? 'Unlimited' : license.max_users
        },
    },
    {
        title: 'Key',
        render: function renderActive(license: any) {
            return <CodeSnippet>{license.key}</CodeSnippet>
        },
    },
    {
        title: 'License added on',
        render: function renderActive(license: any) {
            return humanFriendlyDetailedTime(license.created_at)
        },
    },
]
Example #17
Source File: Tabs.tsx    From foodie with MIT License 5 votes vote down vote up
Tabs: React.FC<IProps> = ({ username, isOwnProfile, followersCount, followingCount }) => {
    const { pathname } = useLocation();
    const [activeNav, setActiveNav] = useState('');

    useEffect(() => {
        const splitPath = pathname.split('/');
        const currentNav = splitPath[splitPath.length - 1];

        setActiveNav(currentNav);
    }, [pathname]);

    return (
        <ul className="flex items-center justify-between tablet:justify-evenly flex-wrap laptop:justify-start space-x-1 laptop:space-x-4 px-4 bg-indigo-100 dark:bg-indigo-1000 laptop:dark:bg-transparent laptop:bg-transparent laptop:p-0">
            <li>
                <Link
                    to={`/user/${username}/`}
                    className={`${linkStyleName} ${(activeNav === username || activeNav === '') && 'border-indigo-700 dark:border-indigo-400  border-b-4 text-gray-800 dark:text-white '}`}
                >
                    <span className="hidden laptop:inline-block">Posts</span>
                    <FormOutlined className="laptop:hidden text-2xl" />
                </Link>
            </li>
            <li>
                <Link
                    to={`/user/${username}/info`}
                    className={`${linkStyleName} ${activeNav === 'info' && 'border-indigo-700 dark:border-indigo-400  border-b-4'}`}
                >
                    <span className="hidden laptop:inline-block">Info</span>
                    <InfoCircleOutlined className="laptop:hidden text-2xl" />
                </Link>
            </li>
            <li>
                <Link
                    to={`/user/${username}/followers`}
                    className={`${linkStyleName} ${activeNav === 'followers' && 'border-indigo-700 dark:border-indigo-400 border-b-4'}`}
                >
                    <span className="laptop:text-lg text-indigo-700 dark:text-indigo-400">{followersCount}</span>
                    <span className="hidden laptop:inline-block">{followersCount > 1 ? 'Followers' : 'Follower'}</span>
                    <TeamOutlined className="laptop:hidden text-2xl" />
                </Link>
            </li>
            <li>
                <Link
                    to={`/user/${username}/following`}
                    className={`${linkStyleName} ${activeNav === 'following' && 'border-indigo-700 dark:border-indigo-400 border-b-4'}`}
                >
                    <span className="laptop:text-lg text-indigo-700 dark:text-indigo-400">{followingCount}</span>
                    <span className="hidden laptop:inline-block">Following</span>
                    <UserAddOutlined className="laptop:hidden text-2xl" />
                </Link>
            </li>
            {isOwnProfile && (
                <li>
                    <Link
                        to={`/user/${username}/bookmarks`}
                        className={`${linkStyleName} ${activeNav === 'bookmarks' && 'border-indigo-700 dark:border-indigo-400 border-b-4'}`}
                    >
                        <span className="hidden laptop:inline-block">Bookmarks</span>
                        <StarOutlined className="laptop:hidden text-2xl" />
                    </Link>
                </li>
            )}
        </ul>
    );
}
Example #18
Source File: PrintButton.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
export function PrintButton(props: PrintButtonProps): React.ReactElement {
  const [visible, setVisible] = useState(false);

  const content = (
    <span>
      查看{" "}
      <a onClick={preRemind}>
        帮助{" "}
        <InfoCircleOutlined
          style={{ transform: "translateY(1px)" }}
        ></InfoCircleOutlined>
      </a>
    </span>
  );

  const handleVisible = (isVisible: boolean) => {
    setVisible(isVisible);
  };

  function preRemind() {
    const padding = 32;
    const pngWidth = 800;
    Modal.info({
      className: styles.printModalBody,
      maskClosable: true,
      title: "打印操作指引",
      width: pngWidth + padding * 2,
      okText: "关闭",
      content: (
        <div style={{ overflowY: "scroll", width: "100%", height: "500px" }}>
          <img src={printHelperUrl} alt="打印操作指导图片" />
        </div>
      ),
    });
  }

  function invokePrint() {
    const originTitle = window.parent.document.title;
    // in iframe, maybe
    window.parent.document.title =
      props.prefixTitle +
      "-" +
      new Date().toISOString().substr(0, 10).replace(/-/g, "");
    window.print();
    window.parent.document.title = originTitle;
  }

  return (
    <div>
      <Popover
        title={null}
        content={content}
        visible={visible}
        onVisibleChange={handleVisible}
        overlayClassName="print-hide"
        placement="left"
      >
        <Button
          className="print-hide"
          icon={<DownloadOutlined />}
          style={{
            backgroundColor: props.backgroundColor,
            color: props.color,
            border: props.border,
            width: props.size || 24,
            height: props.size || 24,
          }}
          onClick={invokePrint}
        ></Button>
      </Popover>
    </div>
  );
}
Example #19
Source File: InsightDisplayConfig.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function InsightDisplayConfig({ filters, activeView, disableTable }: InsightDisplayConfigProps): JSX.Element {
    const showFunnelBarOptions = activeView === InsightType.FUNNELS
    const showPathOptions = activeView === InsightType.PATHS
    const dateFilterDisabled = showFunnelBarOptions && isFunnelEmpty(filters)

    return (
        <div className="display-config-inner">
            <div className="display-config-inner-row">
                {showDateFilter[activeView] && !disableTable && (
                    <span className="filter">
                        <span className="head-title-item">Date range</span>
                        <InsightDateFilter
                            defaultValue="Last 7 days"
                            disabled={dateFilterDisabled}
                            bordered
                            makeLabel={(key) => (
                                <>
                                    <CalendarOutlined /> {key}
                                    {key == 'All time' && (
                                        <Tooltip title={`Only events dated after 2015 will be shown`}>
                                            <InfoCircleOutlined className="info-indicator" />
                                        </Tooltip>
                                    )}
                                </>
                            )}
                        />
                    </span>
                )}

                {showIntervalFilter(activeView, filters) && (
                    <span className="filter">
                        <span className="head-title-item">
                            <span className="hide-lte-md">grouped </span>by
                        </span>
                        <IntervalFilter view={activeView} />
                    </span>
                )}

                {activeView === InsightType.RETENTION && (
                    <>
                        <RetentionDatePicker />
                        <RetentionReferencePicker />
                    </>
                )}

                {showPathOptions && (
                    <span className="filter">
                        <PathStepPicker />
                    </span>
                )}

                {showComparePrevious[activeView] && (
                    <span className="filter">
                        <CompareFilter />
                    </span>
                )}
            </div>
            <div className="display-config-inner-row">
                {showChartFilter(activeView) && (
                    <span className="filter">
                        <span className="head-title-item">Chart type</span>
                        <ChartFilter filters={filters} disabled={filters.insight === InsightType.LIFECYCLE} />
                    </span>
                )}
                {showFunnelBarOptions && filters.funnel_viz_type === FunnelVizType.Steps && (
                    <>
                        <span className="filter">
                            <FunnelDisplayLayoutPicker />
                        </span>
                    </>
                )}
                {showFunnelBarOptions && filters.funnel_viz_type === FunnelVizType.TimeToConvert && (
                    <span className="filter">
                        <FunnelBinsPicker />
                    </span>
                )}
            </div>
        </div>
    )
}
Example #20
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function index(props: IProps) {
  const { preNamePrefix = [], namePrefix = ['options', 'standardOptions'] } = props;

  return (
    <Panel header='高级设置'>
      <>
        <Form.Item
          label={
            <div>
              单位{' '}
              <Tooltip
                overlayInnerStyle={{
                  width: 500,
                }}
                getTooltipContainer={() => document.body}
                title={
                  <div>
                    <div>默认会做 SI Prefixes 处理,如不想默认的处理可选择 none 关闭</div>
                    <div>Data(SI): 基数为 1000, 单位为 B、kB、MB、GB、TB、PB、EB、ZB、YB</div>
                    <div>Data(IEC): 基数为 1024, 单位为 B、KiB、MiB、GiB、TiB、PiB、EiB、ZiB、YiB</div>
                    <div>bits: b</div>
                    <div>bytes: B</div>
                  </div>
                }
              >
                <InfoCircleOutlined />
              </Tooltip>
            </div>
          }
          name={[...namePrefix, 'util']}
        >
          <Select suffixIcon={<CaretDownOutlined />} placeholder='auto' allowClear>
            <Option value='none'>none</Option>
            <OptGroup label='Data(SI)'>
              <Option value='bitsSI'>bits(SI)</Option>
              <Option value='bytesSI'>bytes(SI)</Option>
            </OptGroup>
            <OptGroup label='Data(IEC)'>
              <Option value='bitsIEC'>bits(IEC)</Option>
              <Option value='bytesIEC'>bytes(IEC)</Option>
            </OptGroup>
            <OptGroup label='百分比'>
              <Option value='percent'>百分比(0-100)</Option>
              <Option value='percentUnit'>百分比(0.0-1.0)</Option>
            </OptGroup>
            <OptGroup label='时间'>
              <Option value='seconds'>seconds</Option>
              <Option value='milliseconds'>milliseconds</Option>
              <Option value='humantimeSeconds'>humanize(seconds)</Option>
              <Option value='humantimeMilliseconds'>humanize(milliseconds)</Option>
            </OptGroup>
          </Select>
        </Form.Item>
        <Row gutter={10}>
          <Col span={8}>
            <Form.Item label='最小值' name={[...namePrefix, 'min']}>
              <InputNumber placeholder='auto' style={{ width: '100%' }} />
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label='最大值' name={[...namePrefix, 'max']}>
              <InputNumber placeholder='auto' style={{ width: '100%' }} />
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label='小数点' name={[...namePrefix, 'decimals']}>
              <InputNumber placeholder='auto' style={{ width: '100%' }} />
            </Form.Item>
          </Col>
        </Row>
      </>
    </Panel>
  );
}
Example #21
Source File: FunnelCanvasLabel.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function FunnelCanvasLabel(): JSX.Element | null {
    const { insightProps, filters, activeView } = useValues(insightLogic)
    const { conversionMetrics, clickhouseFeaturesEnabled, aggregationTargetLabel } = useValues(
        funnelLogic(insightProps)
    )
    const { setChartFilter } = useActions(chartFilterLogic(insightProps))

    if (activeView !== InsightType.FUNNELS) {
        return null
    }

    const labels = [
        ...(filters.funnel_viz_type === FunnelVizType.Steps
            ? [
                  <>
                      <span className="text-muted-alt">
                          <Tooltip
                              title={`Overall conversion rate for all ${aggregationTargetLabel.plural} on the entire funnel.`}
                          >
                              <InfoCircleOutlined className="info-indicator left" />
                          </Tooltip>
                          Total conversion rate
                      </span>
                      <span className="text-muted-alt mr-025">:</span>
                      <span className="l4">{formatDisplayPercentage(conversionMetrics.totalRate)}%</span>
                  </>,
              ]
            : []),
        ...(filters.funnel_viz_type !== FunnelVizType.Trends
            ? [
                  <>
                      <span className="text-muted-alt">
                          <Tooltip
                              title={`Average (arithmetic mean) of the total time each ${aggregationTargetLabel.singular} spent in the entire funnel.`}
                          >
                              <InfoCircleOutlined className="info-indicator left" />
                          </Tooltip>
                          Average time to convert{' '}
                      </span>
                      {filters.funnel_viz_type === FunnelVizType.TimeToConvert && <FunnelStepsPicker />}
                      <span className="text-muted-alt mr-025">:</span>
                      <Button
                          type="link"
                          onClick={() => setChartFilter(FunnelVizType.TimeToConvert)}
                          disabled={
                              !clickhouseFeaturesEnabled || filters.funnel_viz_type === FunnelVizType.TimeToConvert
                          }
                      >
                          <span className="l4">{humanFriendlyDuration(conversionMetrics.averageTime)}</span>
                      </Button>
                  </>,
              ]
            : []),
    ]

    return (
        <Row className="funnel-canvas-label" align="middle">
            {labels.map((label, i) => (
                <React.Fragment key={i}>
                    {i > 0 && <span style={{ margin: '2px 8px', borderLeft: '1px solid var(--border)', height: 14 }} />}
                    {label}
                </React.Fragment>
            ))}
        </Row>
    )
}
Example #22
Source File: Tabs.tsx    From datart with Apache License 2.0 5 votes vote down vote up
function CloseIcon({ touched, stage, error }: CloseIconProps) {
  const [hovering, setHovering] = useState(false);

  const onEnter = useCallback(() => {
    setHovering(true);
  }, []);

  const onLeave = useCallback(() => {
    setHovering(false);
  }, []);

  let icon;

  switch (stage) {
    case ViewViewModelStages.Loading:
    case ViewViewModelStages.Running:
    case ViewViewModelStages.Saving:
      icon = <LoadingOutlined />;
      break;
    default:
      if (!hovering) {
        if (error) {
          icon = <InfoCircleOutlined css={errorColor} />;
        } else if (touched) {
          icon = <Editing />;
        } else {
          icon = <CloseOutlined />;
        }
      } else {
        icon = <CloseOutlined />;
      }

      break;
  }

  return (
    <CloseIconWrapper onMouseEnter={onEnter} onMouseLeave={onLeave}>
      {icon}
    </CloseIconWrapper>
  );
}
Example #23
Source File: index.tsx    From leek with Apache License 2.0 4 votes vote down vote up
IndexPage = () => {
  const { currentApp, currentEnv } = useApplication();
  const [stats, setStats] = useState<any>({});
  const stats_widgets = StatsWidgets(stats);

  // Metadata
  const metricsService = new MetricsService();
  const [seenWorkers, setSeenWorkers] = useState<
    MetricsContextData["seenWorkers"]
  >([]);
  const [seenTasks, setSeenTasks] = useState<MetricsContextData["seenTasks"]>(
    []
  );
  const [processedEvents, setProcessedEvents] =
    useState<MetricsContextData["processedEvents"]>(0);
  const [processedTasks, setProcessedTasks] =
    useState<MetricsContextData["processedTasks"]>(0);
  const [seenStates, setSeenStates] = useState<
    MetricsContextData["seenStates"]
  >([]);
  const [seenTaskStates, setSeenTaskStates] = useState<
    MetricsContextData["seenStates"]
  >([]);
  const [seenRoutingKeys, setSeenRoutingKeys] = useState<
    MetricsContextData["seenRoutingKeys"]
  >([]);
  const [seenQueues, setSeenQueues] = useState<
    MetricsContextData["seenQueues"]
  >([]);
  const [searchDriftLoading, setSearchDriftLoading] = useState<boolean>(true);
  const [searchDrift, setSearchDrift] = useState<any>(null);

  const [timeFilters, setTimeFilters] = useState<any>({
    timestamp_type: "timestamp",
    interval_type: "past",
    offset: 900000,
  });

  function getSearchDrift() {
    if (!currentApp || !currentEnv) return;
    setSearchDriftLoading(true);
    metricsService
      .getSearchDrift(currentApp, currentEnv)
      .then(handleAPIResponse)
      .then((result: any) => {
        setSearchDrift(result);
      }, handleAPIError)
      .catch(handleAPIError)
      .finally(() => setSearchDriftLoading(false));
  }

  function getMetrics() {
    if (!currentApp) return;
    metricsService
      .getBasicMetrics(currentApp, currentEnv, timeFilters)
      .then(handleAPIResponse)
      .then((result: any) => {
        setProcessedEvents(result.aggregations.processed_events.value);
        const processed = result.aggregations.seen_states.buckets.reduce(
          (result, item) => {
            if (!workerStates.includes(item.key)) {
              return result + item.doc_count;
            }
            return result;
          },
          0
        );
        setProcessedTasks(processed);
        setSeenWorkers(result.aggregations.seen_workers.buckets);
        setSeenTasks(result.aggregations.seen_tasks.buckets);
        setSeenStates(result.aggregations.seen_states.buckets);
        setSeenTaskStates(
          tasksStatesDefaults
            .map(
              (obj) =>
                result.aggregations.seen_states.buckets.find(
                  (o) => o.key === obj.key
                ) || obj
            )
            .filter((item) => !workerStates.includes(item.key))
        );
        setSeenRoutingKeys(result.aggregations.seen_routing_keys.buckets);
        setSeenQueues(result.aggregations.seen_queues.buckets);
      }, handleAPIError)
      .catch(handleAPIError);
  }

  useEffect(() => {
    let adapted = {
      SEEN_TASKS: seenTasks.length,
      SEEN_WORKERS: seenWorkers.length,
      PROCESSED_EVENTS: processedEvents,
      PROCESSED_TASKS: processedTasks,
      TASKS: 0,
      EVENTS: 0,
      // Tasks
      QUEUED: 0,
      RECEIVED: 0,
      STARTED: 0,
      SUCCEEDED: 0,
      FAILED: 0,
      REJECTED: 0,
      REVOKED: 0,
      IGNORED: 0,
      RETRY: 0,
      RECOVERED: 0,
      CRITICAL: 0,
      // Worker
      ONLINE: 0,
      HEARTBEAT: 0,
      OFFLINE: 0,
    };
    seenStates.map((task, _) => (adapted[task.key] = task.doc_count));
    setStats(adapted);
  }, [seenTasks, seenWorkers, seenStates]);

  useEffect(() => {
    getMetrics();
    getSearchDrift();
    return () => {
      clearInterval(metricsInterval);
    };
  }, []);

  useEffect(() => {
    // Stop refreshing metadata
    if (metricsInterval) clearInterval(metricsInterval);
    // If no application specified, return
    if (!currentApp) return;

    // Else, get metrics every 10 seconds
    getMetrics();
    getSearchDrift();
    metricsInterval = setInterval(() => {
      getMetrics();
      getSearchDrift();
    }, 10000);
  }, [currentApp, currentEnv, timeFilters]);

  return (
    <>
      <Helmet>
        <html lang="en" />
        <title>Metrics</title>
        <meta name="description" content="Events metrics" />
        <meta name="keywords" content="celery, tasks" />
      </Helmet>

      <Row justify="space-between" align="middle" style={{ marginBottom: 16 }}>
        <Statistic
          loading={searchDriftLoading}
          title={
            <Tooltip title="The time of the latest event processed by leek.">
              <span>Latest Event </span>
              <InfoCircleOutlined />
            </Tooltip>
          }
          value={
            searchDrift && searchDrift.latest_event_timestamp
              ? moment(searchDrift.latest_event_timestamp).format(
                  "MMM D HH:mm:ss Z"
                )
              : ""
          }
          valueStyle={{ fontSize: 17.5 }}
          prefix={<FieldTimeOutlined />}
        />

        <Affix
          style={{
            position: "fixed",
            left: "50%",
            transform: "translate(-50%, 0)",
          }}
        >
          <Row>
            <TimeFilter
              timeFilter={timeFilters}
              onTimeFilterChange={setTimeFilters}
            />
          </Row>
        </Affix>

        <Statistic
          loading={searchDriftLoading}
          title={
            <Tooltip title="How many events in the queue waiting to be indexed.">
              <span>Current Drift </span>
              <InfoCircleOutlined />
            </Tooltip>
          }
          value={
            searchDrift && searchDrift.messages_count
              ? searchDrift.messages_count
              : "0"
          }
          valueStyle={{ fontSize: 17.5 }}
          prefix={<EyeInvisibleOutlined />}
        />
      </Row>

      <Row gutter={16} justify="center" align="middle">
        {stats_widgets.map((widget, idx) => (
          <Col
            lg={12}
            md={12}
            sm={12}
            xs={24}
            key={idx}
            style={{ marginBottom: "16px" }}
          >
            <StickerWidget
              number={widget.number}
              text={widget.text}
              icon={widget.icon}
              tooltip={widget.tooltip}
            />
          </Col>
        ))}
      </Row>
    </>
  );
}
Example #24
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function index(props: IProps) {
  const { preNamePrefix = [], namePrefix = ['options', 'valueMappings'] } = props;

  return (
    <Panel header='值映射'>
      <Form.List name={namePrefix}>
        {(fields, { add, remove }) => (
          <>
            <Button
              style={{ width: '100%', marginBottom: 10 }}
              onClick={() => {
                add({
                  type: 'special',
                });
              }}
            >
              添加
            </Button>
            {_.isEmpty(fields) ? null : (
              <Row gutter={10}>
                <Col flex='290px'>
                  <Tooltip
                    overlayInnerStyle={{
                      width: 300,
                    }}
                    title={
                      <div>
                        <div>范围值说明: from &gt;= value &lt;= to</div>
                        <div>范围值默认值: from=-Infinity; to=Infinity </div>
                        <div>特殊值Null说明: 匹配值为 null 或 undefined 或 no data</div>
                      </div>
                    }
                  >
                    条件 <InfoCircleOutlined />
                  </Tooltip>
                </Col>
                <Col flex='210'>显示文字</Col>
                <Col flex='45'>颜色</Col>
                <Col flex='50'>操作</Col>
              </Row>
            )}

            {fields.map(({ key, name, ...restField }) => {
              return (
                <Row key={key} gutter={10} style={{ marginBottom: 10 }}>
                  <Col flex='290px'>
                    <Row gutter={10}>
                      <Col flex='80px'>
                        <Form.Item noStyle {...restField} name={[name, 'type']}>
                          <Select suffixIcon={<CaretDownOutlined />} style={{ width: 80 }}>
                            <Select.Option value='special'>固定值</Select.Option>
                            <Select.Option value='range'>范围值</Select.Option>
                            <Select.Option value='specialValue'>特殊值</Select.Option>
                          </Select>
                        </Form.Item>
                      </Col>
                      <Col flex='1'>
                        <Form.Item noStyle {...restField} shouldUpdate>
                          {({ getFieldValue }) => {
                            const type = getFieldValue([...preNamePrefix, ...namePrefix, name, 'type']);
                            if (type === 'special') {
                              return (
                                <Form.Item noStyle {...restField} name={[name, 'match', 'special']}>
                                  <InputNumber style={{ width: '100%' }} />
                                </Form.Item>
                              );
                            }
                            if (type === 'range') {
                              return (
                                <Row gutter={10}>
                                  <Col span={12}>
                                    <Form.Item noStyle {...restField} name={[name, 'match', 'from']}>
                                      <InputNumber placeholder='from' />
                                    </Form.Item>
                                  </Col>
                                  <Col span={12}>
                                    <Form.Item noStyle {...restField} name={[name, 'match', 'to']}>
                                      <InputNumber placeholder='to' />
                                    </Form.Item>
                                  </Col>
                                </Row>
                              );
                            }
                            if (type === 'specialValue') {
                              return (
                                <Form.Item noStyle {...restField} name={[name, 'match', 'specialValue']}>
                                  <Select suffixIcon={<CaretDownOutlined />} style={{ width: '100%' }}>
                                    <Select.Option value='null'>Null</Select.Option>
                                    <Select.Option value='empty'>Empty string</Select.Option>
                                  </Select>
                                </Form.Item>
                              );
                            }
                            return null;
                          }}
                        </Form.Item>
                      </Col>
                    </Row>
                  </Col>
                  <Col flex='210'>
                    <Form.Item noStyle {...restField} name={[name, 'result', 'text']}>
                      <Input placeholder='可选' />
                    </Form.Item>
                  </Col>
                  <Col flex='45'>
                    <Form.Item noStyle {...restField} name={[name, 'result', 'color']}>
                      <ColorPicker />
                    </Form.Item>
                  </Col>
                  <Col flex='50'>
                    <Button
                      onClick={() => {
                        remove(name);
                      }}
                      icon={<DeleteOutlined />}
                    />
                  </Col>
                </Row>
              );
            })}
          </>
        )}
      </Form.List>
    </Panel>
  );
}
Example #25
Source File: PageSetting.tsx    From yugong with MIT License 4 votes vote down vote up
Pagesetting: React.FC<Props> = () => {
  const appData = useSelector((state: RootState) => state.appData);
  const pageData = useSelector((state: RootState) => state.pageData);
  const updatePage = useDispatch<Dispatch>().pageData.updatePage;
  const [showRunningTimes, setShowRunningTimes] = useState(false);
  const runningTimes = useSelector((state: RootState) => state.runningTimes);
  // const ref = useRef();


  // 监听App页面数据,同步到编辑器数据
  const sendMessage = usePostMessage((data) => {
    const { tag, value } = data;
    if (tag === "updatePage") {
      updatePage(value);
    }
  });

  // 本地存储编辑数据
  const [, setPagedataLocalStorage] = useLocalStorage("pageData", null);

  const handleUpdatePage = useCallback(
    (pageData) => {
      // 1、更新redux数据
      updatePage(pageData);
      // 2、将页面数据本地缓存
      setPagedataLocalStorage(pageData);
      // 3、向下游发送数据
      const win = (document.getElementById("wrapiframe") as HTMLIFrameElement)
        .contentWindow;
      if (win) {
        sendMessage(
          {
            tag: "updatePage",
            value: pageData,
          },
          win
        );
      }
    },
    [sendMessage, setPagedataLocalStorage, updatePage]
  );

  const onChangeEnv = useCallback(
    (envinfo, data) => {
      const optPageData = produce(pageData, draft => {
        if (envinfo.name === "mount") {
          draft.mountEnvents = data;
        }
        if (envinfo.name === "unmount") {
          draft.unmountEnvents = data;
        }
      }, createDesc('页面', `修改${envinfo.name}事件`));
      
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChange = useCallback(
    (
      result: any,
      type: StyleType
    ) => {
      if (type === 'backgroundGroup') {
        const optPageData = produce(pageData, draft => {
          draft.style = {
            backgroundGroup: result
          }
        }, createDesc('页面', '修改背景样式'));
        handleUpdatePage(optPageData);
      }
    },
    [handleUpdatePage, pageData]
  );

  const onChangeUnit = useCallback(
    (type: "unit" | "toUnit") => (e: any) => {
      const optPageData = produce(pageData, draft => {
        if (type === "unit") {
          draft.unit = e;
        }
        if (type === "toUnit") {
          draft.toUnit = e;
        }
  
        if (draft.unit !== "rem" && draft.toUnit !== "rem") {
          delete draft.UIWidth;
          delete draft.baseFont;
        } else {
          draft.UIWidth = undefined;
          draft.baseFont = undefined;
        }
      }, createDesc('页面', '修改全局单位'));

      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChangeStatisticsId = useCallback(
    (e) => {
      const optPageData = produce(pageData, draft => {
        draft.statisticsId = e.target.value;
      }, createDesc('页面', '设置百度统计'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChangeUIWidth = useCallback(
    (e) => {
      const optPageData = produce(pageData, draft => {
        draft.UIWidth = e;
      }, createDesc('页面', '设置UI宽度'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChangeBaseFont = useCallback(
    (e) => {
      const optPageData = produce(pageData, draft => {
        draft.baseFont = e;
      }, createDesc('页面', '设置UI基准字体'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChangeApi = useCallback(
    (data) => {
      const optPageData = produce(pageData, draft => {
        draft.onLoadApi = data;
      }, createDesc('页面', '修改Api'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onRemoveApi = useCallback(
    (_, data: ApiType) => {
      const optPageData = produce(pageData, draft => {
        draft.onLoadApi = reject(draft.onLoadApi, {
          apiId: data.apiId,
        });
      }, createDesc('页面', '删除Api'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onPlus = useCallback(() => {
    const optPageData = produce(pageData, draft => {
      draft.onLoadApi?.push({
        name: `ApiBeforMounted`,
        apiId: nanoid(),
      });
    }, createDesc('页面', '新增Api'));
    handleUpdatePage(optPageData);
  }, [handleUpdatePage, pageData]);

  const onChangeRowHeight = useCallback(
    (e) => {
      const optPageData = produce(pageData, draft => {
        draft.rowHeight = e.target.value;
      }, createDesc('页面', '修改删格行高'));
      
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChangeCols = useCallback(
    (e) => {
      const optPageData = produce(pageData, draft => {
        draft.cols = e.target.value;
      }, createDesc('页面', '修改删格列宽'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChangeSpace = useCallback(
    (e) => {
      const optPageData = produce(pageData, draft => {
        draft.space = e.target.value;
      }, createDesc('页面', '修改删格间距'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onChangePageTitle = useCallback(
    (e) => {
      const optPageData = produce(pageData, draft => {
        draft.pageTitle = e.target.value;
      }, createDesc('页面', '修改标题'));
      handleUpdatePage(optPageData);
    },
    [handleUpdatePage, pageData]
  );

  const onShowRunningTimes = useCallback((e) => {
    e.stopPropagation();
    setShowRunningTimes(true);
  }, []);

  const getDefaultData = useCallback(
    (type: StyleType) => pageData.style?.backgroundGroup || {},
    [pageData.style?.backgroundGroup]
  );

  return (
    <>
      <Collapse accordion bordered={false} defaultActiveKey="baseset">
        <Panel
          header="基本信息"
          key="baseset"
          extra={
            <Tooltip title="查看全局发布变量">
              <ClusterOutlined onClick={onShowRunningTimes} />
            </Tooltip>
          }
        >
          <Row gutter={4} className={s.row}>
            <Col className={s.label} span={4}>
              页面名称:
            </Col>
            <Col span={19}>
              <Input
                placeholder="请输入页面名称"
                value={pageData.pageTitle}
                onChange={onChangePageTitle}
                className={s.num}
              />
            </Col>
            <Col span={1} />
          </Row>

          <Row gutter={4} className={s.row}>
            <Col className={s.label} span={4}>
              栅格列数:
            </Col>
            <Col span={7}>
              <Input
                value={pageData.cols}
                onChange={onChangeCols}
                className={s.num}
                placeholder="输入栅格列数"
              />
            </Col>
            <Col className={s.info} span={1}>
              <Tooltip title={<div>屏幕栅格列数&gt;0 (默认12列)</div>}>
                <InfoCircleOutlined />
              </Tooltip>
            </Col>
            <Col className={s.label} span={4}>
              栅格行高:
            </Col>
            <Col span={7}>
              <Input
                value={pageData.rowHeight}
                onChange={onChangeRowHeight}
                placeholder="输入栅格行高"
                className={s.num}
              />
            </Col>
            <Col className={s.info} span={1}>
              <Tooltip
                title={
                  <div>屏幕栅格行高&gt;0(默认20px),可使用运行时window计算高度</div>
                }
              >
                <InfoCircleOutlined />
              </Tooltip>
            </Col>
          </Row>
          <Row gutter={4} className={s.row}>
            <Col className={s.label} span={4}>
              栅格间距:
            </Col>
            <Col span={7}>
              <Input
                value={pageData.space}
                onChange={onChangeSpace}
                className={s.num}
                placeholder="输入栅格间距"
              />
            </Col>
            <Col className={s.info} span={1}>
              <Tooltip title={<div>栅格间距(默认0px)</div>}>
                <InfoCircleOutlined />
              </Tooltip>
            </Col>
          </Row>
          <Row gutter={4} className={s.row}>
            <Col className={s.label} span={4}>
              基准单位:
            </Col>
            <Col span={19}>
              <Select
                placeholder="请选择"
                className={s.select}
                value={pageData.toUnit}
                onChange={onChangeUnit("toUnit")}
              >
                {units.map((item) => (
                  <Option key={item} value={item}>
                    {item}
                  </Option>
                ))}
              </Select>
            </Col>
            <Col className={s.info} span={1}>
              <Tooltip title={<div>页面的基准单位,用于单位换算;设置为rem时需要设置UI宽度和基准像素</div>}>
                <InfoCircleOutlined />
              </Tooltip>
            </Col>
          </Row>

          {pageData.toUnit === "rem" || pageData.unit === "rem" ? (
            <Row gutter={4} className={s.row}>
              <Col className={s.label} span={4}>
                UI宽度:
              </Col>
              <Col span={7}>
                <InputNumber
                  value={pageData.UIWidth}
                  onChange={onChangeUIWidth}
                  placeholder="px"
                  className={s.num}
                />
              </Col>
              <Col className={s.info} span={1}>
                <Tooltip title={<div>UI设计的屏幕宽度(px)</div>}>
                  <InfoCircleOutlined />
                </Tooltip>
              </Col>
              <Col className={s.label} span={4}>
                基准像素:
              </Col>
              <Col span={7}>
                <InputNumber
                  min={5}
                  value={pageData.baseFont}
                  onChange={onChangeBaseFont}
                  placeholder="px"
                  className={s.num}
                />
              </Col>
              <Col className={s.info} span={1}>
                <Tooltip title={<div>UI设计下,1rem=1基准像素</div>}>
                  <InfoCircleOutlined />
                </Tooltip>
              </Col>
            </Row>
          ) : null}
          <StyleContext.Provider
            value={{
              onChange,
              getDefaultData
            }}
          >
          <Row gutter={4} className={s.row}>
            <Col span={24}>
              <div className={s.bg}>
                <BackgroundGroup />
              </div>
            </Col>
            <Col span={1} />
          </Row>
          </StyleContext.Provider>
        </Panel>
        <Panel header="初始化Api" key="pagemount">
          <div className={s.apiwrap}>
            <h4 className={s.apititle}>
              <Button size="small" icon={<PlusOutlined onClick={onPlus} />} />
            </h4>
            <ApiConfig
              sortable
              onRemove={onRemoveApi}
              apiData={pageData.onLoadApi}
              defaultApiData={cloneDeep(pageData.onLoadApi)}
              onChange={onChangeApi}
            />
          </div>
        </Panel>
        <Panel header="页面事件" key="pageevent">
          <div className={s.events}>
            <>
              {Output.exposeEvents?.map((item, index) => (
                <EventGroup
                  key={index}
                  eventName={item.name}
                  value={
                    item.name === "mount"
                      ? pageData.mountEnvents || []
                      : pageData.unmountEnvents || []
                  }
                  curentEventInfomation={item}
                  onPlay={() => {}}
                  onChange={onChangeEnv}
                />
              ))}
            </>
          </div>
        </Panel>
        <Panel header="百度统计" key="pagecounter">
          <Row className={s.row}>
            <Col className={s.label} span={4}>
              统计Id:
            </Col>
            <Col span={20}>
              <Input
                placeholder="请在百度账号下创建站点,获取统计Id"
                className={s.num}
                value={pageData.statisticsId}
                onChange={onChangeStatisticsId}
              />
            </Col>
          </Row>
        </Panel>
        <Panel header="数据视图" key="pagedata">
          <ReactJson src={{pageData, appData}} collapsed={1} name="project" />
        </Panel>
      </Collapse>
      <RunningTimesModal
        visible={showRunningTimes}
        data={runningTimes}
        onCancel={() => setShowRunningTimes(false)}
      />
    </>
  );
}
Example #26
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function index(props: IProps) {
  const { preNamePrefix = [], namePrefix = ['options', 'valueMappings'] } = props;

  return (
    <Panel header='值映射'>
      <Form.List name={namePrefix}>
        {(fields, { add, remove }) => (
          <>
            <Button
              style={{ width: '100%', marginBottom: 10 }}
              onClick={() => {
                add({
                  type: 'special',
                });
              }}
            >
              添加
            </Button>
            {_.isEmpty(fields) ? null : (
              <Row gutter={10}>
                <Col flex='290px'>
                  <Tooltip
                    overlayInnerStyle={{
                      width: 300,
                    }}
                    title={
                      <div>
                        <div>范围值说明: from &gt;= value &lt;= to</div>
                        <div>范围值默认值: from=-Infinity; to=Infinity </div>
                        <div>特殊值Null说明: 匹配值为 null 或 undefined 或 no data</div>
                      </div>
                    }
                  >
                    条件 <InfoCircleOutlined />
                  </Tooltip>
                </Col>
                <Col flex='210'>显示文字</Col>
                <Col flex='45'>颜色</Col>
                <Col flex='50'>操作</Col>
              </Row>
            )}

            {fields.map(({ key, name, ...restField }) => {
              return (
                <Row key={key} gutter={10} style={{ marginBottom: 10 }}>
                  <Col flex='290px'>
                    <Row gutter={10}>
                      <Col flex='80px'>
                        <Form.Item noStyle {...restField} name={[name, 'type']}>
                          <Select suffixIcon={<CaretDownOutlined />} style={{ width: 80 }}>
                            <Select.Option value='special'>固定值</Select.Option>
                            <Select.Option value='range'>范围值</Select.Option>
                            <Select.Option value='specialValue'>特殊值</Select.Option>
                          </Select>
                        </Form.Item>
                      </Col>
                      <Col flex='1'>
                        <Form.Item noStyle {...restField} shouldUpdate={(prevValues, curValues) => _.get(prevValues, [name, 'type']) !== _.get(curValues, [name, 'type'])}>
                          {({ getFieldValue }) => {
                            const type = getFieldValue([...preNamePrefix, ...namePrefix, name, 'type']);
                            if (type === 'special') {
                              return (
                                <Form.Item noStyle {...restField} name={[name, 'match', 'special']}>
                                  <InputNumber style={{ width: '100%' }} />
                                </Form.Item>
                              );
                            }
                            if (type === 'range') {
                              return (
                                <Row gutter={10}>
                                  <Col span={12}>
                                    <Form.Item noStyle {...restField} name={[name, 'match', 'from']}>
                                      <InputNumber placeholder='from' />
                                    </Form.Item>
                                  </Col>
                                  <Col span={12}>
                                    <Form.Item noStyle {...restField} name={[name, 'match', 'to']}>
                                      <InputNumber placeholder='to' />
                                    </Form.Item>
                                  </Col>
                                </Row>
                              );
                            }
                            if (type === 'specialValue') {
                              return (
                                <Form.Item noStyle {...restField} name={[name, 'match', 'specialValue']}>
                                  <Select suffixIcon={<CaretDownOutlined />}>
                                    <Select.Option value='null'>Null</Select.Option>
                                    <Select.Option value='empty'>Empty string</Select.Option>
                                  </Select>
                                </Form.Item>
                              );
                            }
                            return null;
                          }}
                        </Form.Item>
                      </Col>
                    </Row>
                  </Col>
                  <Col flex='210'>
                    <Form.Item noStyle {...restField} name={[name, 'result', 'text']}>
                      <Input placeholder='可选' />
                    </Form.Item>
                  </Col>
                  <Col flex='45'>
                    <Form.Item noStyle {...restField} name={[name, 'result', 'color']}>
                      <ColorPicker />
                    </Form.Item>
                  </Col>
                  <Col flex='50'>
                    <Button
                      onClick={() => {
                        remove(name);
                      }}
                      icon={<DeleteOutlined />}
                    />
                  </Col>
                </Row>
              );
            })}
          </>
        )}
      </Form.List>
    </Panel>
  );
}
Example #27
Source File: ArgumentsSetting.tsx    From yugong with MIT License 4 votes vote down vote up
ArgumentsSetting: React.FC<Props> = ({
  visible,
  argumentsData,
  initArgumentData,
  onOk,
  onCancel,
  title,
  dataFlexible = false,
  headerFlexible = false,
  forceUpdate,
  visiableRunningTimeIcon = true,
}) => {
  const runningTimes = useSelector((state: RootState) => state.runningTimes);
  const [argumentState, setArgumentState] = useState<ArgumentsItem[]>([]);
  const [showRunningTimes, setShowRunningTimes] = useState(false);
  const forceUpdateByStateTag =
    useDispatch<Dispatch>().controller.forceUpdateByStateTag;
  // 将argument数据接管
  useEffect(() => {
    let data: ArgumentsItem[] = [...(argumentsData || [])];
    // 不可自定义参数且数据为空时,使用组件初始数据
    if (data.length === 0) {
      data = [...(initArgumentData || [])];
    }
    setArgumentState(data);
  }, [argumentsData, headerFlexible, initArgumentData]);

  // 弹窗确定收集编辑完毕的argument数据
  const onModalOk = useCallback(() => {
    if (onOk instanceof Function) {
      // todo 确定页面为何强制更新??
      if (forceUpdate) {
        forceUpdateByStateTag();
      }
      onOk(argumentState);
    }
  }, [onOk, forceUpdateByStateTag, argumentState, forceUpdate]);

  // number
  const onChangeInput = useCallback(
    (index: number, isSelect?: boolean) => (e: any) => {
      const result = cloneDeep(argumentState);
      result[index].data = isSelect ? e : e.target.value;
      setArgumentState(result);
    },
    [argumentState],
  );

  const onChangeRunningTime = useCallback(
    (index: number) => (e: any) => {
      const result = [...argumentState];
      result[index].data = e;
      setArgumentState(result);
    },
    [argumentState],
  );

  const onChangeObjType = useCallback(
    (index: number) => (data: ArgumentsItem) => {
      const result = [...argumentState];
      result[index] = data;
      setArgumentState(result);
    },
    [argumentState],
  );

  const onChangeFieldName = useCallback(
    (index: number) => (e: any) => {
      const result = [...argumentState];
      result[index].fieldName = e.target.value;
      result[index].name = e.target.value;
      setArgumentState(result);
    },
    [argumentState],
  );

  const onChangeDescribe = useCallback(
    (index: number) => (e: any) => {
      const result = [...argumentState];
      result[index].describe = e.target.value;
      setArgumentState(result);
    },
    [argumentState],
  );

  // 移除字段
  const onRemove = useCallback(
    (index: number) => () => {
      let result = [...argumentState];
      result = result.filter((_, i) => i !== index);
      setArgumentState(result);
    },
    [argumentState],
  );

  // 新增字段
  const onAddField = useCallback(() => {
    const result = [...argumentState];
    result.push({
      describe: undefined,
      name: '未命名',
      fieldName: '',
      type: 'string',
      data: '',
    });
    setArgumentState(result);
  }, [argumentState]);

  // 修改字段类型
  const onChangeArgType = useCallback(
    (index: number) => (e: any) => {
      const result = [...argumentState];
      result[index].type = e;
      switch (e) {
        case 'runningTime':
        case 'string':
        case 'number':
          result[index].data = '';
          break;
        case 'array':
          result[index].data = [];
          break;
        case 'object':
          result[index].data = {};
          break;
        case 'boolean':
          result[index].data = {
            comparableAverageA: null,
            comparableAverageB: null,
            method: '===',
          };
          break;

        default:
          break;
      }
      setArgumentState(result);
    },
    [argumentState],
  );

  const onClickShowGloabVar = useCallback(() => {
    console.log(runningTimes);
  }, [runningTimes]);

  const renderNumberString = (
    item: ArgumentsString | ArgumentsNumber,
    index: number,
  ) => {
    // 下拉选择形式
    if (item?.select) {
      const { select } = item;
      const keys = Object.keys(select);
      console.log('select', select);
      console.log('keys', keys);
      return (
        <Select
          onChange={onChangeInput(index, true)}
          value={item.data}
          className={s.select}
          placeholder={`请输入值,${item.describe || ''}`}
        >
          {keys.map((value) => (
            <Select.Option key={value} value={value}>
              {select[value]}
            </Select.Option>
          ))}
        </Select>
      );
    }
    // 输入框形式
    return (
      <Input
        onChange={onChangeInput(index)}
        placeholder={`请输入值,${item.describe || ''}`}
        value={item.data}
        type="text"
        suffix={!!item.html ? <HtmlSuffix /> : null}
      />
    );
  };

  return (
    <>
      <Modal
        title={
          <div className={s.title}>
            <h4>
              {title}{' '}
              {visiableRunningTimeIcon ? <Button
                type="text"
                onClick={onClickShowGloabVar}
                icon={
                  <Tooltip title="查看全局发布变量">
                    <ClusterOutlined
                      onClick={() => setShowRunningTimes(true)}
                    />
                  </Tooltip>
                }
              /> : null}
            </h4>
            <div className={s.right}>
              {headerFlexible ? (
                <Button onClick={onAddField}>新增</Button>
              ) : null}
            </div>
          </div>
        }
        visible={visible}
        onOk={onModalOk}
        onCancel={onCancel}
        bodyStyle={{ padding: '10px' }}
        okText="确定"
        cancelText="取消"
      >
        {argumentState.map((item, index) => {
          const initItem = initArgumentData?.length
            ? initArgumentData[index]
            : undefined;
          return (
            <Card
              className={classNames(s.card, {
                [s.mixedcard]: item.type === 'mixed',
              })}
              key={`${index}`}
              title={
                <div className={s.cardtitle}>
                  <div className={s.cardtitleinfo}>
                    {!headerFlexible ? (
                      <>
                        <span className={s.label}>名称:</span>
                        {item.name || initItem?.name || ''} &nbsp;
                        <Tooltip
                          title={parse(
                            item.describe || initItem?.describe || '',
                          )}
                        >
                          <InfoCircleOutlined
                            style={{
                              color: 'rgba(0,0,0,.45)',
                            }}
                          />
                        </Tooltip>
                      </>
                    ) : (
                      <>
                        <span className={s.label}>字段:</span>
                        <Input
                          className={s.title}
                          value={item.fieldName || initItem?.fieldName || ''}
                          placeholder="限数字或字母"
                          onChange={onChangeFieldName(index)}
                          suffix={
                            <Tooltip
                              title={
                                <Input
                                  className={s.desc}
                                  placeholder="新增字段描述"
                                  value={item.describe}
                                  style={{
                                    width: '200px',
                                  }}
                                  onChange={onChangeDescribe(index)}
                                />
                              }
                            >
                              <InfoCircleOutlined
                                style={{
                                  color: 'rgba(0,0,0,.45)',
                                }}
                              />
                            </Tooltip>
                          }
                        />
                      </>
                    )}
                    <span className={s.divide} />
                    <span className={s.label}>类型:</span>
                    {headerFlexible ? (
                      <Select
                        className={s.type}
                        value={item.type}
                        onChange={onChangeArgType(index)}
                      >
                        <Select.Option value="string">string</Select.Option>
                        <Select.Option value="number">number</Select.Option>
                        <Select.Option value="boolean">boolean</Select.Option>
                        <Select.Option value="object">object</Select.Option>
                        <Select.Option value="array">array</Select.Option>
                        <Select.Option value="mixed">mixed</Select.Option>
                        <Select.Option value="runningTime">
                          runningTime
                        </Select.Option>
                      </Select>
                    ) : (
                      item.type
                    )}
                  </div>
                  <div>
                    {headerFlexible ? (
                      <Button onClick={onRemove(index)}>移除</Button>
                    ) : null}
                  </div>
                </div>
              }
            >
              <div>
                {item.type === 'number' || item.type === 'string'
                  ? renderNumberString(item, index)
                  : null}
                {item.type === 'runningTime' ? (
                  <Select
                    className={s.select}
                    placeholder="请选择"
                    showSearch
                    value={item.data}
                    optionFilterProp="children"
                    filterOption={
                      (input, option) => {
                        const str = option?.children?.join('').toLowerCase();
                        if (str?.indexOf(input) !== -1) {
                          return true;
                        }
                        return false;
                      }
                      // option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
                    }
                    onChange={onChangeRunningTime(index)}
                  >
                    {Object.keys(runningTimes)?.map(
                      (optionsIitem, optionsIitemIndex) => (
                        <Select.Option
                          key={optionsIitemIndex}
                          value={optionsIitem}
                        >
                          {optionsIitem}
                        </Select.Option>
                      ),
                    )}
                  </Select>
                ) : null}
                {item.type === 'object' ? (
                  <ObjectArguments
                    describe={item.describe}
                    htmlInput={!!item.html}
                    onChange={onChangeObjType(index)}
                    typeArguments={item}
                    flexible={!!dataFlexible}
                  />
                ) : null}
                {item.type === 'array' ? (
                  <ArrayArguments
                    htmlInput={!!item.html}
                    describe={item.describe}
                    onChange={onChangeObjType(index)}
                    typeArguments={item}
                    flexible={!!dataFlexible}
                  />
                ) : null}
                {item.type === 'boolean' ? (
                  <BooleanArguments
                    onChange={onChangeObjType(index)}
                    typeArguments={item}
                    flexible={!!dataFlexible}
                  />
                ) : null}
                {item.type === 'mixed' ? (
                  <MixedArguments
                    onChange={onChangeObjType(index)}
                    typeArguments={item}
                    flexible={!!dataFlexible}
                  />
                ) : null}
              </div>
            </Card>
          );
        })}
      </Modal>
      <RunningTimesModal
        visible={showRunningTimes}
        data={runningTimes}
        onCancel={() => setShowRunningTimes(false)}
      />
    </>
  );
}
Example #28
Source File: groups.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
Resource: React.FC = () => {
  const { t } = useTranslation();
  const { type } =
    useParams<{
      type: string;
    }>();
  const [activeKey, setActiveKey] = useState<UserType>(UserType.Team);
  const [visible, setVisible] = useState<boolean>(false);
  const [action, setAction] = useState<ActionType>();
  const [userId, setUserId] = useState<string>('');
  const [teamId, setTeamId] = useState<string>('');
  const [memberId, setMemberId] = useState<string>('');
  const [memberList, setMemberList] = useState<User[]>([]);
  const [allMemberList, setAllMemberList] = useState<User[]>([]);
  const [teamInfo, setTeamInfo] = useState<Team>();
  const [teamList, setTeamList] = useState<Team[]>([]);
  const [memberLoading, setMemberLoading] = useState<boolean>(false);
  const [searchValue, setSearchValue] = useState<string>('');
  const [searchMemberValue, setSearchMemberValue] = useState<string>('');
  const userRef = useRef(null as any);
  let { profile } = useSelector<RootState, accountStoreState>((state) => state.account);
  const userColumn: ColumnsType<User> = [
    {
      title: t('用户名'),
      dataIndex: 'username',
      ellipsis: true,
    },
    {
      title: t('显示名'),
      dataIndex: 'nickname',
      ellipsis: true,
      render: (text: string, record) => record.nickname || '-',
    },
    {
      title: t('邮箱'),
      dataIndex: 'email',
      render: (text: string, record) => record.email || '-',
    },
    {
      title: t('手机'),
      dataIndex: 'phone',
      render: (text: string, record) => record.phone || '-',
    },
  ];

  const teamMemberColumns: ColumnsType<User> = [
    ...userColumn,
    {
      title: t('操作'),
      width: '100px',
      render: (
        text: string,
        record, // <DelPopover
      ) => (
        //   teamId={teamId}
        //   memberId={record.id}
        //   userType='member'
        //   onClose={() => handleClose()}
        // ></DelPopover>
        <a
          style={{
            color: 'red',
          }}
          onClick={() => {
            let params = {
              ids: [record.id],
            };
            confirm({
              title: t('是否删除该成员'),
              onOk: () => {
                deleteMember(teamId, params).then((_) => {
                  message.success(t('成员删除成功'));
                  handleClose('updateMember');
                });
              },
              onCancel: () => {},
            });
          }}
        >
          {t('删除')}
        </a>
      ),
    },
  ];

  useEffect(() => {
    getList(true);
  }, []); //teamId变化触发

  useEffect(() => {
    if (teamId) {
      getTeamInfoDetail(teamId);
    }
  }, [teamId]);

  const getList = (isDeleteOrAdd = false) => {
    getTeamList('', isDeleteOrAdd);
  };

  // 获取团队列表
  const getTeamList = (search?: string, isDelete?: boolean) => {
    getTeamInfoList({ query: search || '' }).then((data) => {
      setTeamList(data.dat || []);
      if ((!teamId || isDelete) && data.dat.length > 0) {
        setTeamId(data.dat[0].id);
      }
    });
  };

  // 获取团队详情
  const getTeamInfoDetail = (id: string) => {
    setMemberLoading(true);
    getTeamInfo(id).then((data: TeamInfo) => {
      setTeamInfo(data.user_group);
      setMemberList(data.users);
      setAllMemberList(data.users);
      setMemberLoading(false);
    });
  };

  const handleSearch = (type?: string, val?: string) => {
    if (type === 'team') {
      getTeamList(val);
    } else {
      if (!val) {
        getTeamInfoDetail(teamId);
      } else {
        setMemberLoading(true);
        let newList = allMemberList.filter(
          (item) =>
            item.username.indexOf(val) !== -1 ||
            item.nickname.indexOf(val) !== -1 ||
            item.id.toString().indexOf(val) !== -1 ||
            item.phone.indexOf(val) !== -1 ||
            item.email.indexOf(val) !== -1,
        );
        setMemberList(newList);
        setMemberLoading(false);
      }
    }
  };

  const handleClick = (type: ActionType, id?: string, memberId?: string) => {
    if (id) {
      setTeamId(id);
    } else {
      setTeamId('');
    }

    if (memberId) {
      setMemberId(memberId);
    } else {
      setMemberId('');
    }

    setAction(type);
    setVisible(true);
  };

  // 弹窗关闭回调
  const handleClose = (isDeleteOrAdd: boolean | string = false) => {
    setVisible(false);
    if (searchValue) {
      handleSearch('team', searchValue);
    } else {
      // 添加、删除成员 不用获取列表
      if (isDeleteOrAdd !== 'updateMember') {
        getList(isDeleteOrAdd !== 'updateName'); // 修改名字,不用选中第一个
      }
    }
    if (teamId && (isDeleteOrAdd === 'update' || isDeleteOrAdd === 'updateMember' || isDeleteOrAdd === 'updateName')) {
      getTeamInfoDetail(teamId);
    }
  };

  return (
    <PageLayout title={t('团队管理')} icon={<UserOutlined />} hideCluster>
      <div className='user-manage-content'>
        <div style={{ display: 'flex', height: '100%' }}>
          <div className='left-tree-area'>
            <div className='sub-title'>
              {t('团队列表')}
              <Button
                style={{
                  height: '30px',
                }}
                size='small'
                type='link'
                onClick={() => {
                  handleClick(ActionType.CreateTeam);
                }}
              >
                {t('新建团队')}
              </Button>
            </div>
            <div style={{ display: 'flex', margin: '5px 0px 12px' }}>
              <Input
                prefix={<SearchOutlined />}
                value={searchValue}
                onChange={(e) => {
                  setSearchValue(e.target.value);
                }}
                placeholder={t('搜索团队名称')}
                onPressEnter={(e) => {
                  // @ts-ignore
                  getTeamList(e.target.value);
                }}
                onBlur={(e) => {
                  // @ts-ignore
                  getTeamList(e.target.value);
                }}
              />
            </div>

            <List
              style={{
                marginBottom: '12px',
                flex: 1,
                overflow: 'auto',
              }}
              dataSource={teamList}
              size='small'
              renderItem={(item) => (
                <List.Item key={item.id} className={teamId === item.id ? 'is-active' : ''} onClick={() => setTeamId(item.id)}>
                  {item.name}
                </List.Item>
              )}
            />
          </div>
          {teamList.length > 0 ? (
            <div className='resource-table-content'>
              <Row className='team-info'>
                <Col
                  span='24'
                  style={{
                    color: '#000',
                    fontSize: '14px',
                    fontWeight: 'bold',
                    display: 'inline',
                  }}
                >
                  {teamInfo && teamInfo.name}
                  <EditOutlined
                    title={t('刷新')}
                    style={{
                      marginLeft: '8px',
                      fontSize: '14px',
                    }}
                    onClick={() => handleClick(ActionType.EditTeam, teamId)}
                  ></EditOutlined>
                  <DeleteOutlined
                    style={{
                      marginLeft: '8px',
                      fontSize: '14px',
                    }}
                    onClick={() => {
                      confirm({
                        title: t('是否删除该团队'),
                        onOk: () => {
                          deleteTeam(teamId).then((_) => {
                            message.success(t('团队删除成功'));
                            handleClose(true);
                          });
                        },
                        onCancel: () => {},
                      });
                    }}
                  />
                </Col>
                <Col
                  style={{
                    marginTop: '8px',
                    color: '#666',
                  }}
                >
                  {t('备注')}:{teamInfo && teamInfo.note ? teamInfo.note : '-'}
                </Col>
              </Row>
              <Row justify='space-between' align='middle'>
                <Col span='12'>
                  <Input
                    prefix={<SearchOutlined />}
                    value={searchMemberValue}
                    className={'searchInput'}
                    onChange={(e) => setSearchMemberValue(e.target.value)}
                    placeholder={t('用户名、显示名、邮箱或手机')}
                    onPressEnter={(e) => handleSearch('member', searchMemberValue)}
                  />
                </Col>
                <Button
                  type='primary'
                  ghost
                  onClick={() => {
                    handleClick(ActionType.AddUser, teamId);
                  }}
                >
                  {t('添加成员')}
                </Button>
              </Row>

              <Table rowKey='id' columns={teamMemberColumns} dataSource={memberList} loading={memberLoading} />
            </div>
          ) : (
            <div className='blank-busi-holder'>
              <p style={{ textAlign: 'left', fontWeight: 'bold' }}>
                <InfoCircleOutlined style={{ color: '#1473ff' }} /> {t('提示信息')}
              </p>
              <p>
                没有与您相关的团队,请先&nbsp;
                <a onClick={() => handleClick(ActionType.CreateTeam)}>创建团队</a>
              </p>
            </div>
          )}
        </div>
        <UserInfoModal
          visible={visible}
          action={action as ActionType}
          width={500}
          userType={activeKey}
          onClose={handleClose}
          onSearch={(val) => {
            setSearchValue(val);
            handleSearch('team', val);
          }}
          userId={userId}
          teamId={teamId}
          memberId={memberId}
        />
      </div>
    </PageLayout>
  );
}
Example #29
Source File: Tabs.tsx    From datart with Apache License 2.0 4 votes vote down vote up
Tabs = memo(() => {
  const [operatingView, setOperatingView] = useState<null | ViewViewModel>(
    null,
  );
  const [confirmVisible, setConfirmVisible] = useState(false);
  const dispatch = useDispatch();
  const history = useHistory();
  const { editorInstance } = useContext(EditorContext);
  const orgId = useSelector(selectOrgId);
  const editingViews = useSelector(selectEditingViews);
  const id = useSelector(state =>
    selectCurrentEditingViewAttr(state, { name: 'id' }),
  ) as string;
  const t = useI18NPrefix('view.tabs');

  const redirect = useCallback(
    currentEditingViewKey => {
      if (currentEditingViewKey) {
        history.push(`/organizations/${orgId}/views/${currentEditingViewKey}`);
      } else {
        history.push(`/organizations/${orgId}/views`);
      }
    },
    [history, orgId],
  );

  const tabChange = useCallback(
    activeKey => {
      if (id !== activeKey) {
        history.push(`/organizations/${orgId}/views/${activeKey}`);
      }
    },
    [history, id, orgId],
  );

  const tabEdit = useCallback(
    (targetKey, action) => {
      const view = editingViews.find(v => v.id === targetKey);

      switch (action) {
        case 'remove':
          if (view!.touched === false) {
            dispatch(removeEditingView({ id: targetKey, resolve: redirect }));
          } else {
            setOperatingView(view!);
            setConfirmVisible(true);
          }
          break;
        default:
          break;
      }
    },
    [dispatch, editingViews, redirect],
  );

  const hideConfirm = useCallback(() => {
    setConfirmVisible(false);
  }, []);

  const removeTab = useCallback(() => {
    dispatch(removeEditingView({ id: operatingView!.id, resolve: redirect }));
    setConfirmVisible(false);
  }, [dispatch, operatingView, redirect]);

  const runTab = useCallback(() => {
    const fragment = editorInstance
      ?.getModel()
      ?.getValueInRange(editorInstance.getSelection()!);
    setConfirmVisible(false);
    dispatch(runSql({ id, isFragment: !!fragment }));
  }, [dispatch, id, editorInstance]);

  return (
    <Wrapper>
      <TabsComponent
        hideAdd
        type="editable-card"
        activeKey={id}
        onChange={tabChange}
        onEdit={tabEdit}
      >
        {editingViews.map(({ id, name, touched, stage, error }) => (
          <TabPane
            key={id}
            tab={error ? <span css={errorColor}>{name}</span> : name}
            closeIcon={
              <CloseIcon touched={touched} stage={stage} error={!!error} />
            }
          />
        ))}
      </TabsComponent>
      <Confirm
        visible={confirmVisible}
        title={t('warning')}
        icon={<InfoCircleOutlined style={{ color: ORANGE }} />}
        footer={
          <Space>
            <Button onClick={removeTab}>{t('discard')}</Button>
            <Button onClick={hideConfirm}>{t('cancel')}</Button>
            <Button onClick={runTab} type="primary">
              {t('execute')}
            </Button>
          </Space>
        }
      />
    </Wrapper>
  );
})