lodash#size TypeScript Examples

The following examples show how to use lodash#size. 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: get-profiles-from-response.spec.ts    From linkedin-private-api with MIT License 6 votes vote down vote up
test('should pull mini profiles out of response', () => {
  const response = createResponse(10);
  const profiles = getProfilesFromResponse(response);

  expect(size(profiles)).toEqual(10);
  forEach(profiles, profile => {
    expect(Object.keys(profile).sort()).toEqual(Object.keys(Object.values(profiles)[0]).sort());
  });
});
Example #2
Source File: form-submit.tsx    From example with MIT License 6 votes vote down vote up
export function FormSubmit({ form, icon, label, state, disabled }: IFormSubmitProps) {
	const { formState: { errors, isSubmitting, isValidating } } = form

	const isValid = size(errors) === 0

	let color
	let iconEl
	if (!isValid) {
		color = "warning"
		iconEl = <Icon icon={faExclamationTriangle}/>
	} else {
		switch (state) {
			case "error":
				color = "error"
				iconEl = <Icon icon={faExclamationTriangle}/>
				break
			case "success":
				color = "success"
				iconEl = <Icon icon={faCheckDouble}/>
				break
			case "normal":
			default:
				color = "primary"
				iconEl = <Icon icon={icon ?? faCheck}/>
		}
	}

	return <LoadingButton
		type="submit"
		loading={isSubmitting || isValidating}
		loadingPosition="start"
		startIcon={iconEl}
		color={color as any}
		variant="contained"
		disabled={disabled}
	>
		{label}
	</LoadingButton>
}
Example #3
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
ChangeResult = () => {
  const {
    caseTotal,
    choosenInfo: { primaryKeys },
  } = testCaseStore.useStore((s) => s);
  const { openNormalModal } = testCaseStore.reducers;

  const checked = size(primaryKeys);

  const onClick = () => {
    if (!caseTotal || !checked) {
      message.error(i18n.t('dop:After the use case is selected, the batch operation can be performed.'));
      return;
    }
    openNormalModal(TestOperation.testPlanTestCasesExecutionResult);
  };

  return (
    <>
      <Button onClick={onClick}>{i18n.t('dop:Change Execution Result')}</Button>
      <MetaModal />
    </>
  );
}
Example #4
Source File: pivot-facet.ts    From S2 with MIT License 6 votes vote down vote up
/**
   * 计算树状结构行头宽度
   * @returns number
   */
  private getTreeRowHeaderWidth(): number {
    const { rows, dataSet, rowCfg, treeRowsWidth } = this.cfg;
    // user drag happened
    if (rowCfg?.treeRowsWidth) {
      return rowCfg?.treeRowsWidth;
    }

    // + province/city/level
    const treeHeaderLabel = rows
      .map((key: string): string => dataSet.getFieldName(key))
      .join('/');
    const { bolderText: cornerCellTextStyle, icon: cornerIconStyle } =
      this.spreadsheet.theme.cornerCell;
    // 初始化角头时,保证其在树形模式下不换行,给与两个icon的宽度空余(tree icon 和 action icon),减少复杂的 action icon 判断
    const maxLabelWidth =
      measureTextWidth(treeHeaderLabel, cornerCellTextStyle) +
      cornerIconStyle.size * 2 +
      cornerIconStyle.margin?.left +
      cornerIconStyle.margin?.right +
      this.rowCellTheme.padding?.left +
      this.rowCellTheme.padding?.right;

    return Math.max(treeRowsWidth, maxLabelWidth);
  }
Example #5
Source File: pivot-facet.ts    From S2 with MIT License 6 votes vote down vote up
/**
   *  计算平铺模式等宽条件下的列宽
   * @returns number
   */
  private getAdaptGridColWidth(colLeafNodes: Node[], rowHeaderWidth?: number) {
    const { rows, cellCfg } = this.cfg;
    const rowHeaderColSize = rows.length;
    const colHeaderColSize = colLeafNodes.length;
    const canvasW = this.getCanvasHW().width;
    const size = Math.max(1, rowHeaderColSize + colHeaderColSize);
    if (!rowHeaderWidth) {
      // canvasW / (rowHeader's col size + colHeader's col size) = [celCfg.width, canvasW]
      return Math.max(getCellWidth(cellCfg), canvasW / size);
    }
    // (canvasW - rowHeaderW) / (colHeader's col size) = [celCfg.width, canvasW]
    return Math.max(
      getCellWidth(cellCfg),
      (canvasW - rowHeaderWidth) / colHeaderColSize,
    );
  }
Example #6
Source File: pivot-facet.ts    From S2 with MIT License 6 votes vote down vote up
private getColLabelLength(col: Node) {
    // 如果 label 字段形如 "["xx","xxx"]",直接获取其长度
    const labels = safeJsonParse(col?.value);
    if (isArray(labels)) {
      return labels.length;
    }

    // 否则动态采样前50条数据,如果数据value是数组类型,获取其长度
    const { dataSet } = this.cfg;
    const multiData = dataSet.getMultiData(
      col.query,
      col.isTotals || col.isTotalMeasure,
    );
    // 采样前50,根据指标个数获取单元格列宽
    const demoData = multiData?.slice(0, 50) ?? [];
    const lengths = [];
    forEach(demoData, (value) => {
      forIn(value, (v: MultiData) => {
        if (isObject(v) && v?.values) {
          lengths.push(size(v?.values[0]));
        }
      });
    });
    return max(lengths) || 1;
  }
Example #7
Source File: pivot-facet.ts    From S2 with MIT License 6 votes vote down vote up
/**
   * 获得图标区域预估宽度
   * 不考虑用户自定义的 displayCondition 条件
   * @param iconStyle 图标样式
   * @returns 宽度
   */
  private getExpectedCellIconWidth(
    cellType: CellTypes,
    useDefaultIcon: boolean,
    iconStyle: IconTheme,
  ): number {
    // count icons
    let iconCount = 0;
    if (useDefaultIcon) {
      iconCount = 1;
    } else {
      const customIcons = find(
        this.spreadsheet.options.headerActionIcons,
        (headerActionIcon: HeaderActionIcon) =>
          shouldShowActionIcons(
            {
              ...headerActionIcon,
              // ignore condition func when layout calc
              displayCondition: () => true,
            },
            null,
            cellType,
          ),
      );

      iconCount = customIcons?.iconNames.length ?? 0;
    }

    // calc width
    return iconCount
      ? iconCount * (iconStyle.size + iconStyle.margin.left) +
          iconStyle.margin.right
      : 0;
  }
