@ant-design/icons#CaretDownOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#CaretDownOutlined. 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: Experiment.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function CodeLanguageSelect(): JSX.Element {
    return (
        <Select defaultValue="JavaScript" suffixIcon={<CaretDownOutlined />}>
            <Select.Option value="JavaScript">
                <Row align="middle">
                    <IconJavascript style={{ marginRight: 6 }} /> JavaScript
                </Row>
            </Select.Option>
        </Select>
    )
}
Example #2
Source File: TaskStatusCell.tsx    From RareCamp with Apache License 2.0 6 votes vote down vote up
export default function TaskStatusCell({ task, programId }) {
  const [showEdit, setShowEdit] = useState(false)
  return (
    <td
      onFocus={() => setShowEdit(true)}
      onBlur={() => setShowEdit(false)}
      onMouseOver={() => setShowEdit(true)}
      onMouseOut={() => setShowEdit(false)}
      className="ant-table-cell"
      style={{ width: 130 }}
    >
      <TaskStatus task={task} programId={programId} />
      <CaretDownOutlined
        style={{
          position: 'absolute',
          visibility: showEdit ? 'visible' : 'hidden',
        }}
      />
    </td>
  )
}
Example #3
Source File: index.tsx    From fe-v5 with Apache License 2.0 6 votes vote down vote up
export default function OrderSort(props: Props) {
  const { t } = useTranslation();
  const { onChange, showLabel } = props;
  const [isDesc, setIsDesc] = useState<Boolean>(true);

  const handleClick = (e) => {
    setIsDesc(!isDesc);
    onChange(!isDesc);
    e.preventDefault()
  };

  return (
    <div className='desc-sort'>
      {showLabel && (isDesc ? t('降序') : t('升序'))}

      <div className='desc-sort-icon' onClick={handleClick}>
        <CaretUpOutlined
          style={{
            color: isDesc === false ? 'blue' : '',
          }}
        />
        <CaretDownOutlined
          style={{
            color: isDesc === true ? 'blue' : '',
            marginTop: '-0.3em',
          }}
        />
      </div>
    </div>
  );
}
Example #4
Source File: makeDropdownWidget.tsx    From imove with MIT License 6 votes vote down vote up
makeDropdownWidget = (options: IOptions) => {
  const Widget: React.FC<IDropdownWidgetProps> = (props) => {
    const { flowChart } = props;
    const { tooltip, getIcon, getOverlay, handler } = options;
    const iconWrapperCls = [styles.btnWidget];
    let { disabled = false } = options;
    if (typeof disabled === 'function') {
      disabled = disabled(flowChart);
      disabled && iconWrapperCls.push(styles.disabled);
    }
    const onChange = (data: any): void => {
      if (disabled) return;
      handler(flowChart, data);
      flowChart.trigger('toolBar:forceUpdate');
    };
    return (
      <Tooltip title={tooltip}>
        <Dropdown
          disabled={disabled}
          overlay={getOverlay(flowChart, onChange)}
          trigger={['click']}
        >
          <div className={iconWrapperCls.join(' ')}>
            {getIcon(flowChart)} <CaretDownOutlined className={styles.caret} />
          </div>
        </Dropdown>
      </Tooltip>
    );
  };
  return Widget;
}
Example #5
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,
                }}
                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 #6
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function index({ targets }) {
  const namePrefix = ['overrides'];

  return (
    <Form.List name={namePrefix}>
      {(fields, { add, remove }) => (
        <>
          {fields.map(({ key, name, ...restField }) => {
            return (
              <Panel
                isInner
                header='override'
                extra={
                  <Space>
                    <PlusCircleOutlined
                      onClick={() => {
                        add({
                          type: 'special',
                        });
                      }}
                    />
                    {fields.length > 1 && (
                      <MinusCircleOutlined
                        onClick={() => {
                          remove(name);
                        }}
                      />
                    )}
                  </Space>
                }
              >
                <Form.Item label='查询条件名称' {...restField} name={[name, 'matcher', 'value']}>
                  <Select suffixIcon={<CaretDownOutlined />} allowClear>
                    {_.map(targets, (target) => {
                      return (
                        <Select.Option key={target.refId} value={target.refId}>
                          {target.refId}
                        </Select.Option>
                      );
                    })}
                  </Select>
                </Form.Item>
                <ValueMappings preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'valueMappings']} />
                <StandardOptions preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'standardOptions']} />
              </Panel>
            );
          })}
        </>
      )}
    </Form.List>
  );
}
Example #7
Source File: GraphStyles.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function GraphStyles() {
  const namePrefix = ['custom'];

  return (
    <Panel header='图表样式'>
      <>
        <Row gutter={10}>
          <Col span={12}>
            <Form.Item label='取值计算' name={[...namePrefix, 'calc']}>
              <Select suffixIcon={<CaretDownOutlined />}>
                {_.map(calcsOptions, (item, key) => {
                  return (
                    <Select.Option key={key} value={key}>
                      {item.name}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='图例位置' name={[...namePrefix, 'legengPosition']}>
              <Select suffixIcon={<CaretDownOutlined />}>
                {legendPostion.map((item) => {
                  return (
                    <Select.Option key={item} value={item}>
                      {item}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='最多展示块数' name={[...namePrefix, 'max']} tooltip='超过的块数则合并展示为其他'>
              <InputNumber style={{ width: '100%' }} />
            </Form.Item>
          </Col>
        </Row>
      </>
    </Panel>
  );
}
Example #8
Source File: GraphStyles.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function GraphStyles() {
  const namePrefix = ['custom'];

  return (
    <Panel header='图表样式'>
      <>
        <Row gutter={10}>
          <Col span={12}>
            <Form.Item label='显示内容' name={[...namePrefix, 'textMode']}>
              <Radio.Group buttonStyle='solid'>
                <Radio.Button value='valueAndName'>名称和值</Radio.Button>
                <Radio.Button value='value'>值</Radio.Button>
              </Radio.Group>
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='颜色模式' name={[...namePrefix, 'colorMode']}>
              <Radio.Group buttonStyle='solid'>
                <Radio.Button value='value'>值</Radio.Button>
                <Radio.Button value='background'>背景</Radio.Button>
              </Radio.Group>
            </Form.Item>
          </Col>
        </Row>
        <Row gutter={10}>
          <Col span={12}>
            <Form.Item label='取值计算' name={[...namePrefix, 'calc']}>
              <Select suffixIcon={<CaretDownOutlined />}>
                {_.map(calcsOptions, (item, key) => {
                  return (
                    <Select.Option key={key} value={key}>
                      {item.name}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='每行最多显示' name={[...namePrefix, 'colSpan']}>
              <Select suffixIcon={<CaretDownOutlined />}>
                {_.map(colSpans, (item) => {
                  return (
                    <Select.Option key={item} value={item}>
                      {item}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
          </Col>
        </Row>
        <Row gutter={10}>
          <Col span={12}>
            <Form.Item label='标题字体大小' name={[...namePrefix, 'textSize', 'title']}>
              <InputNumber placeholder='auto' style={{ width: '100%' }} min={12} max={100} />
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='值字体大小' name={[...namePrefix, 'textSize', 'value']}>
              <InputNumber placeholder='auto' style={{ width: '100%' }} min={12} max={100} />
            </Form.Item>
          </Col>
        </Row>
      </>
    </Panel>
  );
}
Example #9
Source File: GraphStyles.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function GraphStyles() {
  const namePrefix = ['custom'];

  return (
    <Panel header='图表样式'>
      <>
        <Row gutter={10}>
          <Col span={12}>
            <Form.Item label='取值计算' name={[...namePrefix, 'calc']}>
              <Select suffixIcon={<CaretDownOutlined />}>
                {_.map(calcsOptions, (item, key) => {
                  return (
                    <Select.Option key={key} value={key}>
                      {item.name}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='图例位置' name={[...namePrefix, 'legengPosition']}>
              <Select suffixIcon={<CaretDownOutlined />}>
                {legendPostion.map((item) => {
                  return (
                    <Select.Option key={item} value={item}>
                      {item}
                    </Select.Option>
                  );
                })}
              </Select>
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='最多展示块数' name={[...namePrefix, 'max']} tooltip='超过的块数则合并展示为其他'>
              <InputNumber style={{ width: '100%' }} />
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label='label是否包含名称' name={[...namePrefix, 'labelWithName']} valuePropName='checked'>
              <Switch />
            </Form.Item>
          </Col>
        </Row>
      </>
    </Panel>
  );
}
Example #10
Source File: GraphStyles.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function GraphStyles() {
  const namePrefix = ['custom'];

  return (
    <Panel header='图表样式'>
      <>
        <Row gutter={10}>
          <Col span={12}>
            <Form.Item label='显示表头' name={[...namePrefix, 'showHeader']} valuePropName='checked'>
              <Switch size='small' />
            </Form.Item>
          </Col>
        </Row>
        <Form.Item label='取值计算' name={[...namePrefix, 'calc']}>
          <Select suffixIcon={<CaretDownOutlined />}>
            {_.map(calcsOptions, (item, key) => {
              return (
                <Select.Option key={key} value={key}>
                  {item.name}
                </Select.Option>
              );
            })}
          </Select>
        </Form.Item>
        <Row gutter={10}>
          <Col span={12}>
            <Form.Item label='显示模式' name={[...namePrefix, 'displayMode']}>
              <Select suffixIcon={<CaretDownOutlined />}>
                <Select.Option value='seriesToRows'>每行展示 serie 的值</Select.Option>
                <Select.Option value='labelValuesToRows'>每行展示指定聚合维度的值</Select.Option>
              </Select>
            </Form.Item>
          </Col>
          <Form.Item noStyle shouldUpdate={(prevValues, curValues) => _.get(prevValues, [...namePrefix, 'displayMode']) !== _.get(curValues, [...namePrefix, 'displayMode'])}>
            {({ getFieldValue }) => {
              if (getFieldValue([...namePrefix, 'displayMode']) === 'labelValuesToRows') {
                return (
                  <Col span={12}>
                    <Form.Item label='显示维度' name={[...namePrefix, 'aggrDimension']}>
                      <Input />
                    </Form.Item>
                  </Col>
                );
              }
              return null;
            }}
          </Form.Item>
        </Row>
      </>
    </Panel>
  );
}
Example #11
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 #12
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function index({ targets }) {
  const namePrefix = ['overrides'];

  return (
    <Form.List name={namePrefix}>
      {(fields, { add, remove }) =>
        fields.map(({ key, name, ...restField }) => {
          return (
            <Panel
              key={key}
              isInner
              header='override'
              extra={
                <Space>
                  <PlusCircleOutlined
                    onClick={() => {
                      add({
                        type: 'special',
                      });
                    }}
                  />
                  {fields.length > 1 && (
                    <MinusCircleOutlined
                      onClick={() => {
                        remove(name);
                      }}
                    />
                  )}
                </Space>
              }
            >
              <Form.Item label='查询条件名称' {...restField} name={[name, 'matcher', 'value']}>
                <Select suffixIcon={<CaretDownOutlined />} allowClear>
                  {_.map(targets, (target) => {
                    return (
                      <Select.Option key={target.refId} value={target.refId}>
                        {target.refId}
                      </Select.Option>
                    );
                  })}
                </Select>
              </Form.Item>
              <ValueMappings preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'valueMappings']} />
              <StandardOptions preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'standardOptions']} />
            </Panel>
          );
        })
      }
    </Form.List>
  );
}
Example #13
Source File: tagItem.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
TagItem: React.FC<Itag> = ({ field, remove, form }) => {
  const { t } = useTranslation();
  const [valuePlaceholder, setValuePlaceholder] = useState<string>('');
  const [funcCur, setfuncCur] = useState('==');

  useEffect(() => {
    const tags = form.getFieldValue('tags');
    funcChange(tags[field.name].func);
  }, [field]);

  const funcChange = (val) => {
    let text = '';
    if (val === 'in') {
      text = '可以输入多个值,用回车分割';
    } else if (val === '=~') {
      text = '请输入正则表达式匹配标签value';
    }
    setfuncCur(val);
    setValuePlaceholder(text);
  };
  return (
    <>
      <Row gutter={[10, 10]} style={{ marginBottom: '10px' }}>
        <Col span={5}>
          <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'key']} fieldKey={[field.name, 'key']} rules={[{ required: true, message: t('key不能为空') }]}>
            <Input placeholder={t('请输入屏蔽事件标签key')} />
          </Form.Item>
        </Col>
        <Col span={3}>
          <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'func']} fieldKey={[field.name, 'func']} initialValue='=='>
            <Select suffixIcon={<CaretDownOutlined />} onChange={funcChange}>
              <Option value='=='>==</Option>
              <Option value='=~'>=~</Option>
              <Option value='in'>in</Option>
            </Select>
          </Form.Item>
        </Col>
        <Col span={15}>
          <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'value']} fieldKey={[field.name, 'value']} rules={[{ required: true, message: t('value不能为空') }]}>
            {funcCur == 'in' ? (
              <Select mode='tags' open={false} style={{ width: '100%' }} placeholder={t(valuePlaceholder)} tokenSeparators={[' ']}></Select>
            ) : (
              <Input className='ant-input' placeholder={t(valuePlaceholder)} />
            )}
          </Form.Item>
        </Col>
        <Col>
          <MinusCircleOutlined style={{ marginTop: '8px' }} onClick={() => remove(field.name)} />
        </Col>
      </Row>
    </>
  );
}
Example #14
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
export default function ColumnSelect(props: Props) {
  const { onSeverityChange, onEventTypeChange, onBusiGroupChange, onClusterChange, noLeftPadding, noRightPadding = true } = props;
  const { clusters, busiGroups } = useSelector<RootState, CommonStoreState>((state) => state.common);
  const [filteredBusiGroups, setFilteredBusiGroups] = useState(busiGroups);
  const fetchBusiGroup = (e) => {
    getBusiGroups(e).then((res) => {
      setFilteredBusiGroups(res.dat || []);
    });
  };
  const handleSearch = useCallback(debounce(fetchBusiGroup, 800), []);

  return (
    <Space style={{ marginLeft: noLeftPadding ? 0 : 8, marginRight: noRightPadding ? 0 : 8 }}>
      {onClusterChange && (
        <Select mode='multiple' allowClear style={{ minWidth: 80 }} placeholder='集群' onChange={onClusterChange} getPopupContainer={() => document.body}>
          {clusters.map((k) => (
            <Select.Option value={k} key={k}>
              {k}
            </Select.Option>
          ))}
        </Select>
      )}
      {onBusiGroupChange && (
        <Select
          allowClear
          showSearch
          style={{ minWidth: 120 }}
          placeholder='业务组'
          dropdownClassName='overflow-586'
          filterOption={false}
          onSearch={handleSearch}
          getPopupContainer={() => document.body}
          onFocus={() => {
            getBusiGroups('').then((res) => {
              setFilteredBusiGroups(res.dat || []);
            });
          }}
          onClear={() => {
            getBusiGroups('').then((res) => {
              setFilteredBusiGroups(res.dat || []);
            });
          }}
          onChange={onBusiGroupChange}
        >
          {filteredBusiGroups.map((item) => (
            <Select.Option value={item.id} key={item.id}>
              {item.name}
            </Select.Option>
          ))}
        </Select>
      )}
      {onSeverityChange && (
        <Select suffixIcon={<CaretDownOutlined />} allowClear style={{ minWidth: 80 }} placeholder='事件级别' onChange={onSeverityChange} getPopupContainer={() => document.body}>
          <Select.Option value={1}>一级告警</Select.Option>
          <Select.Option value={2}>二级告警</Select.Option>
          <Select.Option value={3}>三级告警</Select.Option>
        </Select>
      )}
      {onEventTypeChange && (
        <Select suffixIcon={<CaretDownOutlined />} allowClear style={{ minWidth: 80 }} placeholder='事件类别' onChange={onEventTypeChange} getPopupContainer={() => document.body}>
          <Select.Option value={0}>Triggered</Select.Option>
          <Select.Option value={1}>Recovered</Select.Option>
        </Select>
      )}
    </Space>
  );
}
Example #15
Source File: FormDataItem.tsx    From yugong with MIT License 5 votes vote down vote up
FormDataItem: React.FC<Props> = ({ onMinus, value, order }) => {
  const { onChangeRunningData, runningData, dataPath } = useContext(FormModuleContext)
  const onChange = useCallback(
    (data: {[keys: string]: any}) => {
      if (!runningData) return;
      const operateData = cloneDeep(runningData)
      const itemPath = `${dataPath}[${order - 1}]`;
      const itemData = get(operateData, itemPath);
      const newItemData = {
        ...itemData,
        ...data
      }
      set(operateData, itemPath, newItemData);
      onChangeRunningData?.(operateData)
    },
    [dataPath, onChangeRunningData, order, runningData],
  )
  
  const [showOptions, setShowOptions] = useState(false);
  const disabled = false;
  return (
    <div className={s.root}>
      <LineItem
        label={
          <div className={s.dragwrap}>
            <span className={s.drag}>
              <DragHandle />
            </span>
            第{order}项
          </div>
        }
      >
        <Input
          disabled={disabled}
          className={s.inp}
          onChange={(e) => onChange({title: e.target.value})}
          value={value.title}
          placeholder="名称"
        />
        <Input name="rowMap"
          disabled={disabled}
          className={classNames(s.inp, s.nbl, s.nbrad)}
          onChange={(e) => onChange({dataIndex: e.target.value})}
          value={value?.dataIndex}
          placeholder="字段(必填)"
          />
        <Button
          disabled={disabled}
          className={classNames(s.btn, s.nbl, s.nbr)}
          icon={showOptions ? <CaretDownOutlined /> : <CaretRightOutlined />}
          onClick={() => setShowOptions(!showOptions)}
        />
        <Button
          disabled={disabled}
          className={s.btn}
          icon={<MinusOutlined />}
          onClick={() => onMinus?.()}
        />
      </LineItem>
      <div style={{ display: showOptions ? 'block' : 'none' }}>
        <LineItem label="">
          <SubItem value={value} onChange={onChange} />
        </LineItem>
      </div>
    </div>
  );
}
Example #16
Source File: TaskDateCell.tsx    From RareCamp with Apache License 2.0 5 votes vote down vote up
export default function TaskDateCell({ task, programId, dateKey }) {
  const [isDatePickerOpen, setIsDatePickerOpen] = useState(false)
  const [showEdit, setShowEdit] = useState(false)
  const updateTaskMutation = useEditTaskMutation({
    programId,
    taskId: task.taskId,
    projectId: task.projectId,
  })
  return (
    <DateCell
      onFocus={() => setShowEdit(true)}
      onBlur={() => setShowEdit(false)}
      onMouseOver={() => setShowEdit(true)}
      onMouseOut={() => setShowEdit(false)}
      className="ant-table-cell"
      onClick={() => setIsDatePickerOpen(!isDatePickerOpen)}
      onKeyPress={() => setIsDatePickerOpen(!isDatePickerOpen)}
      tabIndex={task.taskId}
    >
      <Space>
        <span>
          {task[dateKey]
            ? dayjs(task[dateKey]).format('DD/MM/YYYY')
            : ''}
        </span>
        <CaretDownOutlined
          style={{
            visibility: showEdit ? 'visible' : 'hidden',
            top: 22,
          }}
        />
        <LoadingOutlined
          style={{
            position: 'absolute',
            visibility: updateTaskMutation.isLoading
              ? 'visible'
              : 'hidden',
            top: 22,
          }}
        />
      </Space>
      <DatePicker
        open={isDatePickerOpen}
        onOpenChange={(open) => setIsDatePickerOpen(open)}
        onChange={(date) => {
          if (date) {
            updateTaskMutation.mutate({
              [dateKey]: date?.toDate(),
            })
          }
        }}
      />
    </DateCell>
  )
}
Example #17
Source File: tagItem.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
TagItem: React.FC<Itag> = ({ field, remove, form }) => {
  const { t } = useTranslation();
  const [valuePlaceholder, setValuePlaceholder] = useState<string>('');
  const [funcCur, setfuncCur] = useState('==');

  useEffect(() => {
    const tags = form.getFieldValue('tags');
    funcChange(tags[field.name].func);
  }, []);

  const funcChange = (val) => {
    let text = '';
    if (val === 'in') {
      text = '可以输入多个值,用回车分割';
    } else if (val === '=~') {
      text = '请输入正则表达式匹配标签value';
    }
    setfuncCur(val);
    setValuePlaceholder(text);
  };
  return (
    <>
      <Row gutter={[10, 10]} style={{ marginBottom: '10px' }}>
        <Col span={5}>
          <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'key']} fieldKey={[field.name, 'key']} rules={[{ required: true, message: t('key不能为空') }]}>
            <Input placeholder={t('请输入订阅事件标签key')} />
          </Form.Item>
        </Col>
        <Col span={3}>
          <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'func']} fieldKey={[field.name, 'func']} initialValue='=='>
            <Select suffixIcon={<CaretDownOutlined />} onChange={funcChange}>
              <Option value='=='>==</Option>
              <Option value='=~'>=~</Option>
              <Option value='in'>in</Option>
            </Select>
          </Form.Item>
        </Col>
        <Col span={15}>
          <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'value']} fieldKey={[field.name, 'value']} rules={[{ required: true, message: t('value不能为空') }]}>
            {funcCur == 'in' ? (
              <Select mode='tags' open={false} style={{ width: '100%' }} placeholder={t(valuePlaceholder)} tokenSeparators={[' ']}></Select>
            ) : (
              <Input className='ant-input' placeholder={t(valuePlaceholder)} />
            )}
          </Form.Item>
        </Col>
        <Col>
          <MinusCircleOutlined style={{ marginTop: '8px' }} onClick={() => remove(field.name)} />
        </Col>
      </Row>
    </>
  );
}
Example #18
Source File: UpgradeSection.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function UpgradeSection(): JSX.Element {
    const { checkForUpdates, toggleSectionOpen } = useActions(pluginsLogic)
    const { sectionsOpen } = useValues(pluginsLogic)
    const { user } = useValues(userLogic)

    const {
        filteredPluginsNeedingUpdates,
        pluginsNeedingUpdates,
        checkingForUpdates,
        installedPluginUrls,
        updateStatus,
        rearranging,
        hasUpdatablePlugins,
    } = useValues(pluginsLogic)

    const upgradeButton = canInstallPlugins(user?.organization) && hasUpdatablePlugins && (
        <Button
            type="default"
            icon={pluginsNeedingUpdates.length > 0 ? <SyncOutlined /> : <CloudDownloadOutlined />}
            onClick={(e) => {
                e.stopPropagation()
                checkForUpdates(true)
            }}
            loading={checkingForUpdates}
        >
            {checkingForUpdates
                ? `Checking plugin ${Object.keys(updateStatus).length + 1} out of ${
                      Object.keys(installedPluginUrls).length
                  }`
                : pluginsNeedingUpdates.length > 0
                ? 'Check again for updates'
                : 'Check for updates'}
        </Button>
    )

    return (
        <>
            <div
                className="plugins-installed-tab-section-header"
                onClick={() => toggleSectionOpen(PluginSection.Upgrade)}
            >
                <Subtitle
                    subtitle={
                        <>
                            {sectionsOpen.includes(PluginSection.Upgrade) ? (
                                <CaretDownOutlined />
                            ) : (
                                <CaretRightOutlined />
                            )}
                            {` Plugins to update (${filteredPluginsNeedingUpdates.length})`}
                        </>
                    }
                    buttons={!rearranging && sectionsOpen.includes(PluginSection.Upgrade) && upgradeButton}
                />
            </div>
            {sectionsOpen.includes(PluginSection.Upgrade) ? (
                <>
                    {pluginsNeedingUpdates.length > 0 ? (
                        <Row gutter={16} style={{ marginTop: 16 }}>
                            {filteredPluginsNeedingUpdates.length > 0 ? (
                                <>
                                    {filteredPluginsNeedingUpdates.map((plugin) => (
                                        <InstalledPlugin key={plugin.id} plugin={plugin} showUpdateButton />
                                    ))}
                                </>
                            ) : (
                                <p style={{ margin: 10 }}>No plugins match your search.</p>
                            )}
                        </Row>
                    ) : (
                        <p style={{ margin: 10 }}>All your plugins are up to date. Great work!</p>
                    )}
                </>
            ) : null}
        </>
    )
}
Example #19
Source File: DisabledPluginsSection.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function DisabledPluginSection(): JSX.Element {
    const { filteredDisabledPlugins, sectionsOpen, disabledPlugins } = useValues(pluginsLogic)
    const { toggleSectionOpen } = useActions(pluginsLogic)

    if (disabledPlugins.length === 0) {
        return <></>
    }

    return (
        <>
            <div
                className="plugins-installed-tab-section-header"
                onClick={() => toggleSectionOpen(PluginSection.Disabled)}
            >
                <Subtitle
                    subtitle={
                        <>
                            {sectionsOpen.includes(PluginSection.Disabled) ? (
                                <CaretDownOutlined />
                            ) : (
                                <CaretRightOutlined />
                            )}
                            {` Installed plugins (${filteredDisabledPlugins.length})`}
                        </>
                    }
                />
            </div>
            {sectionsOpen.includes(PluginSection.Disabled) ? (
                <>
                    {filteredDisabledPlugins.length > 0 ? (
                        <Row gutter={16} style={{ marginTop: 16 }}>
                            {filteredDisabledPlugins.map((plugin) => (
                                <InstalledPlugin key={plugin.id} plugin={plugin} />
                            ))}
                        </Row>
                    ) : (
                        <p style={{ margin: 10 }}>No plugins match your search.</p>
                    )}
                </>
            ) : null}
        </>
    )
}
Example #20
Source File: editModal.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
editModal: React.FC<Props> = ({ isModalVisible, editModalFinish }) => {
  const { t, i18n } = useTranslation();

  const [form] = Form.useForm();
  const { clusters: clusterList } = useSelector<RootState, CommonStoreState>((state) => state.common);

  const [contactList, setInitContactList] = useState([]);
  const [notifyGroups, setNotifyGroups] = useState([]);

  const [field, setField] = useState<string>('cluster');
  const [refresh, setRefresh] = useState(true);

  useEffect(() => {
    getNotifyChannel();
    getGroups('');

    return () => {};
  }, []);

  const enableDaysOfWeekOptions = [t('周日'), t('周一'), t('周二'), t('周三'), t('周四'), t('周五'), t('周六')].map((v, i) => {
    return <Option value={String(i)} key={i}>{`${v}`}</Option>;
  });

  const contactListCheckboxes = contactList.map((c: { key: string; label: string }) => (
    <Checkbox value={c.key} key={c.label}>
      {c.label}
    </Checkbox>
  ));

  const notifyGroupsOptions = notifyGroups.map((ng: any) => (
    <Option value={ng.id} key={ng.id}>
      {ng.name}
    </Option>
  ));

  const getNotifyChannel = async () => {
    const res = await getNotifiesList();
    let contactList = res || [];
    setInitContactList(contactList);
  };

  const getGroups = async (str) => {
    const res = await getTeamInfoList({ query: str });
    const data = res.dat || res;
    setNotifyGroups(data || []);
  };

  const debounceFetcher = useCallback(debounce(getGroups, 800), []);

  const modelOk = () => {
    form.validateFields().then(async (values) => {
      const data = { ...values };
      switch (values.field) {
        case 'enable_time':
          data.enable_stime = values.enable_time[0].format('HH:mm');
          data.enable_etime = values.enable_time[1].format('HH:mm');
          delete data.enable_time;
          break;
        case 'disabled':
          data.disabled = !values.enable_status ? 1 : 0;
          delete data.enable_status;
          break;
        case 'enable_in_bg':
          data.enable_in_bg = values.enable_in_bg ? 1 : 0;
          break;
        case 'callbacks':
          data.callbacks = values.callbacks.map((item) => item.url);
          break;
        case 'notify_recovered':
          data.notify_recovered = values.notify_recovered ? 1 : 0;
          break;
        default:
          break;
      }
      delete data.field;
      Object.keys(data).forEach((key) => {
        // 因为功能上有清除备注的需求,需要支持传空
        if (data[key] === undefined) {
          data[key] = '';
        }
        if (Array.isArray(data[key])) {
          data[key] = data[key].join(' ');
        }
      });
      editModalFinish(true, data);
    });
  };

  const editModalClose = () => {
    editModalFinish(false);
  };

  const fieldChange = (val) => {
    setField(val);
  };

  return (
    <>
      <Modal
        title={t('批量更新')}
        visible={isModalVisible}
        onOk={modelOk}
        onCancel={() => {
          editModalClose();
        }}
      >
        <Form
          {...layout}
          form={form}
          className='strategy-form'
          layout={refresh ? 'horizontal' : 'horizontal'}
          initialValues={{
            prom_eval_interval: 15,
            disabled: 0, // 0:立即启用 1:禁用
            enable_status: true, // true:立即启用 false:禁用
            notify_recovered: 1, // 1:启用
            enable_time: [moment('00:00', 'HH:mm'), moment('23:59', 'HH:mm')],
            cluster: clusterList[0] || 'Default', // 生效集群
            enable_days_of_week: ['1', '2', '3', '4', '5', '6', '0'],
            field: 'cluster',
          }}
        >
          <Form.Item
            label={t('字段:')}
            name='field'
            rules={[
              {
                required: false,
              },
            ]}
          >
            <Select suffixIcon={<CaretDownOutlined />} style={{ width: '100%' }} onChange={fieldChange}>
              {fields.map((item) => (
                <Option key={item.id} value={item.field}>
                  {item.name}
                </Option>
              ))}
            </Select>
          </Form.Item>
          {(() => {
            switch (field) {
              case 'note':
                return (
                  <>
                    <Form.Item
                      label={t('改为:')}
                      name='note'
                      rules={[
                        {
                          required: false,
                        },
                      ]}
                    >
                      <Input placeholder={t('请输入规则备注')} />
                    </Form.Item>
                  </>
                );
              case 'runbook_url':
                return (
                  <>
                    <Form.Item label={t('改为:')} name='runbook_url'>
                      <Input />
                    </Form.Item>
                  </>
                );

              case 'cluster':
                return (
                  <>
                    <Form.Item
                      label={t('改为:')}
                      name='cluster'
                      rules={[
                        {
                          required: false,
                          message: t('生效集群不能为空'),
                        },
                      ]}
                    >
                      <Select suffixIcon={<CaretDownOutlined />}>
                        {clusterList?.map((item) => (
                          <Option value={item} key={item}>
                            {item}
                          </Option>
                        ))}
                      </Select>
                    </Form.Item>
                  </>
                );
              case 'severity':
                return (
                  <>
                    <Form.Item
                      label={t('改为:')}
                      name='severity'
                      initialValue={2}
                      rules={[
                        {
                          required: false,
                          message: t('告警级别不能为空'),
                        },
                      ]}
                    >
                      <Radio.Group>
                        <Radio value={1}>{t('一级报警')}</Radio>
                        <Radio value={2}>{t('二级报警')}</Radio>
                        <Radio value={3}>{t('三级报警')}</Radio>
                      </Radio.Group>
                    </Form.Item>
                  </>
                );
              case 'disabled':
                return (
                  <>
                    <Form.Item
                      label={t('改为:')}
                      name='enable_status'
                      rules={[
                        {
                          required: false,
                          message: t('立即启用不能为空'),
                        },
                      ]}
                      valuePropName='checked'
                    >
                      <Switch />
                    </Form.Item>
                  </>
                );
              case 'enable_in_bg':
                return (
                  <>
                    <Form.Item label={t('改为:')} name='enable_in_bg' valuePropName='checked'>
                      <SwitchWithLabel label='根据告警事件中的ident归属关系判断' />
                    </Form.Item>
                  </>
                );
              case 'prom_eval_interval':
                return (
                  <>
                    <Form.Item
                      label={t('改为:')}
                      rules={[
                        {
                          required: false,
                          message: t('执行频率不能为空'),
                        },
                      ]}
                    >
                      <Space>
                        <Form.Item style={{ marginBottom: 0 }} name='prom_eval_interval' initialValue={15} wrapperCol={{ span: 10 }}>
                          <InputNumber
                            min={1}
                            onChange={(val) => {
                              setRefresh(!refresh);
                            }}
                          />
                        </Form.Item>
                        秒
                        <Tooltip title={t(`每隔${form.getFieldValue('prom_eval_interval')}秒,把PromQL作为查询条件,去查询后端存储,如果查到了数据就表示当次有监控数据触发了规则`)}>
                          <QuestionCircleFilled />
                        </Tooltip>
                      </Space>
                    </Form.Item>
                  </>
                );
              case 'prom_for_duration':
                return (
                  <>
                    <Form.Item
                      label={t('改为:')}
                      rules={[
                        {
                          required: false,
                          message: t('持续时长不能为空'),
                        },
                      ]}
                    >
                      <Space>
                        <Form.Item style={{ marginBottom: 0 }} name='prom_for_duration' initialValue={60} wrapperCol={{ span: 10 }}>
                          <InputNumber min={0} />
                        </Form.Item>
                        秒
                        <Tooltip
                          title={t(
                            `通常持续时长大于执行频率,在持续时长内按照执行频率多次执行PromQL查询,每次都触发才生成告警;如果持续时长置为0,表示只要有一次PromQL查询触发阈值,就生成告警`,
                          )}
                        >
                          <QuestionCircleFilled />
                        </Tooltip>
                      </Space>
                    </Form.Item>
                  </>
                );
              case 'notify_channels':
                return (
                  <>
                    <Form.Item label={t('改为:')} name='notify_channels'>
                      <Checkbox.Group>{contactListCheckboxes}</Checkbox.Group>
                    </Form.Item>
                  </>
                );
              case 'notify_groups':
                return (
                  <>
                    <Form.Item label={t('改为:')} name='notify_groups'>
                      <Select mode='multiple' showSearch optionFilterProp='children' filterOption={false} onSearch={(e) => debounceFetcher(e)} onBlur={() => getGroups('')}>
                        {notifyGroupsOptions}
                      </Select>
                    </Form.Item>
                  </>
                );
              case 'notify_recovered':
                return (
                  <>
                    <Form.Item label={t('改为:')} name='notify_recovered' valuePropName='checked'>
                      <Switch />
                    </Form.Item>
                  </>
                );
              case 'recover_duration':
                return (
                  <>
                    <Form.Item label={t('改为:')}>
                      <Space>
                        <Form.Item
                          style={{ marginBottom: 0 }}
                          name='recover_duration'
                          initialValue={0}
                          wrapperCol={{ span: 10 }}
                          rules={[
                            {
                              required: false,
                              message: t('留观时长不能为空'),
                            },
                          ]}
                        >
                          <InputNumber
                            min={0}
                            onChange={(val) => {
                              setRefresh(!refresh);
                            }}
                          />
                        </Form.Item>
                        秒
                        <Tooltip title={t(`持续${form.getFieldValue('recover_duration') || 0}秒没有再次触发阈值才发送恢复通知`)}>
                          <QuestionCircleFilled />
                        </Tooltip>
                      </Space>
                    </Form.Item>
                  </>
                );
              case 'notify_repeat_step':
                return (
                  <>
                    <Form.Item label={t('改为:')}>
                      <Space>
                        <Form.Item
                          style={{ marginBottom: 0 }}
                          name='notify_repeat_step'
                          initialValue={60}
                          wrapperCol={{ span: 10 }}
                          rules={[
                            {
                              required: false,
                              message: t('重复发送频率不能为空'),
                            },
                          ]}
                        >
                          <InputNumber
                            min={0}
                            onChange={(val) => {
                              setRefresh(!refresh);
                            }}
                          />
                        </Form.Item>
                        分钟
                        <Tooltip title={t(`如果告警持续未恢复,间隔${form.getFieldValue('notify_repeat_step')}分钟之后重复提醒告警接收组的成员`)}>
                          <QuestionCircleFilled />
                        </Tooltip>
                      </Space>
                    </Form.Item>
                  </>
                );
              case 'callbacks':
                return (
                  <>
                    <Form.Item label={t('改为:')}>
                      <Form.List name='callbacks' initialValue={[{}]}>
                        {(fields, { add, remove }, { errors }) => (
                          <>
                            {fields.map((field, index) => (
                              <Row gutter={[10, 0]} key={field.key}>
                                <Col span={22}>
                                  <Form.Item name={[field.name, 'url']}>
                                    <Input />
                                  </Form.Item>
                                </Col>

                                <Col span={1}>
                                  <MinusCircleOutlined className='control-icon-normal' onClick={() => remove(field.name)} />
                                </Col>
                              </Row>
                            ))}
                            <PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
                          </>
                        )}
                      </Form.List>
                    </Form.Item>
                  </>
                );
              case 'append_tags':
                return (
                  <>
                    <Form.Item label='附加标签' name='append_tags' rules={[{ required: false, message: '请填写至少一项标签!' }, isValidFormat]}>
                      <Select mode='tags' tokenSeparators={[' ']} open={false} placeholder={'标签格式为 key=value ,使用回车或空格分隔'} tagRender={tagRender} />
                    </Form.Item>
                  </>
                );
              case 'enable_time':
                return (
                  <>
                    <Form.Item
                      label={t('改为:')}
                      name='enable_days_of_week'
                      rules={[
                        {
                          required: false,
                          message: t('生效时间不能为空'),
                        },
                      ]}
                    >
                      <Select mode='tags'>{enableDaysOfWeekOptions}</Select>
                    </Form.Item>
                    <Form.Item
                      name='enable_time'
                      {...tailLayout}
                      rules={[
                        {
                          required: false,
                          message: t('生效时间不能为空'),
                        },
                      ]}
                    >
                      <TimePicker.RangePicker
                        format='HH:mm'
                        onChange={(val, val2) => {
                          form.setFieldsValue({
                            enable_stime: val2[0],
                            enable_etime: val2[1],
                          });
                        }}
                      />
                    </Form.Item>
                  </>
                );
              default:
                return null;
            }
          })()}
        </Form>
      </Modal>
    </>
  );
}
Example #21
Source File: operateForm.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
operateForm: React.FC<Props> = ({ type, detail = {} }) => {
  const { t, i18n } = useTranslation();
  const history = useHistory(); // 创建的时候默认选中的值

  const [form] = Form.useForm();
  const { clusters: clusterList } = useSelector<RootState, CommonStoreState>((state) => state.common);
  const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);

  const [contactList, setInitContactList] = useState([]);
  const [notifyGroups, setNotifyGroups] = useState<any[]>([]);
  const [initVal, setInitVal] = useState<any>({});
  const [refresh, setRefresh] = useState(true);
  useEffect(() => {
    getNotifyChannel();
    getGroups('');

    return () => {};
  }, []);

  useEffect(() => {
    const data = {
      ...detail,
      enable_time: detail?.enable_stime ? [detail.enable_stime, detail.enable_etime] : [],
      enable_status: detail?.disabled === undefined ? true : !detail?.disabled,
    };
    setInitVal(data);
    if (type == 1) {
      const groups = (detail.notify_groups_obj ? detail.notify_groups_obj.filter((item) => !notifyGroups.find((i) => item.id === i.id)) : []).concat(notifyGroups);
      setNotifyGroups(groups);
    }
  }, [JSON.stringify(detail)]);

  const enableDaysOfWeekOptions = [t('周日'), t('周一'), t('周二'), t('周三'), t('周四'), t('周五'), t('周六')].map((v, i) => {
    return <Option value={String(i)} key={i}>{`${v}`}</Option>;
  });

  const contactListCheckboxes = contactList.map((c: { key: string; label: string }) => (
    <Checkbox value={c.key} key={c.label}>
      {c.label}
    </Checkbox>
  ));

  const notifyGroupsOptions = notifyGroups.map((ng: any) => (
    <Option value={String(ng.id)} key={ng.id}>
      {ng.name}
    </Option>
  ));

  const getNotifyChannel = async () => {
    const res = await getNotifiesList();
    let contactList = res || [];
    setInitContactList(contactList);
  };

  const getGroups = async (str) => {
    const res = await getTeamInfoList({ query: str });
    const data = res.dat || res;
    const combineData = (detail.notify_groups_obj ? detail.notify_groups_obj.filter((item) => !data.find((i) => item.id === i.id)) : []).concat(data);
    setNotifyGroups(combineData || []);
  };

  const addSubmit = () => {
    form.validateFields().then(async (values) => {
      const res = await prometheusQuery({ query: values.prom_ql }, values.cluster);
      if (res.error) {
        notification.error({
          message: res.error,
        });
        return false;
      }
      const callbacks = values.callbacks.map((item) => item.url);
      const data = {
        ...values,
        enable_stime: values.enable_time[0].format('HH:mm'),
        enable_etime: values.enable_time[1].format('HH:mm'),
        disabled: !values.enable_status ? 1 : 0,
        notify_recovered: values.notify_recovered ? 1 : 0,
        enable_in_bg: values.enable_in_bg ? 1 : 0,
        callbacks,
      };
      let reqBody,
        method = 'Post';
      if (type === 1) {
        reqBody = data;
        method = 'Put';
        const res = await EditStrategy(reqBody, curBusiItem.id, detail.id);
        if (res.err) {
          message.error(res.error);
        } else {
          message.success(t('编辑成功!'));
          history.push('/alert-rules');
        }
      } else {
        reqBody = [data];
        const { dat } = await addOrEditStrategy(reqBody, curBusiItem.id, method);
        let errorNum = 0;
        const msg = Object.keys(dat).map((key) => {
          dat[key] && errorNum++;
          return dat[key];
        });

        if (!errorNum) {
          message.success(`${type === 2 ? t('告警规则克隆成功') : t('告警规则创建成功')}`);
          history.push('/alert-rules');
        } else {
          message.error(t(msg));
        }
      }
    });
  };

  const debounceFetcher = useCallback(debounce(getGroups, 800), []);
  return (
    <div className='operate_con'>
      <Form
        {...layout}
        form={form}
        className='strategy-form'
        layout={refresh ? 'horizontal' : 'horizontal'}
        initialValues={{
          prom_eval_interval: 15,
          prom_for_duration: 60,
          severity: 2,
          disabled: 0, // 0:立即启用 1:禁用  待修改
          // notify_recovered: 1, // 1:启用
          cluster: clusterList[0] || 'Default', // 生效集群
          enable_days_of_week: ['1', '2', '3', '4', '5', '6', '0'],
          ...detail,
          enable_in_bg: detail?.enable_in_bg === 1,
          enable_time: detail?.enable_stime ? [moment(detail.enable_stime, 'HH:mm'), moment(detail.enable_etime, 'HH:mm')] : [moment('00:00', 'HH:mm'), moment('23:59', 'HH:mm')],
          enable_status: detail?.disabled === undefined ? true : !detail?.disabled,
          notify_recovered: detail?.notify_recovered === 1 || detail?.notify_recovered === undefined ? true : false, // 1:启用 0:禁用
          recover_duration: detail?.recover_duration || 0,
          callbacks: !!detail?.callbacks
            ? detail.callbacks.map((item) => ({
                url: item,
              }))
            : [{}],
        }}
      >
        <Space direction='vertical' style={{ width: '100%' }}>
          <Card title={t('基本配置')}>
            <Form.Item
              label={t('规则标题:')}
              name='name'
              rules={[
                {
                  required: true,
                  message: t('规则标题不能为空'),
                },
              ]}
            >
              <Input placeholder={t('请输入规则标题')} />
            </Form.Item>
            <Form.Item
              label={t('规则备注:')}
              name='note'
              rules={[
                {
                  required: false,
                },
              ]}
            >
              <Input placeholder={t('请输入规则备注')} />
            </Form.Item>
            <Form.Item
              label={t('告警级别')}
              name='severity'
              rules={[
                {
                  required: true,
                  message: t('告警级别不能为空'),
                },
              ]}
            >
              <Radio.Group>
                <Radio value={1}>{t('一级报警')}</Radio>
                <Radio value={2}>{t('二级报警')}</Radio>
                <Radio value={3}>{t('三级报警')}</Radio>
              </Radio.Group>
            </Form.Item>
            <Form.Item
              label={t('生效集群')}
              name='cluster'
              rules={[
                {
                  required: true,
                  message: t('生效集群不能为空'),
                },
              ]}
            >
              <Select suffixIcon={<CaretDownOutlined />}>
                {clusterList?.map((item) => (
                  <Option value={item} key={item}>
                    {item}
                  </Option>
                ))}
              </Select>
            </Form.Item>
            <AdvancedWrap>
              <AbnormalDetection form={form} />
            </AdvancedWrap>
            <Form.Item noStyle shouldUpdate={(prevValues, curValues) => prevValues.cluster !== curValues.cluster}>
              {() => {
                return (
                  <Form.Item label='PromQL' className={'Promeql-content'} required>
                    <Form.Item name='prom_ql' validateTrigger={['onBlur']} trigger='onChange' rules={[{ required: true, message: t('请输入PromQL') }]}>
                      <PromQLInput
                        url='/api/n9e/prometheus'
                        headers={{
                          'X-Cluster': form.getFieldValue('cluster'),
                          Authorization: `Bearer ${localStorage.getItem('access_token') || ''}`,
                        }}
                      />
                    </Form.Item>
                  </Form.Item>
                );
              }}
            </Form.Item>
            <Form.Item required label={t('执行频率')}>
              <Space>
                <Form.Item
                  style={{ marginBottom: 0 }}
                  name='prom_eval_interval'
                  initialValue={15}
                  wrapperCol={{ span: 10 }}
                  rules={[
                    {
                      required: true,
                      message: t('执行频率不能为空'),
                    },
                  ]}
                >
                  <InputNumber
                    min={1}
                    onChange={(val) => {
                      setRefresh(!refresh);
                    }}
                  />
                </Form.Item>
                秒
                <Tooltip title={t(`每隔${form.getFieldValue('prom_eval_interval')}秒,把PromQL作为查询条件,去查询后端存储,如果查到了数据就表示当次有监控数据触发了规则`)}>
                  <QuestionCircleFilled />
                </Tooltip>
              </Space>
            </Form.Item>
            <Form.Item
              required
              label={t('持续时长')}
              rules={[
                {
                  required: true,
                  message: t('持续时长不能为空'),
                },
              ]}
            >
              <Space>
                <Form.Item style={{ marginBottom: 0 }} name='prom_for_duration' wrapperCol={{ span: 10 }}>
                  <InputNumber min={0} />
                </Form.Item>
                秒
                <Tooltip
                  title={t(
                    `通常持续时长大于执行频率,在持续时长内按照执行频率多次执行PromQL查询,每次都触发才生成告警;如果持续时长置为0,表示只要有一次PromQL查询触发阈值,就生成告警`,
                  )}
                >
                  <QuestionCircleFilled />
                </Tooltip>
              </Space>
            </Form.Item>
            <Form.Item label='附加标签' name='append_tags' rules={[{ required: false, message: '请填写至少一项标签!' }, isValidFormat]}>
              <Select mode='tags' tokenSeparators={[' ']} open={false} placeholder={'标签格式为 key=value ,使用回车或空格分隔'} tagRender={tagRender} />
            </Form.Item>
            <Form.Item label={t('预案链接')} name='runbook_url'>
              <Input />
            </Form.Item>
          </Card>
          <Card title={t('生效配置')}>
            <Form.Item
              label={t('立即启用')}
              name='enable_status'
              rules={[
                {
                  required: true,
                  message: t('立即启用不能为空'),
                },
              ]}
              valuePropName='checked'
            >
              <Switch />
            </Form.Item>
            <Form.Item
              label={t('生效时间')}
              name='enable_days_of_week'
              rules={[
                {
                  required: true,
                  message: t('生效时间不能为空'),
                },
              ]}
            >
              <Select mode='tags'>{enableDaysOfWeekOptions}</Select>
            </Form.Item>
            <Form.Item
              name='enable_time'
              {...tailLayout}
              rules={[
                {
                  required: true,
                  message: t('生效时间不能为空'),
                },
              ]}
            >
              <TimePicker.RangePicker
                format='HH:mm'
                onChange={(val, val2) => {
                  form.setFieldsValue({
                    enable_stime: val2[0],
                    enable_etime: val2[1],
                  });
                }}
              />
            </Form.Item>
            <Form.Item label={t('仅在本业务组生效')} name='enable_in_bg' valuePropName='checked'>
              <SwitchWithLabel label='根据告警事件中的ident归属关系判断' />
            </Form.Item>
          </Card>
          <Card title={t('通知配置')}>
            <Form.Item label={t('通知媒介')} name='notify_channels'>
              <Checkbox.Group>{contactListCheckboxes}</Checkbox.Group>
            </Form.Item>
            <Form.Item label={t('告警接收组')} name='notify_groups'>
              <Select mode='multiple' showSearch optionFilterProp='children' filterOption={false} onSearch={(e) => debounceFetcher(e)} onBlur={() => getGroups('')}>
                {notifyGroupsOptions}
              </Select>
            </Form.Item>
            <Form.Item label={t('启用恢复通知')}>
              <Space>
                <Form.Item name='notify_recovered' valuePropName='checked' style={{ marginBottom: 0 }}>
                  <Switch />
                </Form.Item>
                <Tooltip title={t(`告警恢复时也发送通知`)}>
                  <QuestionCircleFilled />
                </Tooltip>
              </Space>
            </Form.Item>
            <Form.Item label={t('留观时长')} required>
              <Space>
                <Form.Item style={{ marginBottom: 0 }} name='recover_duration' initialValue={0} wrapperCol={{ span: 10 }}>
                  <InputNumber
                    min={0}
                    onChange={(val) => {
                      setRefresh(!refresh);
                    }}
                  />
                </Form.Item>
                秒
                <Tooltip title={t(`持续${form.getFieldValue('recover_duration')}秒没有再次触发阈值才发送恢复通知`)}>
                  <QuestionCircleFilled />
                </Tooltip>
              </Space>
            </Form.Item>
            <Form.Item label={t('重复发送频率')} required>
              <Space>
                <Form.Item
                  style={{ marginBottom: 0 }}
                  name='notify_repeat_step'
                  initialValue={60}
                  wrapperCol={{ span: 10 }}
                  rules={[
                    {
                      required: true,
                      message: t('重复发送频率不能为空'),
                    },
                  ]}
                >
                  <InputNumber
                    min={0}
                    onChange={(val) => {
                      setRefresh(!refresh);
                    }}
                  />
                </Form.Item>
                分钟
                <Tooltip title={t(`如果告警持续未恢复,间隔${form.getFieldValue('notify_repeat_step')}分钟之后重复提醒告警接收组的成员`)}>
                  <QuestionCircleFilled />
                </Tooltip>
              </Space>
            </Form.Item>
            <Form.Item label={t('回调地址')}>
              <Form.List name='callbacks' initialValue={[{}]}>
                {(fields, { add, remove }) => (
                  <>
                    {fields.map((field) => (
                      <Row gutter={[10, 0]} key={field.key}>
                        <Col span={22}>
                          <Form.Item name={[field.name, 'url']} fieldKey={[field.fieldKey, 'url']}>
                            <Input />
                          </Form.Item>
                        </Col>

                        <Col span={1}>
                          <MinusCircleOutlined className='control-icon-normal' onClick={() => remove(field.name)} />
                        </Col>
                      </Row>
                    ))}
                    <PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
                  </>
                )}
              </Form.List>
            </Form.Item>
          </Card>
          <Form.Item
            // {...tailLayout}
            style={{
              marginTop: 20,
            }}
          >
            <Button type='primary' onClick={addSubmit} style={{ marginRight: '8px' }}>
              {type === 1 ? t('编辑') : type === 2 ? t('克隆') : t('创建')}
            </Button>
            {type === 1 && (
              <Button
                danger
                style={{ marginRight: '8px' }}
                onClick={() => {
                  Modal.confirm({
                    title: t('是否删除该告警规则?'),
                    onOk: () => {
                      deleteStrategy([detail.id], curBusiItem.id).then(() => {
                        message.success(t('删除成功'));
                        history.push('/alert-rules');
                      });
                    },

                    onCancel() {},
                  });
                }}
              >
                {t('删除')}
              </Button>
            )}

            <Button
              onClick={() => {
                history.push('/alert-rules');
              }}
            >
              {t('取消')}
            </Button>
          </Form.Item>
        </Space>
      </Form>
    </div>
  );
}
Example #22
Source File: operateForm.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
OperateForm: React.FC<Props> = ({ detail = {}, type, tagsObj = {} }) => {
  const btimeDefault = new Date().getTime();
  const etimeDefault = new Date().getTime() + 1 * 60 * 60 * 1000; // 默认时长1h
  const { t, i18n } = useTranslation();
  const { clusters: clusterList } = useSelector<RootState, CommonStoreState>((state) => state.common);
  const layout = {
    labelCol: {
      span: 24,
    },
    wrapperCol: {
      span: 24,
    },
  };
  const tailLayout = {
    labelCol: {
      span: 0,
    },
    wrapperCol: {
      span: 24,
    },
  };

  const [form] = Form.useForm(null as any);
  const history = useHistory();
  const [btnLoading, setBtnLoading] = useState<boolean>(false);
  const [timeLen, setTimeLen] = useState('1h');
  const { curBusiItem, busiGroups } = useSelector<RootState, CommonStoreState>((state) => state.common);

  useEffect(() => {
    const btime = form.getFieldValue('btime');
    const etime = form.getFieldValue('etime');
    if (!!etime && !!btime) {
      const d = moment.duration(etime - btime).days();
      const h = moment.duration(etime - btime).hours();
      const m = moment.duration(etime - btime).minutes();
      const s = moment.duration(etime - btime).seconds();
    }
    if (curBusiItem) {
      form.setFieldsValue({ busiGroup: curBusiItem.id });
    } else if (busiGroups.length > 0) {
      form.setFieldsValue({ busiGroup: busiGroups[0].id });
    } else {
      message.warning('无可用业务组');
      history.push('/alert-mutes');
    }
    return () => {};
  }, [form]);

  useEffect(() => {
    // 只有add 的时候才传入tagsObj
    if (tagsObj?.tags && tagsObj?.tags.length > 0) {
      const tags = tagsObj?.tags?.map((item) => {
        return {
          ...item,
          value: item.func === 'in' ? item.value.split(' ') : item.value,
        };
      });
      form.setFieldsValue({
        tags: tags || [{}],
        cluster: tagsObj.cluster,
      });
    }
  }, [tagsObj]);

  const timeChange = () => {
    const btime = form.getFieldValue('btime');
    const etime = form.getFieldValue('etime');
    if (!!etime && !!btime) {
      const d = Math.floor(moment.duration(etime - btime).asDays());
      const h = Math.floor(moment.duration(etime - btime).hours());
      const m = Math.floor(moment.duration(etime - btime).minutes());
      const s = Math.floor(moment.duration(etime - btime).seconds());
      const timeLen = `${d ? `${d}d ` : ''}${h ? `${h}h ` : ''}${m ? `${m}m ` : ''}${s ? `${s}s` : ''}`;
      setTimeLen(timeLen);
    }
  };

  const onFinish = (values) => {
    setBtnLoading(true);
    const tags = values?.tags?.map((item) => {
      return {
        ...item,
        value: Array.isArray(item.value) ? item.value.join(' ') : item.value,
      };
    });
    const params = {
      ...values,
      btime: moment(values.btime).unix(),
      etime: moment(values.etime).unix(),
      tags,
    };
    const curBusiItemId = form.getFieldValue('busiGroup');
    addShield(params, curBusiItemId)
      .then((_) => {
        message.success(t('新建告警屏蔽成功'));
        history.push('/alert-mutes');
      })
      .finally(() => {
        setBtnLoading(false);
      });
  };
  const onFinishFailed = () => {
    setBtnLoading(false);
  };

  const timeLenChange = (val: string) => {
    setTimeLen(val);

    const time = new Date().getTime();
    if (val === 'forever') {
      const longTime = 7 * 24 * 3600 * 1000 * 10000;
      form.setFieldsValue({
        btime: moment(time),
        etime: moment(time).add({
          seconds: longTime,
        }),
      });
      return;
    }
    const unit = val.charAt(val.length - 1);
    const num = val.substr(0, val.length - 1);
    form.setFieldsValue({
      btime: moment(time),
      etime: moment(time).add({
        [unit]: num,
      }),
    });
  };

  const content = (
    <Form
      form={form}
      {...layout}
      layout='vertical'
      className='operate-form'
      onFinish={onFinish}
      onFinishFailed={onFinishFailed}
      initialValues={{
        ...detail,
        btime: detail?.btime ? moment(detail.btime * 1000) : moment(btimeDefault),
        etime: detail?.etime ? moment(detail.etime * 1000) : moment(etimeDefault),
        cluster: clusterList[0] || 'Default',
      }}
    >
      <Card>
        <Form.Item label={t('业务组:')} name='busiGroup'>
          <Select suffixIcon={<CaretDownOutlined />}>
            {busiGroups?.map((item) => (
              <Option value={item.id} key={item.id}>
                {item.name}
              </Option>
            ))}
          </Select>
        </Form.Item>

        <Form.Item
          label={t('生效集群:')}
          name='cluster'
          rules={[
            {
              required: true,
              message: t('生效集群不能为空'),
            },
          ]}
        >
          <Select suffixIcon={<CaretDownOutlined />}>
            {clusterList?.map((item) => (
              <Option value={item} key={item}>
                {item}
              </Option>
            ))}
          </Select>
        </Form.Item>

        <Row gutter={10}>
          <Col span={8}>
            <Form.Item label={t('屏蔽开始时间:')} name='btime'>
              <DatePicker showTime onChange={timeChange} />
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label={t('屏蔽时长:')}>
              <Select suffixIcon={<CaretDownOutlined />} placeholder={t('请选择屏蔽时长')} onChange={timeLenChange} value={timeLen}>
                {timeLensDefault.map((item: any, index: number) => (
                  <Option key={index} value={item.value}>
                    {item.value}
                  </Option>
                ))}
              </Select>
            </Form.Item>
          </Col>
          <Col span={8}>
            <Form.Item label={t('屏蔽结束时间:')} name='etime'>
              <DatePicker showTime onChange={timeChange} />
            </Form.Item>
          </Col>
        </Row>
        <Row gutter={[10, 10]} style={{ marginBottom: '8px' }}>
          <Col span={5}>
            {t('屏蔽事件标签Key:')}
            <Tooltip title={t(`这里的标签是指告警事件的标签,通过如下标签匹配规则过滤告警事件`)}>
              <QuestionCircleFilled />
            </Tooltip>
          </Col>
          <Col span={3}>{t('运算符:')}</Col>
          <Col span={16}>{t('标签Value:')}</Col>
        </Row>
        <Form.List name='tags' initialValue={[{}]}>
          {(fields, { add, remove }) => (
            <>
              {fields.map((field, index) => (
                <TagItem field={field} key={index} remove={remove} form={form} />
              ))}
              <Form.Item>
                <PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
              </Form.Item>
            </>
          )}
        </Form.List>

        {/* <Form.Item label={t('屏蔽时间')} name='time'>
          <RangeDatePicker />
        </Form.Item> */}
        <Form.Item
          label={t('屏蔽原因')}
          name='cause'
          rules={[
            {
              required: true,
              message: t('请填写屏蔽原因'),
            },
          ]}
        >
          <TextArea rows={3} />
        </Form.Item>
        <Form.Item {...tailLayout}>
          <Row gutter={[10, 10]}>
            <Col span={1}>
              <Button type='primary' htmlType='submit'>
                {type === 2 ? t('克隆') : t('创建')}
              </Button>
            </Col>

            <Col
              span={1}
              style={{
                marginLeft: 40,
              }}
            >
              <Button onClick={() => window.history.back()}>{t('取消')}</Button>
            </Col>
          </Row>
        </Form.Item>
      </Card>
    </Form>
  );
  return <div className='operate-form-index'>{content}</div>;
}
Example #23
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
UserForm = React.forwardRef<ReactNode, UserAndPasswordFormProps>((props, ref) => {
  const { t } = useTranslation();
  const { userId } = props;
  const [form] = Form.useForm();
  const [initialValues, setInitialValues] = useState<User>();
  const [loading, setLoading] = useState<boolean>(true);
  const [contactsList, setContactsList] = useState<ContactsItem[]>([]);
  const [roleList, setRoleList] = useState<{ name: string; note: string }[]>([]);

  useImperativeHandle(ref, () => ({
    form: form,
  }));
  useEffect(() => {
    if (userId) {
      getUserInfoDetail(userId);
    } else {
      setLoading(false);
    }

    getContacts();
    getRoles().then((res) => setRoleList(res));
  }, []);

  const getContacts = () => {
    getNotifyChannels().then((data: Array<ContactsItem>) => {
      setContactsList(data);
    });
  };

  const getUserInfoDetail = (id: string) => {
    getUserInfo(id).then((data: User) => {
      let contacts: Array<Contacts> = [];

      if (data.contacts) {
        Object.keys(data.contacts).forEach((item: string) => {
          let val: Contacts = {
            key: item,
            value: data.contacts[item],
          };
          contacts.push(val);
        });
      }

      setInitialValues(
        Object.assign({}, data, {
          contacts,
        }),
      );
      setLoading(false);
    });
  };

  return !loading ? (
    <Form {...layout} form={form} initialValues={initialValues} preserve={false}>
      {!userId && (
        <Form.Item
          label={t('用户名')}
          name='username'
          rules={[
            {
              required: true,
              message: t('用户名不能为空!'),
            },
          ]}
        >
          <Input />
        </Form.Item>
      )}
      <Form.Item label={t('显示名')} name='nickname'>
        <Input />
      </Form.Item>
      {!userId && (
        <>
          <Form.Item
            name='password'
            label={t('密码')}
            rules={[
              {
                required: true,
                message: t('请输入密码!'),
              },
            ]}
            hasFeedback
          >
            <Input.Password />
          </Form.Item>

          <Form.Item
            name='confirm'
            label={t('确认密码')}
            dependencies={['password']}
            hasFeedback
            rules={[
              {
                required: true,
                message: t('请确认密码!'),
              },
              ({ getFieldValue }) => ({
                validator(_, value) {
                  if (!value || getFieldValue('password') === value) {
                    return Promise.resolve();
                  }

                  return Promise.reject(new Error('密码不一致!'));
                },
              }),
            ]}
          >
            <Input.Password />
          </Form.Item>
        </>
      )}
      <Form.Item
        label={t('角色')}
        name='roles'
        rules={[
          {
            required: true,
            message: t('角色不能为空!'),
          },
        ]}
      >
        <Select mode='multiple'>
          {roleList.map((item, index) => (
            <Option value={item.name} key={index}>
              <div>
                <div>{item.name}</div>
                <div style={{ color: '#8c8c8c' }}>{item.note}</div>
              </div>
            </Option>
          ))}
        </Select>
      </Form.Item>
      <Form.Item label={t('邮箱')} name='email'>
        <Input />
      </Form.Item>
      <Form.Item label={t('手机')} name='phone'>
        <Input />
      </Form.Item>
      <Form.Item label={t('更多联系方式')}>
        <Form.List name='contacts'>
          {(fields, { add, remove }) => (
            <>
              {fields.map(({ key, name, fieldKey, ...restField }) => (
                <Space
                  key={key}
                  style={{
                    display: 'flex',
                  }}
                  align='baseline'
                >
                  <Form.Item
                    style={{
                      width: '180px',
                    }}
                    {...restField}
                    name={[name, 'key']}
                    fieldKey={[fieldKey, 'key']}
                    rules={[
                      {
                        required: true,
                        message: t('联系方式不能为空'),
                      },
                    ]}
                  >
                    <Select suffixIcon={<CaretDownOutlined />} placeholder={t('请选择联系方式')}>
                      {contactsList.map((item, index) => (
                        <Option value={item.key} key={index}>
                          {item.label}
                        </Option>
                      ))}
                    </Select>
                  </Form.Item>
                  <Form.Item
                    {...restField}
                    style={{
                      width: '170px',
                    }}
                    name={[name, 'value']}
                    fieldKey={[fieldKey, 'value']}
                    rules={[
                      {
                        required: true,
                        message: t('值不能为空'),
                      },
                    ]}
                  >
                    <Input placeholder={t('请输入值')} />
                  </Form.Item>
                  <MinusCircleOutlined className='control-icon-normal' onClick={() => remove(name)} />
                </Space>
              ))}
              <PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
            </>
          )}
        </Form.List>
      </Form.Item>
    </Form>
  ) : null;
})
Example #24
Source File: operateForm.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
OperateForm: React.FC<Props> = ({ detail = {}, type }) => {
  const { t, i18n } = useTranslation();
  const { clusters: clusterList } = useSelector<RootState, CommonStoreState>((state) => state.common);
  const layout = {
    labelCol: {
      span: 24,
    },
    wrapperCol: {
      span: 24,
    },
  };
  const tailLayout = {
    labelCol: {
      span: 24,
    },
    wrapperCol: {
      span: 24,
    },
  };

  const [form] = Form.useForm(null as any);
  const history = useHistory();
  const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
  const [btnLoading, setBtnLoading] = useState<boolean>(false);
  const [ruleModalShow, setRuleModalShow] = useState<boolean>(false);
  const [ruleCur, setRuleCur] = useState<any>();
  const [contactList, setInitContactList] = useState([]);
  const [littleAffect, setLittleAffect] = useState(true);
  const [notifyGroups, setNotifyGroups] = useState<any[]>([]);

  useEffect(() => {
    getNotifyChannel();
    getGroups('');
  }, []);

  useEffect(() => {
    setRuleCur({
      id: detail.rule_id || 0,
      name: detail.rule_name,
    });
  }, [detail.rule_id]);

  const notifyGroupsOptions = (detail.user_groups ? detail.user_groups.filter((item) => !notifyGroups.find((i) => item.id === i.id)) : []).concat(notifyGroups).map((ng: any) => (
    <Option value={String(ng.id)} key={ng.id}>
      {ng.name}
    </Option>
  ));

  const getNotifyChannel = async () => {
    const res = await getNotifiesList();
    let contactList = res || [];
    setInitContactList(contactList);
  };

  const getGroups = async (str) => {
    const res = await getTeamInfoList({ query: str });
    const data = res.dat || res;
    setNotifyGroups(data || []);
  };

  const debounceFetcher = useCallback(_.debounce(getGroups, 800), []);

  const onFinish = (values) => {
    setBtnLoading(true);
    const tags = values?.tags?.map((item) => {
      return {
        ...item,
        value: Array.isArray(item.value) ? item.value.join(' ') : item.value,
      };
    });
    const params = {
      ...values,
      tags,
      redefine_severity: values.redefine_severity ? 1 : 0,
      redefine_channels: values.redefine_channels ? 1 : 0,
      rule_id: ruleCur.id,
      user_group_ids: values.user_group_ids ? values.user_group_ids.join(' ') : '',
      new_channels: values.new_channels ? values.new_channels.join(' ') : '',
    };
    if (type === 1) {
      editSubscribe([{ ...params, id: detail.id }], curBusiItem.id)
        .then((_) => {
          message.success(t('编辑订阅规则成功'));
          history.push('/alert-subscribes');
        })
        .finally(() => {
          setBtnLoading(false);
        });
    } else {
      addSubscribe(params, curBusiItem.id)
        .then((_) => {
          message.success(t('新建订阅规则成功'));
          history.push('/alert-subscribes');
        })
        .finally(() => {
          setBtnLoading(false);
        });
    }
  };

  const onFinishFailed = () => {
    setBtnLoading(false);
  };

  const chooseRule = () => {
    setRuleModalShow(true);
  };

  const subscribeRule = (val) => {
    setRuleModalShow(false);
    setRuleCur(val);
    form.setFieldsValue({
      rile_id: val.id || 0,
    });
  };

  return (
    <>
      <div className='operate-form-index' id={littleAffect ? 'littleAffect' : ''}>
        <Form
          form={form}
          {...layout}
          layout='vertical'
          className='operate-form'
          onFinish={onFinish}
          onFinishFailed={onFinishFailed}
          initialValues={{
            ...detail,
            cluster: clusterList[0] || 'Default',
            redefine_severity: detail?.redefine_severity ? true : false,
            redefine_channels: detail?.redefine_channels ? true : false,
            user_group_ids: detail?.user_group_ids ? detail?.user_group_ids?.split(' ') : [],
            new_channels: detail?.new_channels?.split(' '),
          }}
        >
          <Card>
            <Form.Item
              label={t('生效集群:')}
              name='cluster'
              rules={[
                {
                  required: true,
                  message: t('生效集群不能为空'),
                },
              ]}
            >
              <Select suffixIcon={<CaretDownOutlined />}>
                {clusterList?.map((item, index) => (
                  <Option value={item} key={index}>
                    {item}
                  </Option>
                ))}
              </Select>
            </Form.Item>
            <Form.Item label={t('订阅告警规则:')}>
              {!!ruleCur?.id && (
                <Button
                  type='primary'
                  ghost
                  style={{ marginRight: '8px' }}
                  onClick={() => {
                    ruleCur?.id && history.push(`/alert-rules/edit/${ruleCur?.id}`);
                  }}
                >
                  {ruleCur?.name}
                </Button>
              )}

              <EditOutlined style={{ cursor: 'pointer', fontSize: '18px' }} onClick={chooseRule} />
              {!!ruleCur?.id && <DeleteOutlined style={{ cursor: 'pointer', fontSize: '18px', marginLeft: 5 }} onClick={() => subscribeRule({})} />}
            </Form.Item>
            <Row gutter={[10, 10]} style={{ marginBottom: '8px' }}>
              <Col span={5}>
                {t('订阅事件标签Key:')}
                <Tooltip title={t(`这里的标签是指告警事件的标签,通过如下标签匹配规则过滤告警事件`)}>
                  <QuestionCircleFilled />
                </Tooltip>
              </Col>
              <Col span={3}>{t('运算符:')}</Col>
              <Col span={16}>{t('标签Value:')}</Col>
            </Row>
            <Form.List name='tags' initialValue={[{}]}>
              {(fields, { add, remove }) => (
                <>
                  {fields.map((field, index) => (
                    <TagItem field={field} fields={fields} key={index} remove={remove} add={add} form={form} />
                  ))}
                  <Form.Item>
                    <PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
                  </Form.Item>
                </>
              )}
            </Form.List>
            <Form.Item label={t('告警级别:')} name='redefine_severity' valuePropName='checked'>
              <Checkbox
                value={1}
                style={{ lineHeight: '32px' }}
                onChange={(e) => {
                  form.setFieldsValue({
                    redefine_severity: e.target.checked ? 1 : 0,
                  });
                  setLittleAffect(!littleAffect);
                }}
              >
                {t('重新定义')}
              </Checkbox>
            </Form.Item>
            <Form.Item label={t('新的告警级别:')} name='new_severity' initialValue={2} style={{ display: form.getFieldValue('redefine_severity') ? 'block' : 'none' }}>
              <Radio.Group>
                <Radio key={1} value={1}>
                  {t('一级报警')}
                </Radio>
                <Radio key={2} value={2}>
                  {t('二级报警')}
                </Radio>
                <Radio key={3} value={3}>
                  {t('三级报警')}
                </Radio>
              </Radio.Group>
            </Form.Item>

            <Form.Item label={t('通知媒介:')} name='redefine_channels' valuePropName='checked'>
              <Checkbox
                value={1}
                style={{ lineHeight: '32px' }}
                onChange={(e) => {
                  form.setFieldsValue({
                    redefine_channels: e.target.checked ? 1 : 0,
                  });
                  setLittleAffect(!littleAffect);
                }}
              >
                {t('重新定义')}
              </Checkbox>
            </Form.Item>
            <Form.Item label={t('新的通知媒介:')} name='new_channels' style={{ display: form.getFieldValue('redefine_channels') ? 'block' : 'none' }}>
              <Checkbox.Group>
                {contactList.map((c: { key: string; label: string }) => (
                  <Checkbox value={c.key} key={c.label}>
                    {c.label}
                  </Checkbox>
                ))}
              </Checkbox.Group>
            </Form.Item>

            <Form.Item label={t('订阅告警接收组:')} name='user_group_ids' rules={[{ required: true, message: t('告警接收组不能为空') }]}>
              <Select mode='multiple' showSearch optionFilterProp='children' filterOption={false} onSearch={(e) => debounceFetcher(e)} onBlur={() => getGroups('')}>
                {notifyGroupsOptions}
              </Select>
            </Form.Item>
            <Form.Item {...tailLayout}>
              <Button type='primary' htmlType='submit' style={{ marginRight: '8px' }}>
                {type === 1 ? t('编辑') : type === 2 ? t('克隆') : t('创建')}
              </Button>
              {type === 1 && (
                <Button
                  danger
                  style={{ marginRight: '8px' }}
                  onClick={() => {
                    Modal.confirm({
                      title: t('是否删除该告警规则?'),
                      onOk: () => {
                        detail?.id &&
                          deleteSubscribes({ ids: [detail.id] }, curBusiItem.id).then(() => {
                            message.success(t('删除成功'));
                            history.push('/alert-subscribes');
                          });
                      },

                      onCancel() {},
                    });
                  }}
                >
                  {t('删除')}
                </Button>
              )}
              <Button onClick={() => window.history.back()}>{t('取消')}</Button>
            </Form.Item>
          </Card>
        </Form>
        <RuleModal
          visible={ruleModalShow}
          ruleModalClose={() => {
            setRuleModalShow(false);
          }}
          subscribe={subscribeRule}
        />
      </div>
    </>
  );
}
Example #25
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
TeamForm = React.forwardRef<ReactNode, TeamProps>((props, ref) => {
  const { t } = useTranslation();
  const { businessId, action } = props;
  const [form] = Form.useForm();
  const [userTeam, setUserTeam] = useState<Team[]>([]);
  const [initialValues, setInitialValues] = useState({
    label_enable: false,
    label_value: '',
    members: [{ perm_flag: true }],
    name: '',
  });
  const [loading, setLoading] = useState<boolean>(true);
  const [refresh, setRefresh] = useState(true);
  useImperativeHandle(ref, () => ({
    form: form,
  }));

  useEffect(() => {
    if (businessId && action === ActionType.EditBusiness) {
      getTeamInfoDetail(businessId);
    } else {
      setLoading(false);
    }
  }, []);

  const getTeamInfoDetail = (id: string) => {
    getBusinessTeamInfo(id).then((data: { name: string; label_enable: number; label_value: string; user_groups: { perm_flag: string; user_group: { id: number } }[] }) => {
      setInitialValues({
        name: data.name,
        label_enable: data.label_enable === 1,
        label_value: data.label_value,
        members: data.user_groups.map((item) => ({
          perm_flag: item.perm_flag === 'rw',
          user_group_id: item.user_group.id,
        })),
      });
      setLoading(false);
    });
  };

  useEffect(() => {
    getList('');
  }, []);

  const getList = (str: string) => {
    getTeamInfoList({ query: str }).then((res) => {
      setUserTeam(res.dat);
    });
  };

  const debounceFetcher = useCallback(debounce(getList, 800), []);

  return !loading ? (
    <Form {...layout} form={form} initialValues={initialValues} preserve={false} layout={refresh ? 'horizontal' : 'horizontal'}>
      {action !== ActionType.AddBusinessMember && (
        <>
          <Form.Item
            label={t('业务组名称')}
            name='name'
            rules={[
              {
                required: true,
                message: t('业务组名称不能为空!'),
              },
            ]}
          >
            <Input />
          </Form.Item>
          <Form.Item
            label={t('作为标签使用')}
            name='label_enable'
            valuePropName='checked'
            tooltip={{ title: '系统会自动把业务组的英文标识作为标签附到该业务组下辖监控对象的时序数据上', getPopupContainer: () => document.body }}
          >
            <Switch />
          </Form.Item>

          <Form.Item noStyle shouldUpdate={(prevValues, curValues) => prevValues.label_enable !== curValues.label_enable}>
            {({ getFieldValue }) => {
              return (
                getFieldValue('label_enable') && (
                  <Form.Item
                    label={t('英文标识')}
                    name='label_value'
                    rules={[
                      {
                        required: true,
                      },
                    ]}
                    tooltip={{
                      title: (
                        <span>
                          尽量用英文,不能与其他业务组标识重复,系统会自动生成 <Tag color='blue'>busigroup={form.getFieldValue('label_value')}</Tag> 的标签
                        </span>
                      ),
                      getPopupContainer: () => document.body,
                    }}
                  >
                    <Input
                      onChange={(val) => {
                        setRefresh(!refresh);
                      }}
                    />
                  </Form.Item>
                )
              );
            }}
          </Form.Item>
        </>
      )}

      {(action === ActionType.CreateBusiness || action === ActionType.AddBusinessMember) && (
        <Form.Item
          label={t('团队')}
          required
          // tooltip={{
          //   title: '默认可读勾选可写',
          // }}
        >
          <Form.List name='members'>
            {(fields, { add, remove }) => (
              <>
                {fields.map(({ key, name, fieldKey, ...restField }) => (
                  <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align='baseline'>
                    <Form.Item
                      style={{ width: 450 }}
                      {...restField}
                      name={[name, 'user_group_id']}
                      fieldKey={[fieldKey, 'user_group_id']}
                      rules={[{ required: true, message: t('业务组团队不能为空!') }]}
                    >
                      <Select
                        suffixIcon={<CaretDownOutlined />}
                        style={{ width: '100%' }}
                        filterOption={false}
                        onSearch={(e) => debounceFetcher(e)}
                        showSearch
                        onBlur={() => getList('')}
                      >
                        {userTeam.map((team) => (
                          <Option key={team.id} value={team.id}>
                            {team.name}
                          </Option>
                        ))}
                      </Select>
                    </Form.Item>
                    <Form.Item {...restField} name={[name, 'perm_flag']} fieldKey={[fieldKey, 'perm_flag']} valuePropName='checked'>
                      <Switch checkedChildren='读写' unCheckedChildren='只读' />
                    </Form.Item>
                    <MinusCircleOutlined onClick={() => remove(name)} />
                  </Space>
                ))}
                <Form.Item>
                  <Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
                    添加团队
                  </Button>
                </Form.Item>
              </>
            )}
          </Form.List>
        </Form.Item>
      )}
    </Form>
  ) : null;
})
Example #26
Source File: structure.tsx    From ui with GNU Affero General Public License v3.0 4 votes vote down vote up
Structure: FunctionComponent<Props> = (props) => {
  const { t, i18n } = useTranslation()
  const size = useWindowSize()
  const [userMenu, setUserMenu] = React.useState(false)
  const [open, setOpen] = React.useState<string[]>()
  const [selected, setSelected] = React.useState<string[]>()
  const [sidebar, setSidebar] = React.useState(size.width < 700)
  const router = useRouter()
  const user = useMeQuery()

  React.useEffect(() => {
    if (sidebar !== size.width < 700) {
      setSidebar(size.width < 700)
    }
  }, [size.width])

  React.useEffect(() => {
    if (props.selected) {
      const parts = props.selected.split('.')

      const last = parts.pop()

      if (parts.length > 0) {
        setOpen(parts)
      }

      setSelected([last])
    }
  }, [props.selected])

  const buildMenu = (data: SideMenuElement[]): JSX.Element[] => {
    return data
      .filter((element) => {
        if (!element.role) {
          return true
        }

        if (user.loading) {
          return false
        }

        return user.data?.me.roles.includes(element.role)
      })
      .map(
        (element): JSX.Element => {
          if (element.items && element.items.length > 0) {
            if (element.group) {
              return (
                <ItemGroup
                  key={element.key}
                  title={
                    <Space
                      style={{
                        textTransform: 'uppercase',
                        paddingTop: 16,
                        fontWeight: 'bold',
                        color: '#444',
                      }}
                    >
                      {element.icon}
                      <div>
                        {t(element.name)}
                      </div>
                    </Space>
                  }
                >
                  {buildMenu(element.items)}
                </ItemGroup>
              )
            }

            return (
              <SubMenu
                key={element.key}
                title={
                  <Space>
                    {element.icon}
                    <div>
                      {t(element.name)}
                    </div>
                  </Space>
                }
              >
                {buildMenu(element.items)}
              </SubMenu>
            )
          }

          return (
            <Menu.Item
              onClick={async () => {
                if (element.href) {
                  await router.push(element.href)
                }
              }}
              key={element.key}
            >
              <Space>
                {element.icon}
                <div>
                  {t(element.name)}
                </div>
              </Space>
            </Menu.Item>
          )
        }
      )
  }

  const signOut = (): void => {
    clearAuth()
    router.reload()
  }

  return (
    <Layout style={{ height: '100vh' }} className={'admin'}>
      <Header
        style={{
          paddingLeft: 0,
        }}
      >
        <Space
          style={{
            float: 'left',
            color: '#FFF',
            fontSize: 14,
            marginRight: 26,
            fontWeight: 'bold',
          }}
        >
          {React.createElement(sidebar ? MenuUnfoldOutlined : MenuFoldOutlined, {
            className: 'sidebar-toggle',
            onClick: () => setSidebar(!sidebar),
          })}

          <div style={{
            display: 'flex',
            alignItems: 'center',
          }}>
            <img
              height={40}
              src={require('../assets/images/logo_white.png?resize&size=256')}
              alt={'OhMyForm'}
            />
          </div>
        </Space>
        <div style={{ float: 'right', display: 'flex', height: '100%' }}>
          <Dropdown
            overlay={
              <Menu>
                <Menu.Item key={'profile'} onClick={() => router.push('/admin/profile')}>Profile</Menu.Item>
                <Menu.Divider key={'d1'} />
                <Menu.Item key={'logout'} onClick={signOut}>Logout</Menu.Item>
              </Menu>
            }
            onVisibleChange={setUserMenu}
            visible={userMenu}
          >
            <Space
              style={{
                color: '#FFF',
                alignItems: 'center',
                display: 'inline-flex',
              }}
            >
              <div>Hi {user.data && user.data.me.username},</div>
              <UserOutlined style={{ fontSize: 24 }} />
              <CaretDownOutlined />
            </Space>
          </Dropdown>
        </div>
      </Header>
      <Layout
        style={{
          height: '100%',
        }}
      >
        <Sider
          collapsed={sidebar}
          trigger={null}
          collapsedWidth={0}
          breakpoint={'xs'}
          width={200}
          style={{
            background: '#fff',
            maxHeight: '100%',
            overflow: 'auto',
          }}
          className={'sidemenu'}
        >
          <Menu
            mode="inline"
            style={{ flex: 1 }}
            defaultSelectedKeys={['1']}
            selectedKeys={selected}
            onSelect={(s): void => setSelected(s.keyPath )}
            openKeys={open}
            onOpenChange={(open): void => setOpen(open )}
          >
            {buildMenu(sideMenu)}
          </Menu>
          <Menu mode="inline" selectable={false}>
            <Menu.Item className={'language-selector'} key={'language-selector'}>
              <Select
                bordered={false}
                value={i18n.language.replace(/-.*/, '')}
                onChange={(next) => i18n.changeLanguage(next)}
                style={{
                  width: '100%',
                }}
              >
                {languages.map((language) => (
                  <Select.Option value={language} key={language}>
                    {t(`language:${language}`)}
                  </Select.Option>
                ))}
              </Select>
            </Menu.Item>
            <Menu.Item style={{ display: 'flex', alignItems: 'center' }} key={'github'}>
              <GitHubButton type="stargazers" namespace="ohmyform" repo="ohmyform" />
            </Menu.Item>
            <Menu.Item key={'version'}>
              Version: <Tag color="gold">{process.env.version}</Tag>
            </Menu.Item>
          </Menu>
        </Sider>
        <Layout
          style={{ padding: '0 24px 24px', minHeight: 500, height: '100%', overflow: 'auto' }}
        >
          {props.title && (
            <PageHeader
              title={props.title}
              subTitle={props.subTitle}
              extra={props.extra}
              breadcrumb={{
                routes: [
                  ...(props.breadcrumbs || []).map((b) => ({
                    breadcrumbName: b.name,
                    path: '',
                  })),
                  {
                    breadcrumbName: props.title,
                    path: '',
                  },
                ],
                params: props.breadcrumbs,
                itemRender(route, params: BreadcrumbEntry[], routes) {
                  if (routes.indexOf(route) === routes.length - 1) {
                    return <span>{route.breadcrumbName}</span>
                  }

                  const entry = params[routes.indexOf(route)]

                  return (
                    <Link href={entry.href} as={entry.as || entry.href}>
                      {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
                      <a>{entry.name}</a>
                    </Link>
                  )
                },
              }}
            />
          )}

          {props.error && (
            <Alert message={props.error} type={'error'} style={{ marginBottom: 24 }} />
          )}

          <Spin spinning={!!props.loading}>
            <Content
              style={{
                background: props.padded ? '#fff' : null,
                padding: props.padded ? 24 : 0,
                ...props.style,
              }}
            >
              {props.children}
            </Content>
          </Spin>
        </Layout>
      </Layout>
    </Layout>
  )
}
Example #27
Source File: Experiment.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function Experiment(): JSX.Element {
    const {
        newExperimentData,
        experimentId,
        experimentData,
        experimentInsightId,
        minimumSampleSizePerVariant,
        recommendedExposureForCountData,
        variants,
        expectedRunningTime,
        experimentResults,
        conversionRateForVariant,
        countDataForVariant,
        editingExistingExperiment,
        experimentInsightType,
        experimentResultsLoading,
        areCountResultsSignificant,
        areConversionResultsSignificant,
    } = useValues(experimentLogic)
    const {
        setNewExperimentData,
        createExperiment,
        launchExperiment,
        setFilters,
        setEditExperiment,
        endExperiment,
        addExperimentGroup,
        updateExperimentGroup,
        removeExperimentGroup,
        setExperimentInsightType,
    } = useActions(experimentLogic)

    const [form] = Form.useForm()

    const [currentVariant, setCurrentVariant] = useState('control')
    const [showWarning, setShowWarning] = useState(true)

    const { insightProps } = useValues(
        insightLogic({
            dashboardItemId: experimentInsightId,
            syncWithUrl: false,
        })
    )
    const {
        isStepsEmpty,
        filterSteps,
        filters: funnelsFilters,
        results,
        conversionMetrics,
    } = useValues(funnelLogic(insightProps))
    const { filters: trendsFilters, results: trendResults } = useValues(trendsLogic(insightProps))

    const conversionRate = conversionMetrics.totalRate * 100
    const sampleSizePerVariant = minimumSampleSizePerVariant(conversionRate)
    const sampleSize = sampleSizePerVariant * variants.length
    const trendCount = trendResults[0]?.count
    const entrants = results?.[0]?.count
    const runningTime = expectedRunningTime(entrants, sampleSize)
    const exposure = recommendedExposureForCountData(trendCount)

    const statusColors = { running: 'green', draft: 'default', complete: 'purple' }
    const status = (): string => {
        if (!experimentData?.start_date) {
            return 'draft'
        } else if (!experimentData?.end_date) {
            return 'running'
        }
        return 'complete'
    }

    return (
        <>
            {experimentId === 'new' || editingExistingExperiment ? (
                <>
                    <Row
                        align="middle"
                        justify="space-between"
                        style={{ borderBottom: '1px solid var(--border)', marginBottom: '1rem', paddingBottom: 8 }}
                    >
                        <PageHeader title={'New Experiment'} />
                    </Row>
                    <Form
                        name="new-experiment"
                        layout="vertical"
                        className="experiment-form"
                        form={form}
                        onValuesChange={(values) => setNewExperimentData(values)}
                        initialValues={{
                            name: newExperimentData?.name,
                            feature_flag_key: newExperimentData?.feature_flag_key,
                            description: newExperimentData?.description,
                        }}
                        onFinish={() => createExperiment(true, exposure, sampleSize)}
                        scrollToFirstError
                    >
                        <div>
                            <Row>
                                <Col span={12} style={{ paddingRight: 24 }}>
                                    <Form.Item
                                        label="Name"
                                        name="name"
                                        rules={[{ required: true, message: 'You have to enter a name.' }]}
                                    >
                                        <Input data-attr="experiment-name" className="ph-ignore-input" />
                                    </Form.Item>
                                    <Form.Item
                                        label="Feature flag key"
                                        name="feature_flag_key"
                                        rules={[
                                            {
                                                required: true,
                                                message: 'You have to enter a feature flag key name.',
                                            },
                                        ]}
                                        help={
                                            <span className="text-small text-muted">
                                                {editingExistingExperiment
                                                    ? ''
                                                    : 'Enter a new and unique name for the feature flag key to be associated with this experiment.'}
                                            </span>
                                        }
                                    >
                                        <Input
                                            data-attr="experiment-feature-flag-key"
                                            disabled={editingExistingExperiment}
                                            placeholder="examples: new-landing-page-experiment, betaFeatureExperiment, ab_test_1_experiment"
                                        />
                                    </Form.Item>
                                    <Form.Item label="Description" name="description">
                                        <Input.TextArea
                                            data-attr="experiment-description"
                                            className="ph-ignore-input"
                                            placeholder="Adding a helpful description can ensure others know what this experiment is about."
                                        />
                                    </Form.Item>
                                </Col>
                                <Col span={12}>
                                    <Form.Item label="Select participants" name="person-selection">
                                        <Col>
                                            <div className="text-muted">
                                                Select the entities who will participate in this experiment. If no
                                                filters are set, 100% of participants will be targeted.
                                            </div>
                                            <div style={{ flex: 3, marginRight: 5 }}>
                                                <PropertyFilters
                                                    endpoint="person"
                                                    pageKey={'EditFunnel-property'}
                                                    propertyFilters={
                                                        (experimentInsightType === InsightType.FUNNELS
                                                            ? funnelsFilters.properties
                                                            : trendsFilters.properties) || []
                                                    }
                                                    onChange={(anyProperties) => {
                                                        setNewExperimentData({
                                                            filters: {
                                                                properties: anyProperties as PropertyFilter[],
                                                            },
                                                        })
                                                        setFilters({
                                                            properties: anyProperties.filter(isValidPropertyFilter),
                                                        })
                                                    }}
                                                    style={{ margin: '1rem 0 0' }}
                                                    taxonomicGroupTypes={[
                                                        TaxonomicFilterGroupType.PersonProperties,
                                                        TaxonomicFilterGroupType.CohortsWithAllUsers,
                                                    ]}
                                                    popoverPlacement="top"
                                                    taxonomicPopoverPlacement="auto"
                                                />
                                            </div>
                                        </Col>
                                    </Form.Item>
                                    {newExperimentData?.parameters?.feature_flag_variants && (
                                        <Col>
                                            <label>
                                                <b>Experiment groups</b>
                                            </label>
                                            <div className="text-muted">
                                                Participants are divided into experiment groups. All experiments must
                                                consist of a control group and at least one test group.
                                            </div>
                                            <Col>
                                                {newExperimentData.parameters.feature_flag_variants.map(
                                                    (variant: MultivariateFlagVariant, idx: number) => (
                                                        <Form
                                                            key={`${variant}-${idx}`}
                                                            initialValues={
                                                                newExperimentData.parameters?.feature_flag_variants
                                                            }
                                                            onValuesChange={(changedValues) => {
                                                                updateExperimentGroup(changedValues, idx)
                                                            }}
                                                            validateTrigger={['onChange', 'onBlur']}
                                                        >
                                                            <Row className="feature-flag-variant">
                                                                <Form.Item
                                                                    name="key"
                                                                    rules={[
                                                                        {
                                                                            required: true,
                                                                            message: 'Key should not be empty.',
                                                                        },
                                                                        {
                                                                            pattern: /^([A-z]|[a-z]|[0-9]|-|_)+$/,
                                                                            message:
                                                                                'Only letters, numbers, hyphens (-) & underscores (_) are allowed.',
                                                                        },
                                                                    ]}
                                                                >
                                                                    <Input
                                                                        disabled={idx === 0}
                                                                        defaultValue={variant.key}
                                                                        data-attr="feature-flag-variant-key"
                                                                        data-key-index={idx.toString()}
                                                                        className="ph-ignore-input"
                                                                        style={{ maxWidth: 150 }}
                                                                        placeholder={`example-variant-${idx + 1}`}
                                                                        autoComplete="off"
                                                                        autoCapitalize="off"
                                                                        autoCorrect="off"
                                                                        spellCheck={false}
                                                                    />
                                                                </Form.Item>
                                                                <div className="ml-05">
                                                                    {' '}
                                                                    Roll out to{' '}
                                                                    <InputNumber
                                                                        disabled={true}
                                                                        defaultValue={variant.rollout_percentage}
                                                                        value={variant.rollout_percentage}
                                                                        formatter={(value) => `${value}%`}
                                                                    />{' '}
                                                                    of <b>participants</b>
                                                                </div>
                                                                <div className="float-right">
                                                                    {!(idx === 0 || idx === 1) && (
                                                                        <Tooltip
                                                                            title="Delete this variant"
                                                                            placement="bottomLeft"
                                                                        >
                                                                            <Button
                                                                                type="link"
                                                                                icon={<DeleteOutlined />}
                                                                                onClick={() =>
                                                                                    removeExperimentGroup(idx)
                                                                                }
                                                                                style={{
                                                                                    color: 'var(--danger)',
                                                                                    float: 'right',
                                                                                }}
                                                                            />
                                                                        </Tooltip>
                                                                    )}
                                                                </div>
                                                            </Row>
                                                        </Form>
                                                    )
                                                )}

                                                {newExperimentData.parameters.feature_flag_variants.length < 4 && (
                                                    <Button
                                                        style={{
                                                            color: 'var(--primary)',
                                                            border: 'none',
                                                            boxShadow: 'none',
                                                            marginTop: '1rem',
                                                        }}
                                                        icon={<PlusOutlined />}
                                                        onClick={() => addExperimentGroup()}
                                                    >
                                                        Add test group
                                                    </Button>
                                                )}
                                            </Col>
                                        </Col>
                                    )}
                                </Col>
                            </Row>

                            <div>
                                <Row className="metrics-selection">
                                    <BindLogic logic={insightLogic} props={insightProps}>
                                        <Row style={{ width: '100%' }}>
                                            <Col span={8} style={{ paddingRight: 8 }}>
                                                <div className="mb">
                                                    <b>Goal type</b>
                                                </div>
                                                <Select
                                                    style={{ display: 'flex' }}
                                                    defaultValue={experimentInsightType}
                                                    onChange={setExperimentInsightType}
                                                    suffixIcon={<CaretDownOutlined />}
                                                    dropdownMatchSelectWidth={false}
                                                >
                                                    <Select.Option value={InsightType.TRENDS}>
                                                        <Col>
                                                            <span>
                                                                <b>Trend</b>
                                                            </span>
                                                            <div>
                                                                Track how many participants complete a specific event or
                                                                action
                                                            </div>
                                                        </Col>
                                                    </Select.Option>
                                                    <Select.Option value={InsightType.FUNNELS}>
                                                        <Col>
                                                            <span>
                                                                <b>Funnel</b>
                                                            </span>
                                                            <div>Track conversion rates between events and actions</div>
                                                        </Col>
                                                    </Select.Option>
                                                </Select>
                                                <div className="mb mt">
                                                    <b>Experiment goal</b>
                                                </div>
                                                <Row>
                                                    <Card
                                                        className="action-filters-bordered"
                                                        style={{ width: '100%', marginRight: 8 }}
                                                        bodyStyle={{ padding: 0 }}
                                                    >
                                                        {experimentInsightType === InsightType.FUNNELS && (
                                                            <ActionFilter
                                                                filters={funnelsFilters}
                                                                setFilters={(payload) => {
                                                                    setNewExperimentData({ filters: payload })
                                                                    setFilters(payload)
                                                                }}
                                                                typeKey={`EditFunnel-action`}
                                                                hideMathSelector={true}
                                                                hideDeleteBtn={filterSteps.length === 1}
                                                                buttonCopy="Add funnel step"
                                                                showSeriesIndicator={!isStepsEmpty}
                                                                seriesIndicatorType="numeric"
                                                                fullWidth
                                                                sortable
                                                                showNestedArrow={true}
                                                                propertiesTaxonomicGroupTypes={[
                                                                    TaxonomicFilterGroupType.EventProperties,
                                                                    TaxonomicFilterGroupType.PersonProperties,
                                                                    TaxonomicFilterGroupType.Cohorts,
                                                                    TaxonomicFilterGroupType.Elements,
                                                                ]}
                                                                rowClassName="action-filters-bordered"
                                                            />
                                                        )}
                                                        {experimentInsightType === InsightType.TRENDS && (
                                                            <ActionFilter
                                                                horizontalUI
                                                                filters={trendsFilters}
                                                                setFilters={(payload: Partial<FilterType>) => {
                                                                    setNewExperimentData({ filters: payload })
                                                                    setFilters(payload)
                                                                }}
                                                                typeKey={`experiment-trends`}
                                                                buttonCopy="Add graph series"
                                                                showSeriesIndicator
                                                                singleFilter={true}
                                                                hideMathSelector={true}
                                                                propertiesTaxonomicGroupTypes={[
                                                                    TaxonomicFilterGroupType.EventProperties,
                                                                    TaxonomicFilterGroupType.PersonProperties,
                                                                    TaxonomicFilterGroupType.Cohorts,
                                                                    TaxonomicFilterGroupType.Elements,
                                                                ]}
                                                                customRowPrefix={
                                                                    trendsFilters.insight === InsightType.LIFECYCLE ? (
                                                                        <>
                                                                            Showing <b>Unique users</b> who did
                                                                        </>
                                                                    ) : undefined
                                                                }
                                                            />
                                                        )}
                                                    </Card>
                                                </Row>
                                            </Col>
                                            <Col span={16}>
                                                <InsightContainer
                                                    disableHeader={experimentInsightType === InsightType.TRENDS}
                                                    disableTable={true}
                                                />
                                            </Col>
                                        </Row>
                                    </BindLogic>
                                </Row>
                            </div>
                            <Card className="experiment-preview">
                                <Row className="preview-row">
                                    <Col>
                                        <div className="card-secondary">Preview</div>
                                        <div>
                                            <span className="mr-05">
                                                <b>{newExperimentData?.name}</b>
                                            </span>
                                            {newExperimentData?.feature_flag_key && (
                                                <CopyToClipboardInline
                                                    explicitValue={newExperimentData.feature_flag_key}
                                                    iconStyle={{ color: 'var(--text-muted-alt)' }}
                                                    description="feature flag key"
                                                >
                                                    <span className="text-muted">
                                                        {newExperimentData.feature_flag_key}
                                                    </span>
                                                </CopyToClipboardInline>
                                            )}
                                        </div>
                                    </Col>
                                </Row>
                                <Row className="preview-row">
                                    {experimentInsightType === InsightType.TRENDS ? (
                                        <>
                                            <Col span={12}>
                                                <div className="card-secondary">Baseline Count</div>
                                                <div className="l4">{trendCount}</div>
                                            </Col>
                                            <Col span={12}>
                                                <div className="card-secondary">Recommended Duration</div>
                                                <div>
                                                    <span className="l4">~{exposure}</span> days
                                                </div>
                                            </Col>
                                        </>
                                    ) : (
                                        <>
                                            <Col span={8}>
                                                <div className="card-secondary">Baseline Conversion Rate</div>
                                                <div className="l4">{conversionRate.toFixed(1)}%</div>
                                            </Col>
                                            <Col span={8}>
                                                <div className="card-secondary">Recommended Sample Size</div>
                                                <div className="pb">
                                                    <span className="l4">~{sampleSizePerVariant}</span> persons
                                                </div>
                                            </Col>
                                            <Col span={8}>
                                                <div className="card-secondary">Expected Duration</div>
                                                <div>
                                                    <span className="l4">~{runningTime}</span> days
                                                </div>
                                            </Col>
                                        </>
                                    )}
                                </Row>
                            </Card>
                        </div>
                        <Button icon={<SaveOutlined />} className="float-right" type="primary" htmlType="submit">
                            Save
                        </Button>
                    </Form>
                </>
            ) : experimentData ? (
                <div className="view-experiment">
                    <Row className="draft-header">
                        <Row justify="space-between" align="middle" className="full-width pb">
                            <Col>
                                <Row>
                                    <PageHeader
                                        style={{ margin: 0, paddingRight: 8 }}
                                        title={`${experimentData?.name}`}
                                    />
                                    <CopyToClipboardInline
                                        explicitValue={experimentData.feature_flag_key}
                                        iconStyle={{ color: 'var(--text-muted-alt)' }}
                                    >
                                        <span className="text-muted">{experimentData.feature_flag_key}</span>
                                    </CopyToClipboardInline>
                                    <Tag
                                        style={{ alignSelf: 'center', marginLeft: '1rem' }}
                                        color={statusColors[status()]}
                                    >
                                        <b className="uppercase">{status()}</b>
                                    </Tag>
                                </Row>
                                <span className="description">
                                    {experimentData.description || 'There is no description for this experiment.'}
                                </span>
                            </Col>
                            {experimentData && !experimentData.start_date && (
                                <div>
                                    <Button className="mr-05" onClick={() => setEditExperiment(true)}>
                                        Edit
                                    </Button>
                                    <Button type="primary" onClick={() => launchExperiment()}>
                                        Launch
                                    </Button>
                                </div>
                            )}
                            {experimentData && experimentData.start_date && !experimentData.end_date && (
                                <Button className="stop-experiment" onClick={() => endExperiment()}>
                                    Stop experiment
                                </Button>
                            )}
                        </Row>
                    </Row>
                    <Row className="mb">
                        <Col span={10} style={{ paddingRight: '1rem' }}>
                            {showWarning &&
                                experimentResults &&
                                ((experimentInsightType == InsightType.TRENDS && areCountResultsSignificant) ||
                                    (experimentInsightType == InsightType.FUNNELS &&
                                        areConversionResultsSignificant)) && (
                                    <Row className="significant-results">
                                        <Col span={19} style={{ color: '#497342' }}>
                                            Experiment results are significant. You can end your experiment now or let
                                            it run until completion.
                                        </Col>
                                        <Col span={5}>
                                            <Button style={{ color: '#497342' }} onClick={() => setShowWarning(false)}>
                                                Dismiss
                                            </Button>
                                        </Col>
                                    </Row>
                                )}
                            <Col>
                                <div className="card-secondary">Participants</div>
                                <div>
                                    {!!experimentData.filters.properties?.length ? (
                                        <div>
                                            {experimentData.filters.properties.map((item) => {
                                                return (
                                                    <PropertyFilterButton
                                                        key={item.key}
                                                        item={item}
                                                        greyBadges={true}
                                                    />
                                                )
                                            })}
                                        </div>
                                    ) : (
                                        '100% of users'
                                    )}
                                </div>
                            </Col>
                            {experimentInsightType === InsightType.TRENDS ? (
                                <Col>
                                    <div className="card-secondary mt">Recommended running time</div>
                                    <span>
                                        ~{experimentData.parameters?.recommended_running_time}{' '}
                                        <span className="text-muted">days</span>
                                    </span>
                                </Col>
                            ) : (
                                <Col>
                                    <div className="card-secondary mt">Recommended sample size</div>
                                    <span>
                                        ~{experimentData.parameters?.recommended_sample_size}{' '}
                                        <span className="text-muted">persons</span>
                                    </span>
                                </Col>
                            )}
                            <Col>
                                <div className="card-secondary mt">Variants</div>
                                <ul className="variants-list">
                                    {experimentData.parameters?.feature_flag_variants?.map(
                                        (variant: MultivariateFlagVariant, idx: number) => (
                                            <li key={idx}>{variant.key}</li>
                                        )
                                    )}
                                </ul>
                            </Col>
                            <Row>
                                <Col className="mr">
                                    <div className="card-secondary mt">Start date</div>
                                    {experimentData.start_date ? (
                                        <span>{dayjs(experimentData.start_date).format('D MMM YYYY')}</span>
                                    ) : (
                                        <span className="description">Not started yet</span>
                                    )}
                                </Col>
                                {experimentData.end_date && (
                                    <Col className="ml">
                                        <div className="card-secondary mt">Completed date</div>
                                        <span>{dayjs(experimentData.end_date).format('D MMM YYYY')}</span>
                                    </Col>
                                )}
                            </Row>
                        </Col>
                        <Col span={14}>
                            <div style={{ borderBottom: '1px solid (--border)' }}>
                                <b>Test that your code works properly for each variant</b>
                            </div>
                            <Row justify="space-between">
                                <div>
                                    Feature flag override for{' '}
                                    <Select
                                        onChange={setCurrentVariant}
                                        defaultValue={'control'}
                                        suffixIcon={<CaretDownOutlined />}
                                    >
                                        {experimentData.parameters.feature_flag_variants?.map(
                                            (variant: MultivariateFlagVariant, idx: number) => (
                                                <Select.Option key={idx} value={variant.key}>
                                                    {variant.key}
                                                </Select.Option>
                                            )
                                        )}
                                    </Select>
                                </div>
                                <div>
                                    Language <CodeLanguageSelect />
                                </div>
                            </Row>
                            <CodeSnippet language={Language.JavaScript}>
                                {`posthog.feature_flags.override({'${experimentData.feature_flag_key}': '${currentVariant}'})`}
                            </CodeSnippet>
                            <CodeSnippet language={Language.JavaScript} wrap>
                                {`if (posthog.getFeatureFlag('${
                                    experimentData.feature_flag_key ?? ''
                                }') === '${currentVariant}') {
    // where '${currentVariant}' is the variant, run your code here
}`}
                            </CodeSnippet>
                            <a
                                target="_blank"
                                rel="noopener noreferrer"
                                href="https://posthog.com/docs/user-guides/feature-flags"
                            >
                                <Row align="middle">
                                    Experiment implementation guide
                                    <IconOpenInNew className="ml-05" />
                                </Row>
                            </a>
                        </Col>
                    </Row>
                    <div className="experiment-result">
                        {experimentResults ? (
                            <Row justify="space-around" style={{ flexFlow: 'nowrap' }}>
                                {experimentData.parameters.feature_flag_variants.map(
                                    (variant: MultivariateFlagVariant, idx: number) => (
                                        <Col key={idx} className="pr">
                                            <div style={{ fontSize: 16 }}>
                                                <b>{capitalizeFirstLetter(variant.key)}</b>
                                            </div>
                                            {experimentInsightType === InsightType.FUNNELS
                                                ? 'Conversion rate: '
                                                : 'Count: '}
                                            <b>
                                                {experimentInsightType === InsightType.FUNNELS
                                                    ? `${conversionRateForVariant(variant.key)}%`
                                                    : countDataForVariant(variant.key)}
                                            </b>
                                            {experimentInsightType === InsightType.FUNNELS && (
                                                <>
                                                    <Progress
                                                        percent={Number(conversionRateForVariant(variant.key))}
                                                        size="small"
                                                        showInfo={false}
                                                        strokeColor={getSeriesColor(idx + 1)}
                                                    />
                                                    <div>
                                                        Probability that this variant has higher conversion than other
                                                        variants:{' '}
                                                        <b>
                                                            {(experimentResults.probability[variant.key] * 100).toFixed(
                                                                1
                                                            )}
                                                            %
                                                        </b>
                                                    </div>
                                                </>
                                            )}
                                        </Col>
                                    )
                                )}
                            </Row>
                        ) : experimentResultsLoading ? (
                            <div className="text-center">
                                <Spinner />
                            </div>
                        ) : (
                            <span style={{ fontWeight: 500 }}>
                                There are no results for this experiment yet.{' '}
                                {!experimentData.start_date && 'Launch this experiment to start it!'}
                            </span>
                        )}
                        {experimentResults ? (
                            <BindLogic
                                logic={insightLogic}
                                props={{
                                    dashboardItemId: experimentResults.itemID,
                                    filters: {
                                        ...experimentResults.filters,
                                        insight: experimentInsightType,
                                        display: experimentData.filters.display,
                                        ...(experimentInsightType === InsightType.FUNNELS && {
                                            layout: FunnelLayout.vertical,
                                            funnel_viz_type: FunnelVizType.Steps,
                                        }),
                                        ...(experimentInsightType === InsightType.TRENDS && {
                                            display: ChartDisplayType.ActionsLineGraphCumulative,
                                        }),
                                    },
                                    cachedResults: experimentResults.insight,
                                    syncWithUrl: false,
                                    doNotLoad: true,
                                }}
                            >
                                <div className="mt">
                                    <InsightContainer
                                        disableHeader={experimentInsightType === InsightType.TRENDS}
                                        disableTable={experimentInsightType === InsightType.FUNNELS}
                                    />
                                </div>
                            </BindLogic>
                        ) : (
                            <div
                                style={{
                                    display: 'flex',
                                    justifyContent: 'center',
                                    alignItems: 'center',
                                    marginTop: 16,
                                    background: '#FAFAF9',
                                    border: '1px solid var(--border)',
                                    width: '100%',
                                    minHeight: 320,
                                    fontSize: 24,
                                }}
                            >
                                {experimentResultsLoading ? (
                                    <Spinner />
                                ) : (
                                    <b>There are no results for this experiment yet.</b>
                                )}
                            </div>
                        )}
                    </div>
                </div>
            ) : (
                <div>Loading Data...</div>
            )}
        </>
    )
}
Example #28
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 #29
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
ContactWayList: React.FC = () => {
  const [currentGroup, setCurrentGroup] = useState<ContactWayGroupItem>({});
  const [itemDetailVisible, setItemDetailVisible] = useState(false);
  const [currentItem, setCurrentItem] = useState<ContactWayItem>({});
  const [selectedItems, setSelectedItems] = useState<ContactWayItem[]>([]);
  const [filterGroupID, setFilterGroupID] = useState('0');
  const [groupItems, setGroupItems] = useState<ContactWayGroupItem[]>([]);
  const [groupItemsTimestamp, setGroupItemsTimestamp] = useState(Date.now);
  const [createGroupVisible, setCreateGroupVisible] = useState(false);
  const [batchUpdateVisible, setBatchUpdateVisible] = useState(false);
  const [editGroupVisible, setEditGroupVisible] = useState(false);
  const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
  const actionRef = useRef<ActionType>();

  function showDeleteGroupConfirm(item: ContactWayGroupItem) {
    Modal.confirm({
      title: `删除分组`,
      content: `是否确认删除「${item.name}」分组?`,
      // icon: <ExclamationCircleOutlined/>,
      okText: '删除',
      okType: 'danger',
      cancelText: '取消',
      onOk() {
        return HandleRequest({ids: [item.id]}, DeleteGroup, () => {
          setGroupItemsTimestamp(Date.now);
        });
      },
    });
  }

  useEffect(() => {
    QuerySimpleStaffs({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        setAllStaffs(
          res?.data?.items?.map((item: SimpleStaffInterface) => {
            return {
              label: item.name,
              value: item.ext_id,
              ...item,
            };
          }) || [],
        );
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QueryGroup({page_size: 1000, sort_field: 'sort_weight', sort_type: 'asc'})
      .then((resp) => {
        if (resp && resp.data && resp.data.items) {
          setGroupItems(resp.data.items);
        }
      })
      .catch((err) => {
        message.error(err);
      });
  }, [groupItemsTimestamp]);

  const columns: ProColumns<ContactWayItem>[] = [
    {
      title: 'ID',
      dataIndex: 'id',
      valueType: 'text',
      hideInTable: true,
      hideInSearch: true,
      fixed:'left',
    },
    {
      title: '渠道码',
      dataIndex: 'qr_code',
      valueType: 'image',
      hideInSearch: true,
      width: 80,
      fixed:'left',
      render: (dom, item) => {
        return (
          <div className={'qrcodeWrapper'}>
            <img
              src={item.qr_code}
              onClick={() => {
                setItemDetailVisible(true);
                setCurrentItem(item);
              }}
              className={'qrcode clickable'}
              alt={item.name}
            />
          </div>
        );
      },
    },
    {
      title: '名称',
      dataIndex: 'name',
      valueType: 'text',
      fixed:'left',
    },
    {
      title: '使用员工',
      dataIndex: 'staffs',
      valueType: 'text',
      hideInSearch: true,
      width: 210,
      render: (_, item) => {
        let staffs: any[] = [];
        item.schedules?.forEach((schedule) => {
          if (schedule.staffs) {
            staffs = [...staffs, ...schedule.staffs];
          }
        });
        if (item.schedule_enable === True) {
          staffs = uniqWith(staffs, (a, b) => a.ext_staff_id === b.ext_staff_id);
          return <CollapsedStaffs limit={2} staffs={staffs}/>;
        }
        return <CollapsedStaffs limit={2} staffs={item.staffs}/>;
      },
    },
    {
      title: '使用员工',
      dataIndex: 'ext_staff_ids',
      valueType: 'text',
      hideInTable: true,
      renderFormItem: () => {
        return <StaffTreeSelect options={allStaffs}/>;
      },
    },
    {
      title: '备份员工',
      dataIndex: 'backup_staffs',
      valueType: 'text',
      hideInSearch: true,
      width: 210,
      render: (_, item) => {
        return <CollapsedStaffs limit={2} staffs={item.backup_staffs}/>;
      },
    },
    {
      title: '标签',
      dataIndex: 'customer_tags',
      valueType: 'text',
      ellipsis: true,
      hideInSearch: true,
      width: 210,
      render: (_, item) => {
        return <CollapsedTags limit={3} tags={item.customer_tags}/>;
      },
    },
    {
      title: '添加人次',
      dataIndex: 'add_customer_count',
      valueType: 'digit',
      hideInSearch: true,
      sorter: true,
      showSorterTooltip: false,
      width: 120,
      tooltip: '统计添加渠道码的人次,若客户重复添加将会记录多条数据',
    },
    {
      title: '创建时间',
      dataIndex: 'created_at',
      valueType: 'dateRange',
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.created_at).format('YYYY-MM-DD HH:mm').split(' ').join('<br />'),
            }}
          />
        );
      },
    },
    {
      title: '操作',
      width: 180,
      valueType: 'option',
      render: (_, item) => [
        <a
          key='detail'
          onClick={() => {
            setItemDetailVisible(true);
            setCurrentItem(item);
          }}
        >
          详情
        </a>,
        <a
          key='download'
          onClick={() => {
            if (item?.qr_code) {
              FileSaver.saveAs(item?.qr_code, `${item.name}.png`);
            }
          }}
        >
          下载
        </a>,
        <Dropdown
          key='more'
          overlay={
            <Menu>
              <Menu.Item
                key='edit'
                onClick={() => {
                  history.push(`/staff-admin/customer-growth/contact-way/edit?id=${item.id}`);
                }}
              >
                修改
              </Menu.Item>
              <Menu.Item
                key='copy'
                onClick={() => {
                  history.push(`/staff-admin/customer-growth/contact-way/copy?id=${item.id}`);
                }}
              >
                复制
              </Menu.Item>
              {item.ext_creator_id === localStorage.getItem(LSExtStaffAdminID) && (
                <Menu.Item
                  key='delete'
                  onClick={() => {
                    Modal.confirm({
                      title: `删除渠道码`,
                      content: `是否确认删除「${item.name}」渠道码?`,
                      okText: '删除',
                      okType: 'danger',
                      cancelText: '取消',
                      onOk() {
                        return HandleRequest({ids: [item.id]}, Delete, () => {
                          actionRef.current?.clearSelected?.();
                          actionRef.current?.reload?.();
                        });
                      },
                    });
                  }}
                >删除</Menu.Item>
              )}
            </Menu>
          }
          trigger={['hover']}
        >
          <a style={{display: 'flex', alignItems: 'center'}}>
            编辑
            <CaretDownOutlined style={{fontSize: '8px', marginLeft: '3px'}}/>
          </a>
        </Dropdown>,
      ],
    },
  ];

  // @ts-ignore
  // @ts-ignore
  return (
    <PageContainer
      fixedHeader
      header={{
        title: '渠道活码列表',
        subTitle: (
          <a
            target={'_blank'}
            className={styles.tipsLink}
            // href={'https://www.openscrm.cn/wiki/contact-way'}
          >
            什么是渠道活码?
          </a>
        ),
      }}
      extra={[
        <Button
          key='create'
          type='primary'
          icon={<PlusOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={() => {
            history.push('/staff-admin/customer-growth/contact-way/create');
          }}
        >
          新建活码
        </Button>,
      ]}
    >
      <ProTable<ContactWayItem>
        actionRef={actionRef}
        className={'table'}
        scroll={{x: 'max-content'}}
        columns={columns}
        rowKey='id'
        pagination={{
          pageSizeOptions: ['5', '10', '20', '50', '100'],
          pageSize: 5,
        }}
        toolBarRender={false}
        bordered={false}
        tableAlertRender={false}
        rowSelection={{
          onChange: (_, items) => {
            setSelectedItems(items);
          },
        }}
        tableRender={(_, dom) => (
          <div className={styles.mixedTable}>
            <div className={styles.leftPart}>
              <div className={styles.header}>
                <Button
                  key='1'
                  className={styles.button}
                  type='text'
                  onClick={() => setCreateGroupVisible(true)}
                  icon={<PlusSquareFilled style={{color: 'rgb(154,173,193)', fontSize: 15}}/>}
                >
                  新建分组
                </Button>
              </div>
              <Menu
                onSelect={(e) => {
                  setFilterGroupID(e.key as string);
                }}
                defaultSelectedKeys={['0']}
                mode='inline'
                className={styles.menuList}
              >
                <Menu.Item
                  icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
                  key='0'
                >
                  全部
                </Menu.Item>
                {groupItems.map((item) => (
                  <Menu.Item
                    icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
                    key={item.id}
                  >
                    <div className={styles.menuItem}>
                      {item.name}
                      <span className={styles.count}
                            style={{marginRight: item.is_default === True ? 16 : 0}}>{item.count}</span>
                    </div>
                    {item.is_default === False && (
                      <Dropdown
                        className={'more-actions'}
                        overlay={
                          <Menu
                            onClick={(e) => {
                              e.domEvent.preventDefault();
                              e.domEvent.stopPropagation();
                            }}
                          >
                            <Menu.Item
                              onClick={() => {
                                setCurrentGroup(item);
                                setEditGroupVisible(true);
                              }}
                              key='edit'
                            >
                              修改名称
                            </Menu.Item>
                            <Menu.Item
                              onClick={() => {
                                showDeleteGroupConfirm(item);
                              }}
                              key='delete'
                            >
                              删除分组
                            </Menu.Item>
                          </Menu>
                        }
                        trigger={['hover']}
                      >
                        <MoreOutlined style={{color: '#9b9b9b', fontSize: 18}}/>
                      </Dropdown>
                    )}
                  </Menu.Item>
                ))}
              </Menu>
            </div>
            <div className={styles.rightPart}>
              <div className={styles.tableWrap}>{dom}</div>
            </div>
          </div>
        )}
        params={{
          group_id: filterGroupID !== '0' ? filterGroupID : '',
        }}
        request={async (params, sort, filter) => {
          return ProTableRequestAdapter(params, sort, filter, Query);
        }}
        dateFormatter='string'
      />

      {selectedItems?.length > 0 && (
        // 底部选中条目菜单栏
        <FooterToolbar>
          <span>
            已选择 <a style={{fontWeight: 600}}>{selectedItems.length}</a> 项 &nbsp;&nbsp;
            <span></span>
          </span>
          <Divider type='vertical'/>
          <Button
            type='link'
            onClick={() => {
              actionRef.current?.clearSelected?.();
            }}
          >
            取消选择
          </Button>
          <Button onClick={() => setBatchUpdateVisible(true)}>批量分组</Button>
          <Button
            icon={<CloudDownloadOutlined/>}
            type={'primary'}
            onClick={() => {
              Modal.confirm({
                title: `批量下载渠道码`,
                content: `是否批量下载所选「${selectedItems.length}」个渠道码?`,
                okText: '下载',
                cancelText: '取消',
                onOk: async () => {
                  const zip = new JSZip();
                  // eslint-disable-next-line no-restricted-syntax
                  for (const item of selectedItems) {
                    if (item?.qr_code) {
                      // eslint-disable-next-line no-await-in-loop
                      const img = (await fetch(item?.qr_code)).blob();
                      zip.file(`${item.name}_${item.id}.png`, img);
                    }
                  }
                  const content = await zip.generateAsync({type: 'blob'});
                  FileSaver.saveAs(content, `渠道活码_${moment().format('YYYY_MM_DD')}.zip`);
                  actionRef.current?.clearSelected?.();
                  return true;
                },
              });
            }}
          >
            批量下载
          </Button>
          <Button
            icon={<DeleteOutlined/>}
            onClick={async () => {
              Modal.confirm({
                title: `删除渠道码`,
                content: `是否批量删除所选「${selectedItems.length}」个渠道码?`,
                okText: '删除',
                okType: 'danger',
                cancelText: '取消',
                onOk() {
                  return HandleRequest(
                    {ids: selectedItems.map((item) => item.id)},
                    Delete,
                    () => {
                      actionRef.current?.clearSelected?.();
                      actionRef.current?.reload?.();
                    },
                  );
                },
              });
            }}
            danger={true}
          >
            批量删除
          </Button>
        </FooterToolbar>
      )}

      <ModalForm
        width={468}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        visible={batchUpdateVisible}
        onVisibleChange={setBatchUpdateVisible}
        onFinish={async (values) => {
          return await HandleRequest(
            {ids: selectedItems.map((item) => item.id), ...values},
            BatchUpdate,
            () => {
              actionRef.current?.clearSelected?.();
              actionRef.current?.reload?.();
              setGroupItemsTimestamp(Date.now);
            },
          );
        }}
      >
        <h2 className='dialog-title'> 批量修改渠道码 </h2>
        <ProFormSelect
          // @ts-ignore
          options={groupItems.map((groupItem) => {
            return {key: groupItem.id, label: groupItem.name, value: groupItem.id};
          })}
          labelAlign={'left'}
          name='group_id'
          label='新分组'
          placeholder='请选择分组'
          rules={[
            {
              required: true,
              message: '请选择新分组',
            },
          ]}
        />
      </ModalForm>

      <ModalForm
        width={400}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        visible={createGroupVisible}
        onVisibleChange={setCreateGroupVisible}
        onFinish={async (params) =>
          HandleRequest({...currentGroup, ...params}, CreateGroup, () => {
            setGroupItemsTimestamp(Date.now);
          })
        }
      >
        <h2 className='dialog-title'> 新建分组 </h2>
        <ProFormText
          name='name'
          label='分组名称'
          tooltip='最长为 24 个汉字'
          placeholder='请输入分组名称'
          rules={[
            {
              required: true,
              message: '请填写分组名称',
            },
          ]}
        />
      </ModalForm>

      <ModalForm
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'500px'}
        visible={editGroupVisible}
        onVisibleChange={setEditGroupVisible}
        onFinish={async (params) =>
          HandleRequest({...currentGroup, ...params}, UpdateGroup, () => {
            setGroupItemsTimestamp(Date.now);
          })
        }
      >
        <h2 className='dialog-title'> 修改名称 </h2>
        <ProFormText
          colon={true}
          name='name'
          label='分组名称'
          tooltip='最长为 24 个汉字'
          placeholder='请输入分组名称'
          initialValue={currentGroup.name}
          rules={[
            {
              required: true,
              message: '请填写分组名称',
            },
          ]}
        />
      </ModalForm>

      <Modal
        className={styles.detailDialog}
        width={'800px'}
        visible={itemDetailVisible}
        onCancel={() => setItemDetailVisible(false)}
        footer={null}
      >
        <h2 className='dialog-title' style={{textAlign: "center", fontSize: 19}}> 渠道码详情 </h2>
        <Row>
          <Col span={8} className={styles.leftPart}>
            <img src={currentItem.qr_code}/>
            <h3>{currentItem.name}</h3>
            <Button
              type={'primary'}
              onClick={() => {
                if (currentItem?.qr_code) {
                  FileSaver.saveAs(currentItem?.qr_code, `${currentItem.name}.png`);
                }
              }}
            >
              下载渠道码
            </Button>
            <Button
              onClick={() => {
                history.push(
                  `/staff-admin/customer-growth/contact-way/edit?id=${currentItem.id}`,
                );
              }}
            >
              修改
            </Button>
          </Col>
          <Col span={16} className={styles.rightPart}>
            <div className={styles.section}>
              <div className={styles.titleWrapper}>
                <div className={styles.divider}/>
                <span className={styles.title}>基本设置</span>
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>创建时间:</span>
                <span className='date'>
                  {moment(currentItem.created_at).format('YYYY-MM-DD HH:mm')}
                </span>
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>绑定员工:</span>
                {currentItem.staffs?.map((staff) => (
                  <Tag
                    key={staff.id}
                    className={styles.staffTag}
                    style={{opacity: staff.online === False ? '0.5' : '1'}}
                  >
                    <img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
                    <span className={styles.text}>{staff.name}</span>
                  </Tag>
                ))}
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>备份员工:</span>
                {currentItem.backup_staffs?.map((staff) => (
                  <Tag
                    key={staff.id}
                    className={styles.staffTag}
                    style={{opacity: staff.online === False ? '0.5' : '1'}}
                  >
                    <img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
                    <span className={styles.text}>{staff.name}</span>
                  </Tag>
                ))}
              </div>
              <p className={styles.formItem}>
                <span className={styles.title}>自动通过好友:</span>
                {currentItem.auto_skip_verify_enable === True && (
                  <span>
                    {currentItem.skip_verify_start_time && '~'}
                    {currentItem.skip_verify_end_time}自动通过
                  </span>
                )}
                {currentItem.auto_skip_verify_enable === False && <span>未开启</span>}
              </p>
              <p className={styles.formItem}>
                <span className={styles.title}>客户标签:</span>
                {currentItem.customer_tags?.map((tag) => (
                  <Tag key={tag.id} className={styles.staffTag}>
                    <span className={styles.text}>{tag.name}</span>
                  </Tag>
                ))}
              </p>
            </div>
          </Col>
        </Row>
      </Modal>
    </PageContainer>
  );
}