Example #8
Source File: summary.tsx    From S2 with MIT License 5 votes vote down vote up
TooltipSummary: React.FC<SummaryProps> = React.memo((props) => {
  const { summaries = [] } = props;

  const renderSelected = () => {
    const count = reduce(
      summaries,
      (pre, next) => pre + size(next?.selectedData),
      0,
    );
    return (
      <div className={`${TOOLTIP_PREFIX_CLS}-summary-item`}>
        <span className={`${TOOLTIP_PREFIX_CLS}-selected`}>
          {count} {i18n('项')}
        </span>
        {i18n('已选择')}
      </div>
    );
  };

  const renderSummary = () => {
    return summaries?.map((item) => {
      const { name = '', value } = item || {};
      if (!name && !value) {
        return;
      }

      return (
        <div
          key={`${name}-${value}`}
          className={`${TOOLTIP_PREFIX_CLS}-summary-item`}
        >
          <span className={`${TOOLTIP_PREFIX_CLS}-summary-key`}>
            {name}({i18n('总和')})
          </span>
          <span
            className={cls(
              `${TOOLTIP_PREFIX_CLS}-summary-val`,
              `${TOOLTIP_PREFIX_CLS}-bold`,
            )}
          >
            {value}
          </span>
        </div>
      );
    });
  };

  return (
    <div className={`${TOOLTIP_PREFIX_CLS}-summary`}>
      {renderSelected()}
      {renderSummary()}
    </div>
  );
})
Example #9
Source File: responseBuilder.ts    From ts-di-starter with MIT License 5 votes vote down vote up
/**
   * Handle error response
   *
   * @param {Error} error error object or something and inheirits from it
   * @param {number} [codeOverride] optionally override the code specified in the error
   * @returns {{versions: *, code: number, status: string, dateTime: string, timestamp: number, message: *, data: *}}
   * @private
   */
  private errorResponse(error, codeOverride): ResponseInterface {
    // eslint-disable-next-line lodash/prefer-lodash-typecheck
    if (!(error instanceof Error)) {
      if (isString(error)) {
        return this.errorResponse(new Err(error), codeOverride);
      }

      return this.errorResponse(new Err('Unexpected error'), codeOverride);
    }

    const dateTime = DateTime.utc();
    const code = codeOverride || ResponseBuilder.getCodeFromError(error);
    const response = {
      code,
      status: HTTP_STATUS[code],
      dateTime: dateTime.toISO(),
      timestamp: dateTime.valueOf(),
    } as ResponseInterface;

    if (this.config.versions) {
      response.versions = this.config.versions;
    }

    if (error.message) {
      response.message = error.message;
    }

    const data = omit(error, ['message', 'code']);

    if (size(data) > 0) {
      response.data = data;
    }

    if (response.code >= HTTP_STATUS.INTERNAL_SERVER_ERROR) {
      log.error(`500+ error: ${error.stack}`);
    }

    return response;
  }
Example #10
Source File: test-pie-chart.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
TestPieChart = ({ data }: IProps): JSX.Element => {
  const { result = [] } = data;
  remove(result, { value: 0 });
  const colorsMap = {
    failed: '#DF3409',
    error: '#e63871',
    skipped: '#FEAB00',
    passed: '#2DC083',
  };
  const useColor = result.map((item) => colorsMap[item.type]);
  const getOption = () => {
    const option = {
      tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>{b}: {c} ({d}%)',
      },
      legend: {
        orient: 'vertical',
        left: '0',
        bottom: '0',
        data: ['failed', 'error', 'skipped', 'passed'],
      },
      calculable: true,
      series: [
        {
          name: i18n.t('Status'),
          type: 'pie',
          radius: '60%',
          center: ['50%', '50%'],
          label: {
            normal: { formatter: '{b}:{c}' },
            emphasis: {
              show: true,
              textStyle: {
                fontSize: '16',
                fontWeight: 'bold',
              },
            },
            itemStyle: {
              emphasis: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)',
              },
            },
          },
          data: result,
        },
      ],
      color: useColor,
    };
    return option;
  };
  return <ChartRender data={data} hasData={size(result) > 0} getOption={getOption} />;
}
Example #11
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
BatchProcessing = ({ recycled }: IProps) => {
  const [caseTotal, choosenInfo] = testCaseStore.useStore((s) => [s.caseTotal, s.choosenInfo]);
  const { isAll, primaryKeys } = choosenInfo;
  const { openPlanModal } = testPlanStore.reducers;
  const { openTreeModal } = testSetStore.reducers;
  const { openNormalModal } = testCaseStore.reducers;
  const { toggleToRecycle, deleteEntirely } = testCaseStore.effects;

  const checked = isAll || size(primaryKeys);

  const onClick = ({ key }: any) => {
    if (!caseTotal || !checked) {
      message.error(i18n.t('dop:After the use case is selected, the batch operation can be performed.'));
      return;
    }
    switch (key) {
      case TestOperation.delete:
        Modal.confirm({
          title: i18n.t('Delete'),
          content: i18n.t('dop:are you sure to delete the currently selected use case?'),
          onOk: () => {
            toggleToRecycle({ testCaseIDs: primaryKeys, recycled: true, moveToTestSetID: rootId });
          },
        });
        break;
      case TestOperation.plan:
        openPlanModal({ type: 'multi' });
        break;
      case TestOperation.copy:
      case TestOperation.move:
      case TestOperation.recover:
        openTreeModal({ type: 'multi', action: key });
        break;
      case TestOperation.tag:
      case TestOperation.priority:
        openNormalModal(key);
        break;
      case TestOperation.deleteEntirely:
        Modal.confirm({
          title: i18n.t('dop:delete completely'),
          content:
            i18n.t('dop:the use case will not be recovered after it is completely deleted, ') +
            i18n.t('is it confirmed?'),
          onOk: () => deleteEntirely(),
        });
        break;
      default:
        break;
    }
  };

  return (
    <>
      <DropdownSelect
        menuList={recycled ? menuItemsMap.recycled : menuItemsMap.normal}
        onClickMenu={onClick}
        buttonText={i18n.t('dop:Batch Operations')}
        btnProps={{
          type: 'primary',
          ghost: true,
        }}
      />
      <TestEnvDrawer />
      <PlanModal />
      <TagModal />
      <MetaModal />
    </>
  );
}
Example #12
Source File: index.ts    From S2 with MIT License 4 votes vote down vote up
copyData = (
  sheetInstance: SpreadSheet,
  split: string,
  formatOptions?: FormatOptions,
): string => {
  const { isFormatHeader, isFormatData } = getFormatOptions(formatOptions);
  const { rowsHierarchy, rowLeafNodes, colLeafNodes, getCellMeta } =
    sheetInstance?.facet?.layoutResult;
  const { maxLevel } = rowsHierarchy;
  const { valueInCols } = sheetInstance.dataCfg.fields;
  // Generate the table header.
  const rowsHeader = rowsHierarchy.sampleNodesForAllLevels.map((item) =>
    sheetInstance.dataSet.getFieldName(item.key),
  );

  // get max query property length
  const rowLength = rowLeafNodes.reduce((pre, cur) => {
    const length = cur.query ? Object.keys(cur.query).length : 0;
    return length > pre ? length : pre;
  }, 0);

  // Generate the table body.
  let detailRows = [];
  let maxRowLength = 0;

  if (!sheetInstance.isPivotMode()) {
    detailRows = processValueInDetail(sheetInstance, split, isFormatData);
  } else {
    // Filter out the related row head leaf nodes.
    const caredRowLeafNodes = rowLeafNodes.filter((row) => row.height !== 0);

    for (const rowNode of caredRowLeafNodes) {
      let tempLine = [];
      if (isFormatHeader) {
        tempLine = getRowNodeFormatData(rowNode);
      } else {
        // Removing the space at the beginning of the line of the label.
        rowNode.label = trim(rowNode?.label);
        const id = rowNode.id.replace(ROOT_BEGINNING_REGEX, '');
        tempLine = id.split(ID_SEPARATOR);
      }
      // TODO 兼容下钻,需要获取下钻最大层级
      const totalLevel = maxLevel + 1;
      const emptyLength = totalLevel - tempLine.length;
      if (emptyLength > 0) {
        tempLine.push(...new Array(emptyLength));
      }

      // 指标挂行头且为平铺模式下,获取指标名称
      const lastLabel = sheetInstance.dataSet.getFieldName(last(tempLine));
      tempLine[tempLine.length - 1] = lastLabel;

      for (const colNode of colLeafNodes) {
        if (valueInCols) {
          const viewMeta = getCellMeta(rowNode.rowIndex, colNode.colIndex);
          tempLine.push(
            processValueInCol(viewMeta, sheetInstance, isFormatData),
          );
        } else {
          const viewMeta = getCellMeta(rowNode.rowIndex, colNode.colIndex);
          const lintItem = processValueInRow(
            viewMeta,
            sheetInstance,
            isFormatData,
          );
          if (isArray(lintItem)) {
            tempLine = tempLine.concat(...lintItem);
          } else {
            tempLine.push(lintItem);
          }
        }
      }
      maxRowLength = max([tempLine.length, maxRowLength]);
      const lineString = tempLine
        .map((value) => getCsvString(value))
        .join(split);

      detailRows.push(lineString);
    }
  }

  // Generate the table header.
  let headers: string[][] = [];

  if (isEmpty(colLeafNodes) && !sheetInstance.isPivotMode()) {
    // when there is no column in detail mode
    headers = [rowsHeader];
  } else {
    // 当列头label为array时用于补全其他层级的label
    let arrayLength = 0;
    // Get the table header of Columns.
    let tempColHeader = clone(colLeafNodes).map((colItem) => {
      let curColItem = colItem;

      const tempCol = [];

      // Generate the column dimensions.
      while (curColItem.level !== undefined) {
        let label = getHeaderLabel(curColItem.label);
        if (isArray(label)) {
          arrayLength = max([arrayLength, size(label)]);
        } else {
          // label 为数组时不进行格式化
          label = isFormatHeader ? getNodeFormatLabel(curColItem) : label;
        }
        tempCol.push(label);
        curColItem = curColItem.parent;
      }
      return tempCol;
    });

    if (arrayLength > 1) {
      tempColHeader = processColHeaders(tempColHeader, arrayLength);
    }

    const colLevels = tempColHeader.map((colHeader) => colHeader.length);
    const colLevel = max(colLevels);

    const colHeader: string[][] = [];
    // Convert the number of column dimension levels to the corresponding array.
    for (let i = colLevel - 1; i >= 0; i -= 1) {
      // The map of data set: key-name
      const colHeaderItem = tempColHeader
        // total col completion
        .map((item) =>
          item.length < colLevel
            ? [...new Array(colLevel - item.length), ...item]
            : item,
        )
        .map((item) => item[i])
        .map((colItem) => sheetInstance.dataSet.getFieldName(colItem));
      colHeader.push(flatten(colHeaderItem));
    }

    // Generate the table header.
    headers = colHeader.map((item, index) => {
      if (sheetInstance.isPivotMode()) {
        const { columns, rows, data } = sheetInstance.facet.cornerHeader.cfg;
        const colNodes = data.filter(
          ({ cornerType }) => cornerType === CornerNodeType.Col,
        );
        const rowNodes = data.filter(
          ({ cornerType }) => cornerType === CornerNodeType.Row,
        );

        if (index < colHeader.length - 1) {
          return [
            ...Array(rowLength - 1).fill(''),
            colNodes.find(({ field }) => field === columns[index])?.label || '',
            ...item,
          ];
        }
        if (index < colHeader.length) {
          return [
            ...rows.map(
              (row) => rowNodes.find(({ field }) => field === row)?.label || '',
            ),
            ...item,
          ];
        }

        return rowsHeader.concat(...item);
      }

      return index < colHeader.length
        ? Array(rowLength)
            .fill('')
            .concat(...item)
        : rowsHeader.concat(...item);
    });
  }

  const headerRow = headers
    .map((header) => {
      const emptyLength = maxRowLength - header.length;
      if (emptyLength > 0) {
        header.unshift(...new Array(emptyLength));
      }
      return header.map((h) => getCsvString(h)).join(split);
    })
    .join('\r\n');

  const data = [headerRow].concat(detailRows);
  const result = data.join('\r\n');
  return result;
}
Example #13
Source File: index.tsx    From S2 with MIT License 4 votes vote down vote up
StrategySheet: React.FC<SheetComponentsProps> = React.memo(
  (props) => {
    const { options, themeCfg, dataCfg, ...restProps } = props;
    const s2Ref = React.useRef<SpreadSheet>();

    const strategySheetOptions = React.useMemo<
      Partial<S2Options<React.ReactNode>>
    >(() => {
      if (isEmpty(dataCfg)) {
        return {};
      }
      let hideMeasureColumn = false;
      let hierarchyType: S2Options['hierarchyType'] = 'tree';

      // 根据 dataConfig 切换 hierarchyType
      if (
        isEmpty(dataCfg?.fields?.rows) &&
        !isEmpty(dataCfg?.fields?.customTreeItems)
      ) {
        hierarchyType = 'customTree';
      }

      // 单指标非自定义树结构隐藏指标列
      if (
        size(dataCfg?.fields?.values) === 1 &&
        options.hierarchyType !== 'customTree'
      ) {
        hideMeasureColumn = true;
      }
      return {
        dataCell: (viewMeta: ViewMeta) =>
          new CustomDataCell(viewMeta, viewMeta.spreadsheet),
        colCell: (
          node: Node,
          spreadsheet: SpreadSheet,
          headerConfig: ColHeaderConfig,
        ) => new CustomColCell(node, spreadsheet, headerConfig),
        dataSet: (spreadSheet: SpreadSheet) => new StrategyDataSet(spreadSheet),
        showDefaultHeaderActionIcon: false,
        hierarchyType,
        style: {
          colCfg: {
            hideMeasureColumn,
          },
        },
        interaction: {
          autoResetSheetStyle: true,
          // 趋势分析表禁用 刷选, 多选, 区间多选
          brushSelection: false,
          selectedCellMove: false,
          multiSelection: false,
          rangeSelection: false,
        },
        tooltip: {
          operation: {
            hiddenColumns: true,
          },
          row: {
            content: (cell, defaultTooltipShowOptions) => (
              <RowTooltip
                cell={cell}
                defaultTooltipShowOptions={defaultTooltipShowOptions}
              />
            ),
          },
          col: {
            content: (cell, defaultTooltipShowOptions) => (
              <ColTooltip
                cell={cell}
                defaultTooltipShowOptions={defaultTooltipShowOptions}
              />
            ),
          },
          data: {
            content: (cell, defaultTooltipShowOptions) => (
              <DataTooltip
                cell={cell}
                defaultTooltipShowOptions={defaultTooltipShowOptions}
              />
            ),
          },
        },
      };
    }, [dataCfg, options.hierarchyType]);

    const s2DataCfg = React.useMemo<S2DataConfig>(() => {
      const defaultFields = {
        fields: {
          valueInCols: size(dataCfg?.fields?.values) <= 1, // 多指标数值挂行头,单指标挂列头
        },
      };
      return customMerge(dataCfg, defaultFields);
    }, [dataCfg]);

    const s2Options = React.useMemo<S2Options>(() => {
      return customMerge(options, strategySheetOptions);
    }, [options, strategySheetOptions]);

    return (
      <BaseSheet
        options={s2Options}
        themeCfg={themeCfg}
        dataCfg={s2DataCfg}
        ref={s2Ref}
        {...restProps}
      />
    );
  },
)
Example #14
Source File: pivot-spec.tsx    From S2 with MIT License 4 votes vote down vote up
describe('Pivot Table Core Data Process', () => {
  const s2 = new PivotSheet(
    getContainer(),
    assembleDataCfg({
      totalData: [],
    }),
    assembleOptions({}),
  );
  s2.render();

  describe('1、Transform indexes data', () => {
    const ds = s2.dataSet as PivotDataSet;
    test('should get correct pivot meta', () => {
      const rowPivotMeta = ds.rowPivotMeta;
      const colPivotMeta = ds.colPivotMeta;
      expect([...rowPivotMeta.keys()]).toEqual(['浙江省', '四川省']);
      expect([...colPivotMeta.keys()]).toEqual(['家具', '办公用品']);
      expect([...rowPivotMeta.get('浙江省').children.keys()]).toEqual([
        '杭州市',
        '绍兴市',
        '宁波市',
        '舟山市',
      ]);
      expect([...rowPivotMeta.get('四川省').children.keys()]).toEqual([
        '成都市',
        '绵阳市',
        '南充市',
        '乐山市',
      ]);
    });

    test('should get correct indexes data', () => {
      const indexesData = ds.indexesData;
      expect(flattenDeep(indexesData)).toHaveLength(data.length);
      expect(get(indexesData, '0.0.0.0.0')).toEqual({
        province: '浙江省',
        city: '杭州市',
        type: '家具',
        sub_type: '桌子',
        number: 7789,
        [VALUE_FIELD]: 7789,
        [EXTRA_FIELD]: 'number',
      }); // 左上角
      expect(get(indexesData, '0.0.1.1.0')).toEqual({
        province: '浙江省',
        city: '杭州市',
        type: '办公用品',
        sub_type: '纸张',
        number: 1343,
        [VALUE_FIELD]: 1343,
        [EXTRA_FIELD]: 'number',
      }); // 右上角
      expect(get(indexesData, '1.3.0.0.0')).toEqual({
        province: '四川省',
        city: '乐山市',
        type: '家具',
        sub_type: '桌子',
        number: 2330,
        [VALUE_FIELD]: 2330,
        [EXTRA_FIELD]: 'number',
      }); // 左下角
      expect(get(indexesData, '1.3.1.1.0')).toEqual({
        province: '四川省',
        city: '乐山市',
        type: '办公用品',
        sub_type: '纸张',
        number: 352,
        [VALUE_FIELD]: 352,
        [EXTRA_FIELD]: 'number',
      }); // 右下角
      expect(get(indexesData, '0.3.1.0.0')).toEqual({
        province: '浙江省',
        city: '舟山市',
        type: '办公用品',
        sub_type: '笔',
        number: 1432,
        [VALUE_FIELD]: 1432,
        [EXTRA_FIELD]: 'number',
      }); // 中间
    });
  });

  describe('2、Generate hierarchy', () => {
    const layoutResult = s2.facet.layoutResult;
    const { rowsHierarchy, colsHierarchy } = layoutResult;

    test('should get correct row hierarchy structure', () => {
      // 节点正确
      expect(rowsHierarchy.getIndexNodes()).toHaveLength(8);
      expect(rowsHierarchy.getNodes()).toHaveLength(10);
      // 叶子节点正确
      expect(rowsHierarchy.getLeaves().map((node) => node.label)).toEqual([
        '杭州市',
        '绍兴市',
        '宁波市',
        '舟山市',
        '成都市',
        '绵阳市',
        '南充市',
        '乐山市',
      ]);
      // 层级正确
      expect(rowsHierarchy.getNodes().map((node) => node.label)).toEqual([
        '浙江省',
        '杭州市',
        '绍兴市',
        '宁波市',
        '舟山市',
        '四川省',
        '成都市',
        '绵阳市',
        '南充市',
        '乐山市',
      ]);
      expect(rowsHierarchy.getNodes(0).map((node) => node.label)).toEqual([
        '浙江省',
        '四川省',
      ]);
      expect(rowsHierarchy.getNodes(1).map((node) => node.label)).toEqual([
        '杭州市',
        '绍兴市',
        '宁波市',
        '舟山市',
        '成都市',
        '绵阳市',
        '南充市',
        '乐山市',
      ]);
      // 父子关系正确
      const leavesNodes = rowsHierarchy.getLeaves();
      const firstLeafNode = leavesNodes[0];
      expect(firstLeafNode.label).toEqual('杭州市');
      expect(firstLeafNode.parent.label).toEqual('浙江省');
      expect(firstLeafNode.parent.children?.map((node) => node.label)).toEqual([
        '杭州市',
        '绍兴市',
        '宁波市',
        '舟山市',
      ]);
      const lastLeafNode = leavesNodes[leavesNodes.length - 1];
      expect(lastLeafNode.label).toEqual('乐山市');
      expect(lastLeafNode.parent.label).toEqual('四川省');
      expect(lastLeafNode.parent.children?.map((node) => node.label)).toEqual([
        '成都市',
        '绵阳市',
        '南充市',
        '乐山市',
      ]);
    });
    test('should get correct col hierarchy structure', () => {
      // 节点正确
      expect(colsHierarchy.getIndexNodes()).toHaveLength(4);
      expect(colsHierarchy.getNodes()).toHaveLength(10); // 价格在列头 家具[&]桌子[&]number
      // 叶子节点正确
      expect(colsHierarchy.getLeaves().map((node) => node.label)).toEqual([
        'number',
        'number',
        'number',
        'number',
      ]);
      // 层级正确
      expect(colsHierarchy.getNodes().map((node) => node.label)).toEqual([
        '家具',
        '桌子',
        'number',
        '沙发',
        'number',
        '办公用品',
        '笔',
        'number',
        '纸张',
        'number',
      ]);
      expect(colsHierarchy.getNodes(0).map((node) => node.label)).toEqual([
        '家具',
        '办公用品',
      ]);
      expect(colsHierarchy.getNodes(1).map((node) => node.label)).toEqual([
        '桌子',
        '沙发',
        '笔',
        '纸张',
      ]);
      expect(colsHierarchy.getNodes(2).map((node) => node.label)).toEqual([
        'number',
        'number',
        'number',
        'number',
      ]);
      // 父子关系正确
      const leavesNodes = colsHierarchy.getLeaves();
      const firstLeafNode = leavesNodes[0];
      expect(firstLeafNode.label).toEqual('number');
      expect(firstLeafNode.parent.label).toEqual('桌子');
      expect(firstLeafNode.parent.parent?.label).toEqual('家具');
      expect(
        firstLeafNode.parent.parent?.children?.map((node) => node.label),
      ).toEqual(['桌子', '沙发']);
      const lastLeafNode = leavesNodes[leavesNodes.length - 1];
      expect(lastLeafNode.label).toEqual('number');
      expect(lastLeafNode.parent.label).toEqual('纸张');
      expect(lastLeafNode.parent.parent?.label).toEqual('办公用品');
      expect(
        lastLeafNode.parent.parent?.children?.map((node) => node.label),
      ).toEqual(['笔', '纸张']);
    });
  });

  describe('3、Calculate row & col coordinates', () => {
    const { width, style } = s2.options;
    const { fields } = s2.dataCfg;
    const { rowsHierarchy, colsHierarchy, rowLeafNodes, colLeafNodes } =
      s2.facet.layoutResult;
    const { cellCfg, rowCfg, colCfg } = get(s2, 'facet.cfg');
    const expectedWidth = Math.max(
      style.cellCfg.width,
      width / (size(fields.rows) + size(colLeafNodes)),
    );
    test('should calc correct row & cell width', () => {
      expect(rowLeafNodes[0].width).toEqual(expectedWidth);
      expect(colLeafNodes[0].width).toEqual(expectedWidth);
    });
    test('should calc correct row node size and coordinate', () => {
      // all sample width.
      expect(rowsHierarchy.sampleNodesForAllLevels[0]?.width).toEqual(
        expectedWidth,
      );
      expect(rowsHierarchy.sampleNodesForAllLevels[1]?.width).toEqual(
        expectedWidth,
      );
      // all width
      expect(uniq(rowsHierarchy.getNodes().map((node) => node.width))).toEqual([
        expectedWidth,
      ]);
      // leaf node
      rowLeafNodes.forEach((node, index) => {
        const { padding } = s2.theme.rowCell.cell;
        expect(node.height).toEqual(
          cellCfg.height + padding?.top + padding.bottom,
        );
        expect(node.y).toEqual(node.height * index);
        expect(node.x).toEqual(expectedWidth);
      });
      // level = 0
      const provinceNodes = rowsHierarchy.getNodes(0);
      provinceNodes.forEach((node) => {
        expect(node.height).toEqual(
          node.children
            .map((value) => value.height)
            .reduce((sum, current) => sum + current),
        );
        expect(node.y).toEqual(node.children[0].y);
      });
    });

    test('should calc correct col node size and coordinate', () => {
      // sample height
      expect(colsHierarchy.sampleNodesForAllLevels[0]?.height).toEqual(
        colCfg.height,
      );
      expect(colsHierarchy.sampleNodesForAllLevels[1]?.height).toEqual(
        colCfg.height,
      );
      expect(colsHierarchy.sampleNodesForAllLevels[2]?.height).toEqual(
        colCfg.height,
      );
      // all height
      expect(uniq(colsHierarchy.getNodes().map((node) => node.height))).toEqual(
        [colCfg.height],
      );
      // leaf node
      colLeafNodes.forEach((node, index) => {
        expect(node.width).toEqual(expectedWidth);
        expect(node.x).toEqual(node.width * index);
        expect(node.y).toEqual(node.level * colCfg.height);
      });
      // level = 0;
      const typeNodes = colsHierarchy.getNodes(0);
      typeNodes.forEach((node) => {
        expect(node.width).toEqual(
          node.children
            .map((value) => value.width)
            .reduce((sum, current) => sum + current),
        );
        expect(node.x).toEqual(node.children[0].x);
      });
      // level = 1;
      const type1Nodes = colsHierarchy.getNodes(1);
      type1Nodes.forEach((node) => {
        expect(node.width).toEqual(
          node.children
            .map((value) => value.width)
            .reduce((sum, current) => sum + current),
        );
        expect(node.x).toEqual(node.children[0].x);
      });
    });
  });

  describe('4、Calculate data cell info', () => {
    const { getCellMeta } = s2.facet.layoutResult;
    test('should get correct data value', () => {
      // 左上角
      expect(getCellMeta(0, 0).data[VALUE_FIELD]).toBe(7789);
      expect(getCellMeta(1, 0).data[VALUE_FIELD]).toBe(2367);
      expect(getCellMeta(0, 1).data[VALUE_FIELD]).toBe(5343);
      expect(getCellMeta(1, 1).data[VALUE_FIELD]).toBe(632);
      // 右下角
      expect(getCellMeta(7, 3).data[VALUE_FIELD]).toBe(352);
      expect(getCellMeta(7, 2).data[VALUE_FIELD]).toBe(2458);
      expect(getCellMeta(6, 3).data[VALUE_FIELD]).toBe(3551);
      expect(getCellMeta(6, 2).data[VALUE_FIELD]).toBe(2457);
      // 右上角
      expect(getCellMeta(0, 3).data[VALUE_FIELD]).toBe(1343);
      expect(getCellMeta(0, 2).data[VALUE_FIELD]).toBe(945);
      expect(getCellMeta(1, 3).data[VALUE_FIELD]).toBe(1354);
      expect(getCellMeta(1, 2).data[VALUE_FIELD]).toBe(1304);
      // 左下角
      expect(getCellMeta(7, 0).data[VALUE_FIELD]).toBe(2330);
      expect(getCellMeta(7, 1).data[VALUE_FIELD]).toBe(2445);
      expect(getCellMeta(6, 0).data[VALUE_FIELD]).toBe(1943);
      expect(getCellMeta(6, 1).data[VALUE_FIELD]).toBe(2333);
    });
  });
});
Example #15
Source File: connectChart.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
ConnectChart = (props) => {
  const {
    data,
    seriseName,
    legendFormatter,
    decimal = 2,
    isBarChangeColor,
    tooltipFormatter,
    yAxisNames = [],
    isLabel,
    noAreaColor,
    timeSpan,
    opt,
  } = props;
  const groups = keys(data.results);
  const [selectedGroup, setSelectedGroup] = React.useState();
  const getOption = () => {
    const moreThanOneDay = timeSpan ? timeSpan.seconds > 24 * 3600 : false;
    const { results: originData, xAxis, time, lines } = data;
    const results = sortBy(originData[selectedGroup] || values(originData)[0], 'axisIndex');
    const legendData = [];
    let yAxis = [];
    const series = [];
    const maxArr = [];

    // 处理markLine
    const markLines = lines || [];
    let markLine = {};
    if (markLines.length) {
      markLine = {
        silent: true,
        label: {
          normal: {
            show: true,
            position: 'middle',
            formatter: (params) => {
              const uType = results[0].unitType;
              const { unit } = results[0];

              const y = getFormatter(uType, unit).format(params.data.yAxis, decimal || 2);
              return `${params.name}: ${y}`;
            },
          },
        },
        data: markLines.map(({ name, value }) => [
          { x: '7%', yAxis: value, name },
          { x: '93%', yAxis: value },
        ]),
      };
    }

    map(results, (value, i) => {
      const { axisIndex, name, tag, unit } = value;
      (tag || name) && legendData.push({ name: tag || name });
      const yAxisIndex = axisIndex || 0;
      const areaColor = areaColors[i];
      series.push({
        type: value.chartType || 'line',
        name: value.tag || seriseName || value.name || value.key,
        yAxisIndex,
        data: !isBarChangeColor
          ? value.data
          : map(value.data, (item, j) => {
              const sect = Math.ceil(value.data.length / CHANGE_COLORS.length);
              return { ...item, itemStyle: { normal: { color: CHANGE_COLORS[Number.parseInt(j / sect, 10)] } } };
            }),
        label: {
          normal: {
            show: isLabel,
            position: 'top',
            formatter: (label) => label.data.label,
          },
        },
        markLine: i === 0 ? markLine : {},
        connectNulls: true,
        symbol: 'emptyCircle',
        symbolSize: 1,
        barMaxWidth: 50,
        areaStyle: {
          normal: {
            color: noAreaColor ? 'transparent' : areaColor,
          },
        },
      });
      const curMax = value.data ? calMax([value.data]) : [];
      maxArr[yAxisIndex] = maxArr[yAxisIndex] && maxArr[yAxisIndex] > curMax ? maxArr[yAxisIndex] : curMax;
      const curUnitType = value.unitType || ''; // y轴单位
      const curUnit = value.unit || ''; // y轴单位
      yAxis[yAxisIndex] = {
        name: name || yAxisNames[yAxisIndex] || '',
        nameTextStyle: {
          padding: [0, 0, 0, 5],
        },
        position: yAxisIndex === 0 ? 'left' : 'right',
        offset: 10,
        min: 0,
        splitLine: {
          show: true,
        },
        axisTick: {
          show: false,
        },
        axisLine: {
          show: false,
        },
        unitType: curUnitType,
        unit: curUnit,
        axisLabel: {
          margin: 0,
          formatter: (val) => getFormatter(curUnitType, unit).format(val, decimal),
        },
      };
    });

    const formatTime = (timeStr) => moment(Number(timeStr)).format(moreThanOneDay ? 'D/M HH:mm' : 'HH:mm');

    const getTTUnitType = (i) => {
      const curYAxis = yAxis[i] || yAxis[yAxis.length - 1];
      return [curYAxis.unitType, curYAxis.unit];
    };

    const genTTArray = (param) =>
      param.map((unit, i) => {
        return `<span style='color: ${unit.color}'>${cutStr(unit.seriesName, 20)} : ${getFormatter(
          ...getTTUnitType(i),
        ).format(unit.value, 2)}</span><br/>`;
      });

    let defaultTTFormatter = (param) => `${param[0].name}<br/>${genTTArray(param).join('')}`;

    if (time) {
      defaultTTFormatter = (param) => {
        const endTime = time[param[0].dataIndex + 1];
        if (!endTime) {
          return `${formatTime(param[0].name)}<br />${genTTArray(param).join('')}`;
        }
        return `${formatTime(param[0].name)} - ${formatTime(endTime)}<br/>${genTTArray(param).join('')}`;
      };
    }
    const lgFormatter = (name) => {
      const defaultName = legendFormatter ? legendFormatter(name) : name;
      return cutStr(defaultName, 20);
    };

    const haveTwoYAxis = yAxis.length > 1;
    if (haveTwoYAxis) {
      yAxis = yAxis.map((item, i) => {
        // 有数据和无数据的显示有差异
        const hasData = some(results[i].data || [], (_data) => Number(_data) !== 0);
        let { name } = item;
        if (!hasData) {
          name =
            i === 0
              ? `${'  '.repeat(item.name.length + 1)}${item.name}`
              : `${item.name}${'  '.repeat(item.name.length)}`;
        }

        if (i > 1) {
          // 右侧有超过两个Y轴
          yAxis[i].offset = 80 * (i - 1);
        }
        const maxValue = item.max || maxArr[i];
        return { ...item, name, max: maxValue, interval: maxValue / 5 };
        // 如果有双y轴,刻度保持一致
      });
    } else {
      yAxis[0].name = yAxisNames[0] || '';
    }
    const defaultOption = {
      tooltip: {
        trigger: 'axis',
        transitionDuration: 0,
        confine: true,
        axisPointer: {
          type: 'none',
        },
        formatter: tooltipFormatter || defaultTTFormatter,
      },
      legend: {
        bottom: 10,
        padding: [15, 5, 0, 5],
        orient: 'horizontal',
        align: 'left',
        data: legendData,
        formatter: lgFormatter,
        type: 'scroll',
        tooltip: {
          show: true,
          formatter: (t) => cutStr(t.name, 100),
        },
      },
      grid: {
        top: haveTwoYAxis ? 30 : 25,
        left: 15,
        right: haveTwoYAxis ? 30 : 5,
        bottom: 40,
        containLabel: true,
      },
      xAxis: [
        {
          type: 'category',
          data: xAxis || time || [] /* X轴数据 */,
          axisTick: {
            show: false /* 坐标刻度 */,
          },
          axisLine: {
            show: false,
          },
          axisLabel: {
            formatter: xAxis
              ? (value) => value
              : (value) => moment(Number(value)).format(moreThanOneDay ? 'D/M HH:mm' : 'HH:mm'),
          },
          splitLine: {
            show: false,
          },
        },
      ],
      yAxis,
      textStyle: {
        fontFamily: 'arial',
      },
      series,
    };
    return merge(defaultOption, opt);
  };

  const { xAxis, time, results } = data;
  let hasData = size(results) > 0 && !isEmpty(xAxis || time);
  if (time === undefined && xAxis === undefined) {
    hasData = size(results) > 0;
  }

  const handleChange = (value) => {
    setSelectedGroup(value);
  };

  return (
    <>
      <IF check={hasData}>
        <div className="chart-selecter">
          {i18n.t('msp:select instance')}:
          <Select className="my-3" value={selectedGroup || groups[0]} style={{ width: 200 }} onChange={handleChange}>
            {map(groups, (item) => (
              <Option value={item} key={item}>
                {item}
              </Option>
            ))}
          </Select>
        </div>
      </IF>
      <ChartRender {...props} hasData={hasData} getOption={getOption} />
    </>
  );
}
Example #16
Source File: status-chart.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
StatusChart = (props: IProps) => {
  const { xAxisData, data } = props;
  const getOption = () => {
    const options = {
      title: {
        text: '',
        textStyle: {
          fontWeight: 'normal',
          fontSize: 12,
          color: '#F1F1F3',
        },
        left: '6%',
      },
      tooltip: {
        trigger: 'axis',
        formatter: '{c0} ms',
        confine: true,
        axisPointer: {
          lineStyle: {
            color: '#57617B',
          },
        },
      },
      grid: {
        left: '0%',
        right: '0%',
        bottom: '0',
        top: '4',
      },
      xAxis: [
        {
          show: false,
          type: 'category',
          boundaryGap: false,
          data: xAxisData.map((item) => moment(item).format('HH:mm')),
        },
      ],
      yAxis: [
        {
          type: 'value',
          show: false,
          axisTick: {
            show: false,
          },
        },
      ],
      series: [
        {
          name: '',
          type: 'line',
          smooth: true,
          showSymbol: false,
          symbolSize: 0,
          lineStyle: {
            normal: {
              width: 1,
            },
          },
          areaStyle: {
            normal: {
              color: new LinearGradient(
                0,
                0,
                0,
                1,
                [
                  {
                    offset: 0,
                    color: 'rgba(1, 108, 209, 0.8)',
                  },
                  {
                    offset: 0.8,
                    color: 'rgba(1, 108, 209, 0.1)',
                  },
                ],
                false,
              ),
              shadowColor: 'rgba(228, 139, 76, 0.3)',
              shadowBlur: 10,
            },
          },
          itemStyle: {
            normal: {
              color: 'rgb(1, 108, 209)',
              borderColor: '#e48b4c',
            },
          },
          data,
        },
      ],
    };
    return options;
  };
  return <Echarts {...props} hasData={size(data) > 0} option={getOption()} />;
}
Example #17
Source File: status-detail-chart.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
StatusDetailChart = (props: IProps) => {
  const { xAxisData, data, period } = props;
  const getOption = () => {
    const timeFormatMap = {
      hour: 'mm:ss',
      day: 'HH:mm',
      week: 'MM-DD',
      month: 'MM-DD',
    };
    const options = {
      backgroundColor: '#fff',
      title: {
        text: '',
        textStyle: {
          fontWeight: 'normal',
          fontSize: 12,
          color: '#F1F1F3',
        },
        left: '6%',
      },
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          lineStyle: {
            color: '#57617B',
          },
        },
      },
      grid: {
        left: '60',
        right: '40',
        bottom: '15%',
        top: '15%',
      },
      xAxis: [
        {
          type: 'category',
          boundaryGap: false,
          axisLine: {
            lineStyle: {
              color: '#57617B',
            },
          },
          data: xAxisData.map((item) => moment(item).format(timeFormatMap[period])),
          axisLabel: {
            interval: 0,
            rotate: 50,
            textStyle: {
              fontSize: 12,
            },
          },
        },
      ],
      yAxis: [
        {
          type: 'value',
          axisTick: {
            show: false,
          },
          axisLine: {
            show: false,
          },
          axisLabel: {
            textStyle: {
              fontSize: 12,
            },
          },
          splitLine: {
            show: false,
          },
        },
      ],
      series: [
        {
          name: i18n.t('Response time'),
          type: 'line',
          smooth: true,
          showSymbol: false,
          symbolSize: 0,
          lineStyle: {
            normal: {
              width: 1,
            },
          },
          areaStyle: {
            normal: {
              color: new LinearGradient(
                0,
                0,
                0,
                1,
                [
                  {
                    offset: 0,
                    color: 'rgba(1, 108, 209, 0.8)',
                  },
                  {
                    offset: 0.6,
                    color: 'rgba(1, 108, 209, 0.1)',
                  },
                ],
                false,
              ),
              shadowColor: 'rgba(228, 139, 76, 0.3)',
              shadowBlur: 10,
            },
          },
          itemStyle: {
            normal: {
              color: 'rgb(1, 108, 209)',
              borderColor: '#e48b4c',
            },
          },
          data,
        },
      ],
    };
    return options;
  };
  return <Echarts {...props} hasData={size(data) > 0} option={getOption()} />;
}
Example #18
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
BatchProcessing = ({ afterDelete }: IProps) => {
  const [visible, setVisible] = useState(false);
  const [caseTotal, choosenInfo] = testCaseStore.useStore((s) => [s.caseTotal, s.choosenInfo]);
  const { isAll, primaryKeys } = choosenInfo;
  const [params, query] = routeInfoStore.useStore((s) => [s.params, s.query]);
  const { openRemarkModal } = testPlanStore.reducers;
  const { planUserCaseBatch, deleteRelations, exportFiles } = testPlanStore.effects;
  const checked = isAll || !!size(primaryKeys);
  const afterDeleteRef = React.useRef(afterDelete);
  const onClick = useCallback(
    ({ key }: any) => {
      if (key !== 'excel' && (!caseTotal || !checked)) {
        message.error(i18n.t('dop:After the use case is selected, the batch operation can be performed.'));
        return;
      }
      let selectProjectId;
      let searchQuery = {};
      switch (key) {
        case 'delete':
          Modal.confirm({
            title: i18n.t('Remove'),
            content: i18n.t('dop:plan-remove-case-confirm'),
            onOk: () => deleteRelations({ type: 'multi', relationIDs: [] }).then(afterDeleteRef.current),
          });
          break;
        case 'actor':
          setVisible(true);
          break;
        case 'remark':
          openRemarkModal({ type: 'multi', remark: '' });
          break;
        case 'excel':
          selectProjectId = params.projectId;
          searchQuery = omit(formatQuery(query), ['selectProjectId', 'testPlanId', 'testSetId', 'eventKey']);

          // eslint-disable-next-line no-case-declarations
          let queryParam = qs.stringify(omitBy({ selectProjectId, ...params }, isNull), { arrayFormat: 'none' });
          queryParam += qs.stringify(searchQuery, { arrayFormat: 'none' });
          queryParam += `&${qs.stringify({ relationID: primaryKeys }, { arrayFormat: 'none' })}`;
          exportFiles(`${queryParam}&fileType=excel`);
          break;
        default:
          break;
      }
    },
    [caseTotal, checked, deleteRelations, exportFiles, openRemarkModal, params, query, primaryKeys],
  );

  const menus = useMemo(() => {
    return (
      <Menu onClick={onClick}>
        <Menu.Item key="delete">
          <span>{i18n.t('Delete')}</span>
        </Menu.Item>
        <Menu.Item key="actor">
          <span>{i18n.t('dop:Change Executor')}</span>
        </Menu.Item>
        {/* <Menu.Item key="remark">
          <span>添加备注</span>
        </Menu.Item> */}
        <Menu.Item key="excel">
          <span>{i18n.t('dop:Export Excel')}</span>
        </Menu.Item>
      </Menu>
    );
  }, [onClick]);

  const onCancel = () => {
    setVisible(false);
  };

  const handleOk = (data: Pick<TEST_PLAN.PlanBatch, 'executorID'>) => {
    planUserCaseBatch(data).then(() => {
      onCancel();
    });
  };

  const fieldsList = [
    {
      label: i18n.t('dop:Executor'),
      name: 'executorID',
      getComp: () => <MemberSelector scopeType="project" scopeId={params.projectId} />,
    },
  ];
  return (
    <>
      <DropdownSelect overlay={menus} buttonText={i18n.t('dop:Batch Operations')} />
      <FormModal
        title={i18n.t('dop:Change Executor')}
        visible={visible}
        onOk={handleOk}
        onCancel={onCancel}
        fieldsList={fieldsList}
      />
    </>
  );
}
Example #19
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
CaseImport = ({ visible, onCancel }: IProps) => {
  const [query, setQuery] = useState(defaultQuery as any);
  const { getCases: oldGetCases } = testCaseStore.effects;
  const { clearChoosenInfo } = testCaseStore.reducers;
  const { addToPlanInCaseModal } = testPlanStore.effects;
  const [{ testPlanId }, routeQuery] = routeInfoStore.useStore((s) => [s.params, s.query]);
  const [modalCaseTotal, modalChoosenInfo] = testCaseStore.useStore((s) => [s.modalCaseTotal, s.modalChoosenInfo]);
  const { primaryKeys } = modalChoosenInfo;
  const checked = size(primaryKeys);
  const [confirmLoading] = useLoading(testPlanStore, ['addToPlanInCaseModal']);
  const [priorityFilter, setPriorityFilter] = useState(routeQuery.priority);

  React.useEffect(() => {
    if (!visible) {
      setQuery(defaultQuery);
    }
  }, [visible]);

  React.useEffect(() => {
    setPriorityFilter(routeQuery.priority);
  }, [routeQuery.priority]);

  useUpdateEffect(() => {
    getCases({
      ...query,
      pageNo: 1,
      priority: priorityFilter,
      notInTestPlanID: testPlanId,
      testPlanId: undefined, // 引用的弹框里不传testPlanId,要置为undefined
    });
  }, [priorityFilter]);

  const getCases = useCallback(
    (newQuery: TEST_CASE.QueryCase) => {
      oldGetCases({ ...newQuery, scope: 'caseModal' });
      setQuery(newQuery);
    },
    [oldGetCases],
  );

  const onSelect = useCallback(
    (info: any) => {
      if (!info) {
        return;
      }
      const { testSetID, parentID } = info;
      const searchQuery: any = { ...query, parentID, testSetID };
      // 左侧选择集合变了时,重置pageNO
      if (testSetID !== query.testSetID) {
        searchQuery.pageNo = 1;
      }

      getCases({
        ...searchQuery,
        priority: priorityFilter,
        notInTestPlanID: testPlanId,
        testPlanId: undefined, // 引用的弹框里不传testPlanId,要置为undefined
      });
    },
    [getCases, priorityFilter, query, testPlanId],
  );

  const onTableChange = useCallback(
    (newQuery: object) => {
      setQuery({ ...query, ...newQuery });
    },
    [query],
  );

  const onOk = () => {
    if (!modalCaseTotal || !checked) {
      message.error(i18n.t('dop:After the use case is selected, the batch operation can be performed.'));
      return;
    }
    setPriorityFilter(routeQuery.priority);
    const newQuery: any = { ...query, testPlanId };
    delete newQuery.pageNo;
    delete newQuery.pageSize;
    addToPlanInCaseModal(newQuery).then(() => {
      handleCancel();
    });
  };

  const modalQuery = useMemo(() => {
    return {
      testSetID: query.testSetID,
      pageNo: query.pageNo || 1,
      notInTestPlanID: testPlanId,
      pageSize: query.pageSize,
      priority: priorityFilter,
    };
  }, [query.testSetID, query.pageNo, query.pageSize, testPlanId, priorityFilter]);

  const handleCancel = () => {
    onCancel();
    setPriorityFilter(routeQuery.priority);
    clearChoosenInfo({ mode: 'caseModal' });
  };

  const handleFilter = (value: any) => {
    setPriorityFilter(value);
  };

  return (
    <Modal
      closable={false}
      visible={visible}
      title={i18n.t('dop:Import use case')}
      width={1000}
      onOk={onOk}
      onCancel={handleCancel}
      confirmLoading={confirmLoading}
      destroyOnClose
    >
      <div className="case-import-content">
        <div className="left">
          <CaseTree mode="caseModal" readOnly onSelect={onSelect} />
        </div>
        <div className="right">
          <Select
            style={{ width: 240 }}
            placeholder={i18n.t('dop:Filter by priority')}
            mode="multiple"
            value={priorityFilter}
            onChange={handleFilter}
            className="mb-4"
          >
            {priorityList.map((item) => (
              <Option value={item} key={item}>
                {item}
              </Option>
            ))}
          </Select>

          <CaseTable columns={commonColumns} scope="caseModal" onChange={onTableChange} modalQuery={modalQuery} />
        </div>
      </div>
    </Modal>
  );
}
Example #20
Source File: pivot-facet-spec.ts    From S2 with MIT License 4 votes vote down vote up
describe('Pivot Mode Facet Test', () => {
  const s2: SpreadSheet = new MockSpreadSheet();
  const dataSet: PivotDataSet = new MockPivotDataSet(s2);
  s2.dataSet = dataSet;
  s2.interaction = new RootInteraction(s2);

  const facet: PivotFacet = new PivotFacet({
    spreadsheet: s2,
    dataSet,
    dataCell: (fct) => new DataCell(fct, s2),
    ...assembleDataCfg().fields,
    ...assembleOptions(),
    ...DEFAULT_STYLE,
  });

  describe('should get correct hierarchy', () => {
    const { cellCfg, colCfg, rows, spreadsheet } = facet.cfg;
    const { rowsHierarchy, colsHierarchy, colLeafNodes } = facet.layoutResult;
    const rowCellStyle = spreadsheet.theme.rowCell.cell;
    const width = Math.max(
      DEFAULT_STYLE.cellCfg.width,
      DEFAULT_OPTIONS.width / (size(rows) + size(colLeafNodes)),
    );
    test('row hierarchy', () => {
      expect(rowsHierarchy.getIndexNodes()).toHaveLength(8);
      expect(rowsHierarchy.getLeaves()).toHaveLength(8);
      expect(rowsHierarchy.getNodes()).toHaveLength(10);
      expect(rowsHierarchy.getNodes(0)).toHaveLength(2);

      rowsHierarchy.getLeaves().forEach((node, index) => {
        expect(node.width).toBe(width);
        expect(node.height).toBe(
          cellCfg.height +
            rowCellStyle.padding?.top +
            rowCellStyle.padding?.bottom,
        );
        expect(node.x).toBe(width * node.level);
        expect(node.y).toBe(node.height * index);
      });

      expect(rowsHierarchy.width).toBe(
        rowsHierarchy.sampleNodesForAllLevels
          .map((node) => node.width)
          .reduce((sum, current) => sum + current),
      );
      expect(rowsHierarchy.height).toBe(
        rowsHierarchy
          .getLeaves()
          .map((node) => node.height)
          .reduce((sum, current) => sum + current),
      );
    });
    test('col hierarchy', () => {
      expect(colsHierarchy.getIndexNodes()).toHaveLength(4);
      expect(colsHierarchy.getLeaves()).toHaveLength(4);
      expect(colsHierarchy.getNodes()).toHaveLength(6);
      expect(colsHierarchy.getNodes(0)).toHaveLength(2);

      colsHierarchy.getLeaves().forEach((node, index) => {
        expect(node.width).toBe(width);
        expect(node.height).toBe(colCfg.height);
        expect(node.x).toBe(width * index);
        expect(node.y).toBe(node.height * node.level);
      });

      expect(colsHierarchy.width).toBe(
        colsHierarchy
          .getLeaves()
          .map((node) => node.width)
          .reduce((sum, current) => sum + current),
      );
      expect(colsHierarchy.height).toBe(
        colsHierarchy.sampleNodesForAllLevels
          .map((node) => node.height)
          .reduce((sum, current) => sum + current),
      );
    });
  });

  describe('should get correct cell meta', () => {
    const { getCellMeta } = facet.layoutResult;

    test('should get correct cell meta', () => {
      expect(getCellMeta(0, 1)?.data?.number).toBe(5343);
      expect(getCellMeta(1, 1)?.data?.number).toBe(632);

      expect(getCellMeta(1)?.data?.number).toBe(2367);
    });
  });

  describe('should get correct result when tree mode', () => {
    s2.isHierarchyTreeType = jest.fn().mockReturnValue(true);
    const ds = new MockPivotDataSet(s2);
    const treeFacet = new PivotFacet({
      spreadsheet: s2,
      dataSet: ds,
      ...assembleDataCfg().fields,
      ...assembleOptions(),
      ...DEFAULT_STYLE,
      hierarchyType: 'tree',
    });
    const { rowsHierarchy } = treeFacet.layoutResult;

    test('row hierarchy when tree mode', () => {
      const { cellCfg, spreadsheet } = facet.cfg;
      const rowCellStyle = spreadsheet.theme.rowCell.cell;
      const width = facet.cfg.treeRowsWidth;

      expect(rowsHierarchy.getLeaves()).toHaveLength(8);
      expect(rowsHierarchy.getNodes()).toHaveLength(10);
      expect(rowsHierarchy.width).toBe(width);

      rowsHierarchy.getNodes().forEach((node, index) => {
        expect(node.width).toBe(width);
        expect(node.height).toBe(
          cellCfg.height +
            rowCellStyle.padding?.top +
            rowCellStyle.padding?.bottom,
        );
        expect(node.x).toBe(0);
        expect(node.y).toBe(node.height * index);
      });
    });
  });

  describe('should get correct layer after render', () => {
    facet.render();
    const {
      rowHeader,
      cornerHeader,
      columnHeader,
      centerFrame,
      backgroundGroup,
    } = facet;
    test('get header after render', () => {
      expect(rowHeader instanceof RowHeader).toBeTrue();
      expect(rowHeader.cfg.children).toHaveLength(10);
      expect(rowHeader.cfg.visible).toBeTrue();

      expect(cornerHeader instanceof CornerHeader).toBeTrue();
      expect(cornerHeader.cfg.children).toHaveLength(2);
      expect(cornerHeader.cfg.visible).toBeTrue();

      expect(columnHeader instanceof ColHeader).toBeTrue();
      expect(centerFrame instanceof Frame).toBeTrue();
    });

    test('get background after render', () => {
      const rect = get(backgroundGroup, 'cfg.children[0]');

      expect(backgroundGroup.cfg.children).toHaveLength(1);
      expect(rect.cfg.type).toBe('rect');
      expect(rect.cfg.visible).toBeTrue();
    });

    test('get cell after render', () => {
      const { panelScrollGroup } = s2;
      const sampleDataCell = get(panelScrollGroup, 'cfg.children[0]');
      expect(panelScrollGroup.cfg.children).toHaveLength(32);
      expect(panelScrollGroup.cfg.visible).toBeTrue();
      expect(get(sampleDataCell, 'meta.data.number')).toBe(7789);
    });
  });
});