lodash#uniqBy TypeScript Examples

The following examples show how to use lodash#uniqBy. 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: utils.ts    From gant-design with MIT License 7 votes vote down vote up
export function getAllChildrenNode(
  targetKeys: any[],
  api: GridApi,
  deleteChildren = false,
): RowNode[] {
  const targetNodes: RowNode[] = [];
  targetKeys.map(key => {
    const itemNode = api.getRowNode(key);
    itemNode && targetNodes.push(itemNode);
  });
  if (deleteChildren) return targetNodes;
  const allNodes: RowNode[] = [];
  const groupNodes = groupBy(targetNodes, 'level');
  let level = min(Object.keys(groupNodes).map(level => level));
  while (!isEmpty(groupNodes[level])) {
    const list = groupNodes[level];
    let nextLevel: any = parseInt(level) + 1;
    nextLevel = nextLevel.toString();
    groupNodes[nextLevel] = groupNodes[nextLevel] ? groupNodes[nextLevel] : [];
    list.map(itemNode => {
      const { childrenAfterGroup = [] } = itemNode as RowNode;
      groupNodes[nextLevel].push(...childrenAfterGroup);
      return itemNode.data;
    });
    groupNodes[nextLevel] = uniqBy(groupNodes[nextLevel], 'id');
    allNodes.push(...list);
    level = nextLevel;
  }
  return allNodes;
}
Example #2
Source File: index.ts    From gant-design with MIT License 6 votes vote down vote up
//批量更新数据 返回更新后的gird数据
  batchUpdateDataSource(params: BatchUpdateDataSourceParams, keys: string | string[] = []) {
    const { getRowNodeId } = this.agGridConfig;
    const dataSource: any = [];
    const { add, modify, remove } = params;
    const update = uniqBy([...add, ...modify], 'dataNumber');
    const removeKeys = remove.map(item => getRowNodeId(item));
    const removeNodes = getAllChildrenNode(removeKeys, this.agGridApi, false);
    const assignKeys = typeof keys === 'string' ? [keys] : keys;
    this.agGridApi.forEachNode(function(node, index) {
      const removeIndex = findIndex(
        removeNodes,
        item => getRowNodeId(get(node, 'data')) === getRowNodeId(get(item, 'data')),
      );
      if (removeIndex >= 0) return;
      const updateIndex = findIndex(update, item => item.dataNumber === index);
      const { _rowType, _rowData, _rowCut, _rowError, treeDataPath, ...data } = get(
        node,
        'data',
        {},
      );
      if (updateIndex >= 0) {
        const mergeData = {};
        assignKeys.map(item => {
          mergeData[item] = data[item];
        });
        const updateItem = { ...update[updateIndex], ...mergeData };
        return dataSource.push(updateItem);
      }
      dataSource.push(data);
    } as any);
    console.log('batchUpdateDataSource');
    return dataSource;
  }
Example #3
Source File: vaults.ts    From yearn-watch-legacy with GNU Affero General Public License v3.0 6 votes vote down vote up
sortVaultsByVersion = (
    vaults: VaultApi[],
    desc = true
): VaultApi[] => {
    const uniqueVaults = uniqBy(vaults, 'address');
    uniqueVaults.sort((x, y) => {
        const versions = compareVersions(
            x.apiVersion || '0.0.0',
            y.apiVersion || '0.0.0'
        );
        if (versions !== 0) {
            return versions;
        } else {
            return x.strategies.length - y.strategies.length;
        }
    });

    if (desc) {
        return uniqueVaults.reverse();
    }
    return uniqueVaults;
}
Example #4
Source File: News.tsx    From wuhan2020-frontend-react-native-app with MIT License 6 votes vote down vote up
function NewsScreen() {
  const { data, loading, fetchMore, refresh } = useContext(NewsDataContext);
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = useCallback(() => {
    setRefreshing(true);

    refresh();
    wait(2000).then(() => setRefreshing(false));
  }, [refreshing, refresh]);

  const news = uniqBy(data || [], 'sourceId');

  return (
    <StatusBarSafeLayout>
      <View style={styles.constainer}>
        <H1 title="新闻汇总" />
      </View>

      <ScrollView
        refreshControl={
          <RefreshControl
            tintColor="pink"
            refreshing={refreshing}
            onRefresh={onRefresh}
          />
        }
        onMomentumScrollEnd={fetchMore}>
        {news.map((entry: EntryPropsType) => (
          <Entry key={entry.sourceId} {...entry} />
        ))}
        {loading ? <ActivityIndicator size="large" color="red" /> : null}
      </ScrollView>
    </StatusBarSafeLayout>
  );
}
Example #5
Source File: group.service.ts    From wise-old-man with MIT License 6 votes vote down vote up
/**
 * Returns a list of all groups of which a given player is a member.
 */
async function getPlayerGroups(playerId: number, pagination: Pagination): Promise<ExtendedGroup[]> {
  // Find all memberships for the player
  const memberships = await Membership.findAll({
    where: { playerId },
    include: [{ model: Group }]
  });

  // Extract all the unique groups from the memberships, and format them.
  const groups = uniqBy(memberships, (m: Membership) => m.group.id)
    .slice(pagination.offset, pagination.offset + pagination.limit)
    .map(p => p.group)
    .sort((a, b) => b.score - a.score);

  const extendedGroups = await extendGroups(groups);

  // Add the player's role to every group object
  extendedGroups.forEach(g => {
    memberships.forEach(m => {
      if (m.groupId === g.id) g.role = m.role;
    });
  });

  return extendedGroups;
}
Example #6
Source File: competition.service.ts    From wise-old-man with MIT License 6 votes vote down vote up
/**
 * Set the participants of a competition.
 *
 * This will replace any existing participants.
 */
async function setParticipants(competition: Competition, usernames: string[]) {
  if (!competition) throw new BadRequestError(`Invalid competition.`);

  const uniqueUsernames = uniqBy(usernames, u => playerService.standardize(u));

  const existingParticipants = await competition.$get('participants');
  const existingUsernames = existingParticipants.map(e => e.username);

  const usernamesToAdd = uniqueUsernames.filter(
    u => !existingUsernames.includes(playerService.standardize(u))
  );

  const playersToRemove = existingParticipants.filter(
    p => !uniqueUsernames.map(playerService.standardize).includes(p.username)
  );

  const playersToAdd = await playerService.findAllOrCreate(usernamesToAdd);

  if (playersToRemove && playersToRemove.length > 0) {
    await competition.$remove('participants', playersToRemove);
  }

  if (playersToAdd && playersToAdd.length > 0) {
    await competition.$add('participants', playersToAdd);
  }

  const participants = await competition.$get('participants');
  return participants.map(p => omit(p.toJSON(), ['participations']));
}
Example #7
Source File: graph.ts    From nebula-studio with Apache License 2.0 6 votes vote down vote up
getExploreVertex = async (payload: {
    ids: string[];
    expand?: {
      vertexStyle: string;
      customColor: string;
      customIcon: string;
    };
  }) => {
    const { ids, expand } = payload;
    const _ids = uniqBy(ids, (i) => convertBigNumberToString(i));
    const vertexes: any =
      _ids.length > 0
        ? await this.getVertexes({
          ids: _ids,
          expand,
        })
        : [];
    return uniqBy(vertexes, (i: NodeObject) => convertBigNumberToString(i.id)).filter((i) => i !== undefined);
  };
Example #8
Source File: groupAdjacentAminoacidChanges.ts    From nextclade with MIT License 6 votes vote down vote up
public add(change: AminoacidChange): void {
    // precondition_equal(change.gene === this.gene)
    // precondition_less(changes[changes.length - 1].codon, change.codon) // changes should be sorted by codon

    this.codonAaRange.end = change.codon + 1
    this.codonNucRange.end = change.contextNucRange.end
    this.changes.push(change)
    this.refContext = mergeContext(this.refContext, change.refContext)
    this.queryContext = mergeContext(this.queryContext, change.queryContext)
    this.contextNucRange.end = change.contextNucRange.end

    this.nucSubstitutions = this.nucSubstitutions.concat(change.nucSubstitutions)
    this.nucSubstitutions = uniqBy(this.nucSubstitutions, (sub) => sub.pos)

    this.nucDeletions = this.nucDeletions.concat(change.nucDeletions)
    this.nucDeletions = uniqBy(this.nucDeletions, (del) => del.start)

    this.updateCounts()
  }
Example #9
Source File: list.monad.ts    From relate with GNU General Public License v3.0 6 votes vote down vote up
unique(predicate?: (val: T) => any) {
        if (predicate) {
            // @ts-ignore
            return this.map((val) => uniqBy(val, predicate));
        }

        // @ts-ignore
        return this.map(uniq);
    }
Example #10
Source File: bulkReactions.ts    From commonwealth with GNU General Public License v3.0 6 votes vote down vote up
bulkReactions = async (models: DB, req: Request, res: Response, next: NextFunction) => {
  const { thread_id, proposal_id, comment_id } = req.query;
  let reactions = [];
  try {
    if (thread_id || proposal_id || comment_id) {
      reactions = await models.OffchainReaction.findAll({
        where: {
          thread_id: thread_id || null,
          proposal_id: proposal_id || null,
          comment_id: comment_id || null
        },
        include: [ models.Address ],
        order: [['created_at', 'DESC']],
      });
    }
  } catch (err) {
    return next(new Error(err));
  }

  return res.json({ status: 'Success', result: uniqBy(reactions.map((c) => c.toJSON()), 'id') });
}
Example #11
Source File: index.ts    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
genNodes = (list: TOPOLOGY.INode[], edges: Edge[]): Node<TOPOLOGY.TopoNode>[] => {
  const nodes: Node<TOPOLOGY.TopoNode>[] = [];
  cloneDeep(list).forEach((item) => {
    const { parents = [], ...rest } = item;
    const children = edges.filter((t) => t.source === rest.id).map((t) => t.target);
    const childrenCount = children.length;
    const parent = edges.filter((t) => t.target === rest.id).map((t) => t.source);
    const parentCount = parent.length;
    const isCircular = parent.some((t) => children.includes(t));
    const isLeaf = !childrenCount;
    nodes.push({
      id: rest.id,
      type: getNodeType(rest.type),
      data: {
        isAddon: isAddon(rest.type),
        isService: isService(rest.type),
        isExternalService: isExternalService(rest.type),
        isCircular,
        isUnhealthy: rest.metric.error_rate > 0,
        hoverStatus: 0,
        selectStatus: 0,
        isRoot: !parentCount,
        isParent: !isLeaf,
        isLeaf,
        childrenCount,
        parentCount,
        label: rest.name,
        metaData: rest,
      },
      position: {
        x: 0,
        y: 0,
      },
    });
  });
  return uniqBy(nodes, 'id');
}
Example #12
Source File: assetInfoDbService.ts    From nautilus-wallet with MIT License 6 votes vote down vote up
public async addIfNotExists(assets: IAssetInfo[]) {
    if (isEmpty(assets)) {
      return;
    }

    assets = uniqBy(assets, (a) => a.id);
    const paramIds = assets.map((a) => a.id);
    const dbIds = await dbContext.assetInfo.where("id").anyOf(paramIds).primaryKeys();
    const uncommited = difference(paramIds, dbIds);

    if (!isEmpty(uncommited)) {
      await dbContext.assetInfo.bulkAdd(assets.filter((a) => uncommited.includes(a.id)));
    }
  }
Example #13
Source File: index.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
// istanbul ignore next
  private _handleRowSelectChange = (
    selectedRowKeys: string[],
    selectedRows: any[]
  ): void => {
    const rowKey =
      this.rowKey ?? this._fields.rowKey ?? this.configProps?.rowKey;
    this._selectedRows = selectedRows;
    if (this._selected) {
      const _selectedRows = [...selectedRows, ...this._allChildren];
      if (this.autoSelectParentWhenAllChildrenSelected && this._selectedRow) {
        const selectedRowKeySet = new Set(selectedRowKeys);
        const parent = this._findParentByChildKeyValue(
          this._selectedRow[rowKey] as string,
          rowKey,
          this._dataSource
        );

        if (
          parent &&
          (parent[this.childrenColumnName] as Record<string, unknown>[]).every(
            (item) => selectedRowKeySet.has(item[rowKey] as string)
          )
        ) {
          _selectedRows.push(parent);
        }
      }
      this._selectedRows = uniqBy(_selectedRows, rowKey);
    } else {
      let parent: Record<string, unknown>;

      if (this.autoSelectParentWhenAllChildrenSelected && this._selectedRow) {
        parent = this._findParentByChildKeyValue(
          this._selectedRow[rowKey] as string,
          rowKey,
          this._dataSource
        );
      }
      this._selectedRows = pullAllBy(
        selectedRows,
        this._allChildren.concat(parent),
        rowKey
      );
    }
    this._selectedRow = undefined;
    this.selectedRowKeys = map(this._selectedRows, rowKey);

    let detail = null;
    const data = isEmpty(this._selectUpdateEventDetailField)
      ? this._selectedRows
      : map(this._selectedRows, (row) =>
          get(row, this._selectUpdateEventDetailField)
        );
    detail =
      isEmpty(this._selectUpdateEventDetailKeys) || isEmpty(data)
        ? data
        : set({}, this._selectUpdateEventDetailKeys, data);
    if (!isEmpty(detail)) {
      detail = merge(detail, this._selectUpdateEventDetailExtra);
    }
    if (!this._selectUpdateEventName) {
      this.selectUpdate.emit(detail);
    } else {
      const eventName = this._selectUpdateEventName
        ? this._selectUpdateEventName
        : "select.update";
      this.dispatchEvent(new CustomEvent(eventName, { detail }));
    }
  };
Example #14
Source File: index.tsx    From nebula-studio with Apache License 2.0 5 votes vote down vote up
ConfigCreate = (props: IProps) => {
  const { createType } = props;
  const history = useHistory();
  const [loading, setLoading] = useState(false);
  const { schema: { createTagOrEdge } } = useStore();
  const [gql, setGql] = useState('');
  const [basicForm] = Form.useForm();
  useEffect(() => {
    trackPageView(`/schema/${createType}/create`);
  }, []);

  const updateGql = () => {
    const { name, properties, ttl_col, ttl_duration, comment } = basicForm.getFieldsValue();
    const currentGQL = name
      ? getTagOrEdgeCreateGQL({
        type: createType,
        name,
        properties,
        ttl_col,
        ttl_duration,
        comment,
      })
      : '';
    setGql(currentGQL);
  };
  const handleCreate = async (values) => {
    const { name, properties } = values;
    const uniqProperties = uniqBy(properties, 'name');
    if (properties && properties.length !== uniqProperties.length) {
      return message.warning(intl.get('schema.uniqProperty'));
    } 
    setLoading(true);
    const res = await createTagOrEdge({
      gql,
      type: createType
    });
    setLoading(false);
    if (res.code === 0) {
      message.success(intl.get('schema.createSuccess'));
      history.push({
        pathname: `/schema/${createType}/edit`,
        state: { [createType]: name },
      });
    }
  };
  return (
    <div className={styles.configFormGroup}>
      <Form form={basicForm} 
        onFieldsChange={updateGql}
        name="basicForm" 
        layout="vertical" 
        onFinish={handleCreate}
        {...formItemLayout}>
        <Row className={styles.formItem}>
          <Col span={12}>
            <Form.Item label={intl.get('common.name')} name="name" rules={nameRulesFn()}>
              <Input />
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label={intl.get('common.comment')} name="comment">
              <Input />
            </Form.Item>
          </Col>
        </Row>
        <PropertiesForm formRef={basicForm} onUpdate={updateGql} />
        <TTLForm formRef={basicForm} onUpdate={updateGql} />
        <Form.Item noStyle>
          <GQLCodeMirror currentGQL={gql} />
        </Form.Item>
        <Form.Item noStyle>
          <div className="studioFormFooter">
            <Button onClick={() => history.push(`/schema/${createType}/list`)}>{intl.get('common.cancel')}</Button>
            <Button type="primary" loading={loading} htmlType="submit">{intl.get('common.create')}</Button>
          </div>
        </Form.Item>
      </Form>
    </div>
  );
}
Example #15
Source File: dbContext.ts    From nautilus-wallet with MIT License 5 votes vote down vote up
constructor() {
    super("nautilusDb");
    this.version(1).stores({
      wallets: "++id, network, &publicKey",
      addresses: "&script, type, walletId",
      assets: "&[tokenId+address], &[address+tokenId], walletId"
    });

    this.version(2).stores({
      addresses: "&script, type, state, walletId"
    });

    this.version(3).stores({
      connectedDApps: "&origin, walletId",
      addresses: "&script, type, state, index, walletId"
    });

    this.version(4).upgrade(async (t) => {
      t.table("wallets").each((obj, k) => {
        t.table("wallets").update(k.primaryKey, {
          "settings.avoidAddressReuse": false,
          "settings.hideUsedAddresses": false,
          "settings.defaultChangeIndex": 0
        });
      });
    });

    this.version(5).stores({
      utxos: "&id, spentTxId, address, walletId"
    });

    this.version(6)
      .stores({
        assetInfo: "&id, mintingBoxId, type, subtype"
      })
      .upgrade(async (t) => {
        const assets = await t.table("assets").toArray();
        if (assets.length === 0) {
          return;
        }

        const assetInfo = uniqBy(
          assets
            .filter((a) => a.tokenId !== ERG_TOKEN_ID)
            .map((a) => {
              return {
                id: a.tokenId,
                mintingBoxId: UNKNOWN_MINTING_BOX_ID,
                decimals: a.decimals,
                name: a.name
              } as IAssetInfo;
            }),
          (a) => a.id
        );
        await t.table("assetInfo").bulkAdd(assetInfo);
      });
  }
Example #16
Source File: RichHistoryStarredTab.tsx    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
export function RichHistoryStarredTab(props: Props) {
  const {
    datasourceFilters,
    onSelectDatasourceFilters,
    queries,
    onChangeSortOrder,
    sortOrder,
    activeDatasourceOnly,
    exploreId,
  } = props;

  const theme = useTheme();
  const styles = getStyles(theme);

  const datasourcesRetrievedFromQueryHistory = uniqBy(queries, 'datasourceName').map(d => d.datasourceName);
  const listOfDatasources = createDatasourcesList(datasourcesRetrievedFromQueryHistory);

  const listOfDatasourceFilters = datasourceFilters?.map(d => d.value);

  const starredQueries = queries.filter(q => q.starred === true);
  const starredQueriesFilteredByDatasource = datasourceFilters
    ? starredQueries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName))
    : starredQueries;

  const sortedStarredQueries = sortQueries(starredQueriesFilteredByDatasource, sortOrder);

  return (
    <div className={styles.container}>
      <div className={styles.containerContent}>
        <div className={styles.selectors}>
          {!activeDatasourceOnly && (
            <div aria-label="Filter datasources" className={styles.multiselect}>
              <Select
                isMulti={true}
                options={listOfDatasources}
                value={datasourceFilters}
                placeholder="Filter queries for specific data sources(s)"
                onChange={onSelectDatasourceFilters}
              />
            </div>
          )}
          <div aria-label="Sort queries" className={styles.sort}>
            <Select
              options={sortOrderOptions}
              value={sortOrderOptions.filter(order => order.value === sortOrder)}
              placeholder="Sort queries by"
              onChange={e => onChangeSortOrder(e.value as SortOrder)}
            />
          </div>
        </div>
        {sortedStarredQueries.map(q => {
          const idx = listOfDatasources.findIndex(d => d.label === q.datasourceName);
          return (
            <RichHistoryCard
              query={q}
              key={q.ts}
              exploreId={exploreId}
              dsImg={listOfDatasources[idx].imgUrl}
              isRemoved={listOfDatasources[idx].isRemoved}
            />
          );
        })}
        <div className={styles.feedback}>
          Query history is a beta feature. The history is local to your browser and is not shared with others.
          <a href="https://github.com/grafana/grafana/issues/new/choose">Feedback?</a>
        </div>
      </div>
    </div>
  );
}
Example #17
Source File: TicketClassifierSelect.tsx    From condo with MIT License 5 votes vote down vote up
useTicketClassifierSelectHook = ({
    onChange,
    onSearch,
    initialValue,
}: ITicketClassifierSelectHookInput): ITicketClassifierSelectHookOutput => {
    const intl = useIntl()
    const SelectMessage = intl.formatMessage({ id: 'Select' })

    const [classifiers, setClassifiersFromRules] = useState<Options[]>([])
    const [searchClassifiers, setSearchClassifiers] = useState<Options[]>([])
    const classifiersRef = useRef<HTMLSelectElement>(null)
    const optionsRef = useRef<Options[]>([])
    const selectedRef = useRef<string>(null)

    const setClassifiers = (classifiers) => {
        setClassifiersFromRules(classifiers)
        // We need to remove search classifiers when rules start to work
        setSearchClassifiers([])
    }

    function setSelected (value) {
        selectedRef.current = value
        // Remove search classifiers when user chosen smth - only classifiers will work for now
        setSearchClassifiers([])
    }

    useEffect(() => {
        optionsRef.current = uniqBy([...classifiers, ...searchClassifiers], 'id')
    }, [classifiers, searchClassifiers])

    const SelectComponent = useMemo(() => {
        const SelectComponentWrapper: ClassifierSelectComponent = (props) => {
            const { disabled, style } = props
            return (
                <Select
                    showSearch
                    style={style}
                    allowClear={true}
                    onSelect={onChange}
                    onSearch={onSearch}
                    onClear={() => onChange(null)}
                    optionFilterProp={'title'}
                    defaultActiveFirstOption={false}
                    disabled={disabled}
                    defaultValue={initialValue}
                    ref={classifiersRef}
                    value={selectedRef.current}
                    showAction={['focus', 'click']}
                    placeholder={SelectMessage}

                >
                    {
                        optionsRef.current.map(classifier => (
                            <Option data-cy={'ticket__classifier-option'} value={classifier.id} key={classifier.id} title={classifier.name}>{classifier.name}</Option>
                        ))
                    }

                </Select>
            )
        }
        return SelectComponentWrapper
    }, [initialValue, onSearch, onChange])

    return {
        SelectComponent,
        set: {
            all: setClassifiers,
            one: setSelected,
            search: setSearchClassifiers,
        },
        ref: classifiersRef,
    }
}
Example #18
Source File: TicketAssignments.tsx    From condo with MIT License 4 votes vote down vote up
TicketAssignments = ({
    validations,
    organizationId,
    propertyId,
    disableUserInteraction,
    autoAssign,
    categoryClassifier,
    form,
}: TicketAssignmentsProps) => {
    const intl = useIntl()
    const TicketAssignmentTitle = intl.formatMessage({ id: 'TicketAssignment' })
    const ExecutorLabel = intl.formatMessage({ id: 'field.Executor' })
    const ResponsibleLabel = intl.formatMessage({ id: 'field.Responsible' })
    const ExecutorExtra = intl.formatMessage({ id: 'field.Executor.description' })
    const ResponsibleExtra = intl.formatMessage({ id: 'field.Responsible.description' })
    const ExecutorsOnThisDivisionLabel = intl.formatMessage({ id: 'ticket.assignments.executor.OnThisDivision' })
    const ExecutorsOnOtherDivisionsLabel = intl.formatMessage({ id: 'ticket.assignments.executor.OnOtherDivisions' })
    const OtherExecutors = intl.formatMessage({ id: 'ticket.assignments.executor.Other' })

    const { isSmall } = useLayoutContext()

    const [divisions, setDivisions] = useState([])

    const formatUserFieldLabel = ({ text, value, data: employee }) => {
        if (!employee) {
            return null
        }
        const matchedSpecialization = find(employee.specializations, { id: categoryClassifier })
        return (
            <UserNameField user={{ name: text, id: value }}>
                {({ name, postfix }) => (
                    <>
                        <Typography.Text>
                            {name} {postfix}
                        </Typography.Text>
                        {matchedSpecialization && (
                            <Typography.Text type="secondary">
                                {`(${matchedSpecialization.name.toLowerCase()})`}
                            </Typography.Text>
                        )}
                    </>
                )}
            </UserNameField>
        )
    }

    const getTechniciansFrom = (division) => (
        division.executors.filter(({ specializations }) => (
            specializations.some(({ id }) => id === categoryClassifier)
        ))
    )

    const convertToOption = (employee) => ({
        text: employee.name,
        value: employee.user.id,
        data: employee,
    })

    /**
     * Employees are grouped by following rules:
     * 1. Technicians with matched specialization, belonging to matched division;
     * 2. Technicians with matched specialization, belonging to other matched divisions;
     * 3. Rest of employees.
     */
    const renderOptionGroups = (employeeOptions, renderOption) => {
        const [currentDivision, ...otherDivisions] = divisions
        let techniciansOnDivisionOptions = []
        let techniciansOnOtherDivisionsOptions = []

        if (currentDivision) {
            const techniciansOnDivision = getTechniciansFrom(currentDivision)
            techniciansOnDivisionOptions = techniciansOnDivision.map(convertToOption)

            const techniciansOnOtherDivisions =
                differenceBy(
                    uniqBy(
                        otherDivisions.reduce((acc, otherDivision) => ([
                            ...acc,
                            ...getTechniciansFrom(otherDivision),
                        ]), []),
                        'id',
                    ),
                    techniciansOnDivision,
                    'id',
                )

            techniciansOnOtherDivisionsOptions = techniciansOnOtherDivisions.map(convertToOption)
        }

        const otherTechniciansOptions = differenceBy(employeeOptions, [
            ...techniciansOnDivisionOptions,
            ...techniciansOnOtherDivisionsOptions,
        ], 'value')

        const result = []
        if (!currentDivision || techniciansOnDivisionOptions.length === 0 && techniciansOnOtherDivisionsOptions.length === 0) {
            result.push(otherTechniciansOptions.map(renderOption))
        } else {
            if (techniciansOnDivisionOptions.length > 0) {
                result.push(
                    <Select.OptGroup label={ExecutorsOnThisDivisionLabel}>
                        {techniciansOnDivisionOptions.map(renderOption)}
                    </Select.OptGroup>
                )
            }
            if (techniciansOnOtherDivisionsOptions.length > 0) {
                result.push(
                    <Select.OptGroup label={ExecutorsOnOtherDivisionsLabel}>
                        {techniciansOnOtherDivisionsOptions.map(renderOption)}
                    </Select.OptGroup>
                )
            }
            if (otherTechniciansOptions.length > 0) {
                result.push(
                    <Select.OptGroup label={OtherExecutors}>
                        {otherTechniciansOptions.map(renderOption)}
                    </Select.OptGroup>
                )
            }
        }

        return result
    }

    return (
        <Col span={24}>
            <Row gutter={[0, 8]}>
                <Col span={24}>
                    <Typography.Title level={3}>{TicketAssignmentTitle}</Typography.Title>
                </Col>
                <Col span={isSmall ? 24 : 18}>
                    <Row justify={'space-between'}>
                        {autoAssign && propertyId && (
                            <Col span={24}>
                                <AutoAssignerByDivisions
                                    organizationId={organizationId}
                                    propertyId={propertyId}
                                    categoryClassifier={categoryClassifier}
                                    onDivisionsFound={setDivisions}
                                    form={form}
                                />
                            </Col>
                        )}
                        <Col span={11}>
                            <TicketFormItem
                                name={'executor'}
                                rules={validations.executor}
                                label={<LabelWithInfo title={ExecutorExtra} message={ExecutorLabel}/>}
                            >
                                <GraphQlSearchInput
                                    showArrow={false}
                                    disabled={disableUserInteraction}
                                    formatLabel={formatUserFieldLabel}
                                    renderOptions={renderOptionGroups}
                                    search={searchEmployeeUser(organizationId, ({ role }) => (
                                        get(role, 'canBeAssignedAsExecutor', false)
                                    ))}
                                />
                            </TicketFormItem>
                        </Col>
                        <Col span={11}>
                            <TicketFormItem
                                data-cy={'ticket__assignee-item'}
                                name={'assignee'}
                                rules={validations.assignee}
                                label={<LabelWithInfo title={ResponsibleExtra} message={ResponsibleLabel}/>}
                            >
                                <GraphQlSearchInput
                                    formatLabel={formatUserFieldLabel}
                                    showArrow={false}
                                    disabled={disableUserInteraction}
                                    search={searchEmployeeUser(organizationId, ({ role }) => (
                                        get(role, 'canBeAssignedAsResponsible', false)
                                    ))}
                                />
                            </TicketFormItem>
                        </Col>
                    </Row>
                </Col>
            </Row>
        </Col>
    )
}
Example #19
Source File: upload-sheet-data.ts    From aqualink-app with MIT License 4 votes vote down vote up
uploadTimeSeriesData = async (
  filePath: string,
  fileName: string,
  siteId: string,
  surveyPointId: string | undefined,
  sourceType: SourceType,
  repositories: Repositories,
  failOnWarning?: boolean,
  mimetype?: Mimetype,
) => {
  // // TODO
  // // - Add foreign key constraint to sources on site_id
  console.time(`Upload datafile ${fileName}`);
  const { site, surveyPoint } = surveyPointId
    ? await getSiteAndSurveyPoint(
        parseInt(siteId, 10),
        parseInt(surveyPointId, 10),
        repositories.siteRepository,
        repositories.surveyPointRepository,
      )
    : {
        site: await getSite(parseInt(siteId, 10), repositories.siteRepository),
        surveyPoint: undefined,
      };

  const existingSourceEntity = await repositories.sourcesRepository.findOne({
    relations: ['surveyPoint', 'site'],
    where: {
      site: { id: siteId },
      surveyPoint: surveyPointId || null,
      type: sourceType,
    },
  });

  const sourceEntity =
    existingSourceEntity ||
    (await repositories.sourcesRepository.save({
      type: sourceType,
      site,
      surveyPoint,
    }));

  if (
    sourceType === SourceType.SONDE ||
    sourceType === SourceType.METLOG ||
    sourceType === SourceType.HOBO
  ) {
    const workSheetsFromFile = xlsx.parse(filePath, { raw: true });
    const workSheetData = workSheetsFromFile[0]?.data;
    const { ignoredHeaders, importedHeaders } = validateHeaders(
      fileName,
      workSheetData,
      sourceType,
    );

    if (failOnWarning && ignoredHeaders.length > 0) {
      throw new BadRequestException(
        `${fileName}: The columns ${ignoredHeaders
          .map((header) => `"${header}"`)
          .join(
            ', ',
          )} are not configured for import yet and cannot be uploaded.`,
      );
    }

    const signature = await md5Fle(filePath);

    const uploadExists = await repositories.dataUploadsRepository.findOne({
      where: {
        signature,
        site,
        surveyPoint,
        sensorType: sourceType,
      },
    });

    if (uploadExists) {
      throw new ConflictException(
        `${fileName}: A file upload named '${uploadExists.file}' with the same data already exists`,
      );
    }
    console.time(`Get data from sheet ${fileName}`);
    const results = findSheetDataWithHeader(
      fileName,
      workSheetData,
      sourceType,
      mimetype,
    );
    console.timeEnd(`Get data from sheet ${fileName}`);

    console.time(`Remove duplicates and empty values ${fileName}`);
    const data = uniqBy(
      results
        .reduce((timeSeriesObjects: any[], object) => {
          const { timestamp } = object;
          return [
            ...timeSeriesObjects,
            ...Object.keys(object)
              .filter((k) => k !== 'timestamp')
              .map((key) => {
                return {
                  timestamp,
                  value: parseFloat(object[key]),
                  metric: key,
                  source: sourceEntity,
                };
              }),
          ];
        }, [])
        .filter((valueObject) => {
          if (!isNaN(parseFloat(valueObject.value))) {
            return true;
          }
          logger.log('Excluding incompatible value:');
          logger.log(valueObject);
          return false;
        }),
      ({ timestamp, metric, source }) =>
        `${timestamp}, ${metric}, ${source.id}`,
    );
    console.timeEnd(`Remove duplicates and empty values ${fileName}`);

    const minDate = get(
      minBy(data, (item) => new Date(get(item, 'timestamp')).getTime()),
      'timestamp',
    );
    const maxDate = get(
      maxBy(data, (item) => new Date(get(item, 'timestamp')).getTime()),
      'timestamp',
    );

    // Initialize google cloud service, to be used for media upload
    const googleCloudService = new GoogleCloudService();

    // Note this may fail. It would still return a location, but the file may not have been uploaded
    const fileLocation = googleCloudService.uploadFileAsync(
      filePath,
      sourceType,
      'data_uploads',
      'data_upload',
    );

    const dataUploadsFile = await repositories.dataUploadsRepository.save({
      file: fileName,
      signature,
      sensorType: sourceType,
      site,
      surveyPoint,
      minDate,
      maxDate,
      metrics: importedHeaders,
      fileLocation,
    });

    const dataAsTimeSeries = data.map((x: any) => {
      return {
        timestamp: x.timestamp,
        value: x.value,
        metric: x.metric,
        source: x.source,
        dataUpload: dataUploadsFile,
      };
    });

    // Data is too big to added with one bulk insert so we batch the upload.
    console.time(`Loading into DB ${fileName}`);
    const batchSize = 100;
    logger.log(`Saving time series data in batches of ${batchSize}`);
    const inserts = chunk(dataAsTimeSeries, batchSize).map(
      async (batch: any[]) => {
        try {
          await repositories.timeSeriesRepository
            .createQueryBuilder('time_series')
            .insert()
            .values(batch)
            // If there's a conflict, replace data with the new value.
            // onConflict is deprecated, but updating it is tricky.
            // See https://github.com/typeorm/typeorm/issues/8731?fbclid=IwAR2Obg9eObtGNRXaFrtKvkvvVSWfvjtHpFu-VEM47yg89SZcPpxEcZOmcLw
            .onConflict(
              'ON CONSTRAINT "no_duplicate_data" DO UPDATE SET "value" = excluded.value',
            )
            .execute();
        } catch {
          console.warn('The following batch failed to upload:');
          console.warn(batch);
        }
        return true;
      },
    );

    // Return insert promises and print progress updates
    const actionsLength = inserts.length;
    await Bluebird.Promise.each(inserts, (props, idx) => {
      logger.log(`Saved ${idx + 1} out of ${actionsLength} batches`);
    });
    console.timeEnd(`Loading into DB ${fileName}`);
    logger.log('loading complete');

    refreshMaterializedView(repositories);

    console.timeEnd(`Upload datafile ${fileName}`);
    return ignoredHeaders;
  }

  return [];
}
Example #20
Source File: uploadsSlice.ts    From aqualink-app with MIT License 4 votes vote down vote up
uploadsSlice = createSlice({
  name: "uploads",
  initialState: uploadsSliceInitialState,
  reducers: {
    addUploadsFiles: (
      state,
      action: PayloadAction<UploadsSliceState["files"]>
    ) => {
      const newFiles = uniqBy([...state.files, ...action.payload], "name");
      return {
        ...state,
        files: newFiles,
      };
    },
    removeUploadsFiles: (state, action: PayloadAction<string>) => {
      const newFIles = state.files.filter(
        (file) => file.name !== action.payload
      );
      return {
        ...state,
        files: newFIles,
      };
    },
    clearUploadsFiles: (state) => ({
      ...state,
      files: [],
    }),
    setUploadsTarget: (
      state,
      action: PayloadAction<UploadsSliceState["target"]>
    ) => ({
      ...state,
      target: action.payload,
    }),
    clearUploadsTarget: (state) => ({
      ...state,
      target: undefined,
    }),
    clearUploadsError: (state) => ({
      ...state,
      error: undefined,
    }),
    clearUploadsResponse: (state) => ({
      ...state,
      uploadResponse: undefined,
    }),
  },
  extraReducers: (builder) => {
    builder.addCase(uploadFiles.pending, (state) => {
      return {
        ...state,
        error: undefined,
        uploadInProgress: true,
      };
    });
    builder.addCase(
      uploadFiles.fulfilled,
      (state, action: PayloadAction<UploadsSliceState["uploadResponse"]>) => {
        const response = action.payload;
        const hasError = response?.some((x) => !!x.error);
        const error = response?.reduce((acc, cur) => {
          if (!cur.error) {
            return acc;
          }
          const [maybeFileName, ...maybeFileError] = cur.error.split(": ");
          return {
            ...acc,
            [maybeFileName]: maybeFileError.join(": "),
          };
        }, {});
        return {
          ...state,
          uploadInProgress: false,
          uploadResponse: action.payload,
          error: hasError ? error : undefined,
        };
      }
    );
    builder.addCase(
      uploadFiles.rejected,
      (state, action: PayloadAction<UploadsSliceState["error"]>) => {
        return {
          ...state,
          error: action.payload,
          uploadInProgress: false,
        };
      }
    );
  },
})
Example #21
Source File: group.service.ts    From wise-old-man with MIT License 4 votes vote down vote up
/**
 * Set the members of a group.
 *
 * Note: This will replace any existing members.
 * Note: The members array should have this format:
 * [{username: "ABC", role: "member"}]
 */
async function setMembers(group: Group, members: MemberFragment[]): Promise<Member[]> {
  if (!group) {
    throw new BadRequestError(`Invalid group.`);
  }

  // Ignore any duplicate names
  const uniqueNames = uniqBy(members, m => playerService.standardize(m.username)).map(m => m.username);

  // Fetch (or create) player from the unique usernames
  const players = await playerService.findAllOrCreate(uniqueNames);

  // Define membership models for each player
  const memberships = players.map((player, i) => ({
    playerId: player.id,
    groupId: group.id,
    role: members[i].role || 'member'
    // TODO: this can be problematic in the future and should be fixed ASAP:
    // If we supply a list of 4 usernames, 2 of them being repeated,
    // array size of members will be different than uniqueNames and therefore players
    // so by doing members[i] we might be accessing the wrong index.
  }));

  // Fetch all previous (existing) memberships
  const previousMemberships = await Membership.findAll({
    attributes: ['groupId', 'playerId', 'role'],
    where: { groupId: group.id }
  });

  // Find all "to remove" memberships (were previously members, and should now be removed)
  const removeMemberships = previousMemberships.filter(
    pm => !memberships.find(m => m.playerId === pm.playerId)
  );

  // Find all memberships that should be stay members (opposite of removeMemberships)
  const keptMemberships = previousMemberships.filter(pm => memberships.find(m => m.playerId === pm.playerId));

  // Find all new memberships (were not previously members, but should be added)
  const newMemberships = memberships.filter(
    m => !previousMemberships.map(pm => pm.playerId).includes(m.playerId)
  );

  // Delete all memberships for "removed members", if any exist
  if (removeMemberships.length > 0) {
    const toRemoveIds = removeMemberships.map(rm => rm.playerId);
    await Membership.destroy({ where: { groupId: group.id, playerId: toRemoveIds } });
  }

  // Add all the new memberships, if any exist
  if (memberships.length > 0) {
    await Membership.bulkCreate(newMemberships, { ignoreDuplicates: true });
  }

  // Check if any kept member's role should be changed
  if (keptMemberships.length > 0) {
    await Promise.all(
      keptMemberships.map(async k => {
        const membership = memberships.find(m => m.playerId === k.playerId);

        // Role has changed and should be updated
        if (membership && membership.role !== k.role) {
          await k.update({ role: membership.role });
        }
      })
    );
  }

  const allMembers = await group.$get('members');
  const formatted = allMembers.map(a => a.toJSON());

  // Forcibly add the role property, and omit the memberships field
  formatted.forEach((m: any) => {
    m.role = m.memberships.role;
    delete m.memberships;
  });

  return formatted as Member[];
}
Example #22
Source File: Stitcher.ts    From backstage with Apache License 2.0 4 votes vote down vote up
private async stitchOne(entityRef: string): Promise<void> {
    const entityResult = await this.database<DbRefreshStateRow>('refresh_state')
      .where({ entity_ref: entityRef })
      .limit(1)
      .select('entity_id');
    if (!entityResult.length) {
      // Entity does no exist in refresh state table, no stitching required.
      return;
    }

    // Insert stitching ticket that will be compared before inserting the final entity.
    const ticket = uuid();
    await this.database<DbFinalEntitiesRow>('final_entities')
      .insert({
        entity_id: entityResult[0].entity_id,
        hash: '',
        stitch_ticket: ticket,
      })
      .onConflict('entity_id')
      .merge(['stitch_ticket']);

    // Selecting from refresh_state and final_entities should yield exactly
    // one row (except in abnormal cases where the stitch was invoked for
    // something that didn't exist at all, in which case it's zero rows).
    // The join with the temporary incoming_references still gives one row.
    // The only result set "expanding" join is the one with relations, so
    // the output should be at least one row (if zero or one relations were
    // found), or at most the same number of rows as relations.
    const result: Array<{
      entityId: string;
      processedEntity?: string;
      errors: string;
      incomingReferenceCount: string | number;
      previousHash?: string;
      relationType?: string;
      relationTarget?: string;
    }> = await this.database
      .with('incoming_references', function incomingReferences(builder) {
        return builder
          .from('refresh_state_references')
          .where({ target_entity_ref: entityRef })
          .count({ count: '*' });
      })
      .select({
        entityId: 'refresh_state.entity_id',
        processedEntity: 'refresh_state.processed_entity',
        errors: 'refresh_state.errors',
        incomingReferenceCount: 'incoming_references.count',
        previousHash: 'final_entities.hash',
        relationType: 'relations.type',
        relationTarget: 'relations.target_entity_ref',
      })
      .from('refresh_state')
      .where({ 'refresh_state.entity_ref': entityRef })
      .crossJoin(this.database.raw('incoming_references'))
      .leftOuterJoin('final_entities', {
        'final_entities.entity_id': 'refresh_state.entity_id',
      })
      .leftOuterJoin('relations', {
        'relations.source_entity_ref': 'refresh_state.entity_ref',
      })
      .orderBy('relationType', 'asc')
      .orderBy('relationTarget', 'asc');

    // If there were no rows returned, it would mean that there was no
    // matching row even in the refresh_state. This can happen for example
    // if we emit a relation to something that hasn't been ingested yet.
    // It's safe to ignore this stitch attempt in that case.
    if (!result.length) {
      this.logger.error(
        `Unable to stitch ${entityRef}, item does not exist in refresh state table`,
      );
      return;
    }

    const {
      entityId,
      processedEntity,
      errors,
      incomingReferenceCount,
      previousHash,
    } = result[0];

    // If there was no processed entity in place, the target hasn't been
    // through the processing steps yet. It's safe to ignore this stitch
    // attempt in that case, since another stitch will be triggered when
    // that processing has finished.
    if (!processedEntity) {
      this.logger.debug(
        `Unable to stitch ${entityRef}, the entity has not yet been processed`,
      );
      return;
    }

    // Grab the processed entity and stitch all of the relevant data into
    // it
    const entity = JSON.parse(processedEntity) as AlphaEntity;
    const isOrphan = Number(incomingReferenceCount) === 0;
    let statusItems: EntityStatusItem[] = [];

    if (isOrphan) {
      this.logger.debug(`${entityRef} is an orphan`);
      entity.metadata.annotations = {
        ...entity.metadata.annotations,
        ['backstage.io/orphan']: 'true',
      };
    }
    if (errors) {
      const parsedErrors = JSON.parse(errors) as SerializedError[];
      if (Array.isArray(parsedErrors) && parsedErrors.length) {
        statusItems = parsedErrors.map(e => ({
          type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE,
          level: 'error',
          message: `${e.name}: ${e.message}`,
          error: e,
        }));
      }
    }

    // TODO: entityRef is lower case and should be uppercase in the final
    // result
    const uniqueRelationRows = uniqBy(
      result,
      r => `${r.relationType}:${r.relationTarget}`,
    );
    entity.relations = uniqueRelationRows
      .filter(row => row.relationType /* exclude null row, if relevant */)
      .map<EntityRelation>(row => ({
        type: row.relationType!,
        targetRef: row.relationTarget!,
      }));
    if (statusItems.length) {
      entity.status = {
        ...entity.status,
        items: [...(entity.status?.items ?? []), ...statusItems],
      };
    }

    // If the output entity was actually not changed, just abort
    const hash = generateStableHash(entity);
    if (hash === previousHash) {
      this.logger.debug(`Skipped stitching of ${entityRef}, no changes`);
      return;
    }

    entity.metadata.uid = entityId;
    if (!entity.metadata.etag) {
      // If the original data source did not have its own etag handling,
      // use the hash as a good-quality etag
      entity.metadata.etag = hash;
    }

    // This may throw if the entity is invalid, so we call it before
    // the final_entities write, even though we may end up not needing
    // to write the search index.
    const searchEntries = buildEntitySearch(entityId, entity);

    const amountOfRowsChanged = await this.database<DbFinalEntitiesRow>(
      'final_entities',
    )
      .update({
        final_entity: JSON.stringify(entity),
        hash,
      })
      .where('entity_id', entityId)
      .where('stitch_ticket', ticket)
      .onConflict('entity_id')
      .merge(['final_entity', 'hash']);

    if (amountOfRowsChanged === 0) {
      this.logger.debug(
        `Entity ${entityRef} is already processed, skipping write.`,
      );
      return;
    }

    // TODO(freben): Search will probably need a similar safeguard against
    // race conditions like the final_entities ticket handling above.
    // Otherwise, it can be the case that:
    // A writes the entity ->
    // B writes the entity ->
    // B writes search ->
    // A writes search
    await this.database<DbSearchRow>('search')
      .where({ entity_id: entityId })
      .delete();
    await this.database.batchInsert('search', searchEntries, BATCH_SIZE);
  }
Example #23
Source File: create-table-input.ts    From dyngoose with ISC License 4 votes vote down vote up
export function createTableInput(schema: Schema, forCloudFormation = false): DynamoDB.CreateTableInput {
  const params: DynamoDB.CreateTableInput = {
    TableName: schema.name,
    AttributeDefinitions: [
      {
        AttributeName: schema.primaryKey.hash.name,
        AttributeType: schema.primaryKey.hash.type.type,
      },
    ],
    KeySchema: [
      {
        AttributeName: schema.primaryKey.hash.name,
        KeyType: 'HASH',
      },
    ],
  }

  if (schema.options.billingMode === 'PAY_PER_REQUEST') {
    params.BillingMode = 'PAY_PER_REQUEST'
  } else {
    params.ProvisionedThroughput = {
      ReadCapacityUnits: schema.throughput.read,
      WriteCapacityUnits: schema.throughput.write,
    }
  }

  if (schema.primaryKey.range != null) {
    params.AttributeDefinitions.push({
      AttributeName: schema.primaryKey.range.name,
      AttributeType: schema.primaryKey.range.type.type,
    })

    params.KeySchema.push({
      AttributeName: schema.primaryKey.range.name,
      KeyType: 'RANGE',
    })
  }

  if (schema.options.encrypted === true) {
    if (forCloudFormation) {
      (params as any).SSESpecification = {
        SSEEnabled: true,
      }
    } else {
      params.SSESpecification = { Enabled: true }
    }
  }

  if (schema.options.backup === true) {
    if (forCloudFormation) {
      (params as any).PointInTimeRecoverySpecification = {
        PointInTimeRecoveryEnabled: true,
      }
    }
  }

  if (schema.options.stream != null) {
    if (typeof schema.options.stream === 'boolean') {
      params.StreamSpecification = {
        StreamEnabled: true,
        StreamViewType: 'NEW_AND_OLD_IMAGES',
      }
    } else {
      params.StreamSpecification = schema.options.stream
    }

    // CloudFormation template syntax is slightly different than the input for the CreateTable operation
    // so we delete the StreamEnabled because it is not valid within CloudFormation templates
    if (forCloudFormation) {
      delete (params.StreamSpecification as any).StreamEnabled
    }
  }

  if (schema.localSecondaryIndexes.length > 0) {
    params.LocalSecondaryIndexes = schema.localSecondaryIndexes.map((indexMetadata) => {
      const KeySchema: DynamoDB.KeySchema = [
        {
          AttributeName: schema.primaryKey.hash.name,
          KeyType: 'HASH',
        },
        {
          AttributeName: indexMetadata.range.name,
          KeyType: 'RANGE',
        },
      ]

      // make sure this attribute is defined in the AttributeDefinitions
      if (params.AttributeDefinitions.find((ad) => indexMetadata.range.name === ad.AttributeName) == null) {
        params.AttributeDefinitions.push({
          AttributeName: indexMetadata.range.name,
          AttributeType: indexMetadata.range.type.type,
        })
      }

      const index: DynamoDB.LocalSecondaryIndex = {
        IndexName: indexMetadata.name,
        KeySchema,
        Projection: {
          ProjectionType: indexMetadata.projection == null ? 'ALL' : indexMetadata.projection,
        },
        // Projection: indexMetadata.projection,
      }

      if (indexMetadata.nonKeyAttributes != null && indexMetadata.nonKeyAttributes.length > 0) {
        if (indexMetadata.projection !== 'INCLUDE') {
          throw new SchemaError(`Invalid configuration for LocalSecondaryIndex ${schema.name}/${indexMetadata.name}. nonKeyAttributes can only be used with projection INCLUDE.`)
        }

        index.Projection.NonKeyAttributes = indexMetadata.nonKeyAttributes
      }

      return index
    })
  }

  if (schema.globalSecondaryIndexes.length > 0) {
    params.GlobalSecondaryIndexes = schema.globalSecondaryIndexes.map((indexMetadata) => {
      const KeySchema: DynamoDB.KeySchema = [{
        AttributeName: indexMetadata.hash.name,
        KeyType: 'HASH',
      }]

      // make sure this attribute is defined in the AttributeDefinitions
      if (params.AttributeDefinitions.find((ad) => indexMetadata.hash.name === ad.AttributeName) == null) {
        params.AttributeDefinitions.push({
          AttributeName: indexMetadata.hash.name,
          AttributeType: indexMetadata.hash.type.type,
        })
      }

      if (indexMetadata.range != null) {
        // make sure the rangeKey is defined in the AttributeDefinitions
        if (params.AttributeDefinitions.find((ad) => (indexMetadata.range as Attribute<any>).name === ad.AttributeName) == null) {
          params.AttributeDefinitions.push({
            AttributeName: indexMetadata.range.name,
            AttributeType: indexMetadata.range.type.type,
          })
        }

        KeySchema.push({
          AttributeName: indexMetadata.range.name,
          KeyType: 'RANGE',
        })
      }

      // by default, indexes will share the same throughput as the table
      const throughput = indexMetadata.throughput == null ? schema.throughput : indexMetadata.throughput

      const index: DynamoDB.GlobalSecondaryIndex = {
        IndexName: indexMetadata.name,
        KeySchema,
        Projection: {
          ProjectionType: indexMetadata.projection == null ? 'ALL' : indexMetadata.projection,
        },
      }

      if (schema.options.billingMode !== 'PAY_PER_REQUEST') {
        index.ProvisionedThroughput = {
          ReadCapacityUnits: throughput.read,
          WriteCapacityUnits: throughput.write,
        }
      }

      if (indexMetadata.nonKeyAttributes != null && indexMetadata.nonKeyAttributes.length > 0) {
        if (indexMetadata.projection !== 'INCLUDE') {
          throw new SchemaError(`Invalid configuration for GlobalSecondaryIndex ${schema.name}/${indexMetadata.name}. nonKeyAttributes can only be used with projection INCLUDE.`)
        }

        index.Projection.NonKeyAttributes = indexMetadata.nonKeyAttributes
      }

      return index
    })
  }

  if (forCloudFormation && schema.timeToLiveAttribute != null) {
    (params as any).TimeToLiveSpecification = {
      AttributeName: schema.timeToLiveAttribute.name,
      Enabled: true,
    }
  }

  params.AttributeDefinitions = uniqBy(params.AttributeDefinitions, (attr) => attr.AttributeName)

  return params
}
Example #24
Source File: tpl-builtin.ts    From brick-design with MIT License 4 votes vote down vote up
filters: {
  [propName: string]: (input: any, ...args: any[]) => any;
} = {
  html: (input: string) => escapeHtml(input),
  json: (input, tabSize: number | string = 2) =>
    tabSize
      ? JSON.stringify(input, null, parseInt(tabSize as string, 10))
      : JSON.stringify(input),
  toJson: (input) => {
    let ret;
    try {
      ret = JSON.parse(input);
    } catch (e) {
      ret = null;
    }
    return ret;
  },
  toInt: (input) => (typeof input === 'string' ? parseInt(input, 10) : input),
  toFloat: (input) => (typeof input === 'string' ? parseFloat(input) : input),
  raw: (input) => input,
  now: () => new Date(),
  toDate: (input: any, inputFormat = '') => {
    const data = moment(input, inputFormat);
    data.add();
    return data.isValid() ? data.toDate() : undefined;
  },
  dateModify: (
    input: any,
    modifier: 'add' | 'subtract' | 'endOf' | 'startOf' = 'add',
    amount = 0,
    unit = 'days',
  ) => {
    if (!(input instanceof Date)) {
      input = new Date();
    }

    if (modifier === 'endOf' || modifier === 'startOf') {
      return moment(input)
        [modifier === 'endOf' ? 'endOf' : 'startOf'](amount || 'day')
        .toDate();
    }

    return moment(input)
      [modifier === 'add' ? 'add' : 'subtract'](parseInt(amount, 10) || 0, unit)
      .toDate();
  },
  date: (input, format = 'LLL', inputFormat = 'X') =>
    moment(input, inputFormat).format(format),
  number: (input) => {
    const parts = String(input).split('.');
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    return parts.join('.');
  },
  trim: (input) => (typeof input === 'string' ? input.trim() : input),
  percent: (input, decimals = 0) => {
    input = parseFloat(input) || 0;
    decimals = parseInt(decimals, 10) || 0;

    const whole = input * 100;
    const multiplier = Math.pow(10, decimals);

    return (
      (Math.round(whole * multiplier) / multiplier).toFixed(decimals) + '%'
    );
  },
  duration: (input) => (input ? formatDuration(input) : input),
  bytes: (input) => (input ? prettyBytes(parseFloat(input)) : input),
  round: (input, decimals = 2) => {
    if (isNaN(input)) {
      return 0;
    }

    decimals = parseInt(decimals, 10) || 2;

    const multiplier = Math.pow(10, decimals);
    return (Math.round(input * multiplier) / multiplier).toFixed(decimals);
  },
  truncate: (input, length, end) => {
    if (typeof input !== 'string') {
      return input;
    }

    end = end || '...';

    if (length == null) {
      return input;
    }

    length = parseInt(length, 10) || 200;

    return input.substring(0, length) + (input.length > length ? end : '');
  },
  urlEncode: (input) => encodeURIComponent(input),
  urlDecode: (input) => decodeURIComponent(input),
  default: (input, defaultValue) =>
    input ||
    (() => {
      try {
        if (defaultValue === 'undefined') {
          return undefined;
        }

        return JSON.parse(defaultValue);
      } catch (e) {
        return defaultValue;
      }
    })(),
  join: (input, glue) => (input && input.join ? input.join(glue) : input),
  split: (input, delimiter = ',') =>
    typeof input === 'string' ? input.split(delimiter) : input,
  sortBy: (
    input: any,
    key: string,
    method: 'alpha' | 'numerical' = 'alpha',
    order?: 'asc' | 'desc',
  ) =>
    Array.isArray(input) ? input.sort(makeSorter(key, method, order)) : input,
  unique: (input: any, key?: string) =>
    Array.isArray(input) ? (key ? uniqBy(input, key) : uniq(input)) : input,
  topAndOther: (
    input: any,
    len = 10,
    labelField = 'name',
    restLabel = '其他',
  ) => {
    if (Array.isArray(input) && len) {
      const grouped = groupBy(input, (item: any) => {
        const index = input.indexOf(item) + 1;
        return index >= len ? len : index;
      });

      return Object.keys(grouped).map((key, index) => {
        const group = grouped[key];
        const obj = group.reduce((obj, item) => {
          Object.keys(item).forEach((key) => {
            if (!has(obj, key) || key === 'labelField') {
              obj[key] = item[key];
            } else if (
              typeof item[key] === 'number' &&
              typeof obj[key] === 'number'
            ) {
              obj[key] += item[key];
            } else if (
              typeof item[key] === 'string' &&
              /^(?:\-|\.)\d/.test(item[key]) &&
              typeof obj[key] === 'number'
            ) {
              obj[key] += parseFloat(item[key]) || 0;
            } else if (
              typeof item[key] === 'string' &&
              typeof obj[key] === 'string'
            ) {
              obj[key] += `, ${item[key]}`;
            } else {
              obj[key] = item[key];
            }
          });

          return obj;
        }, {});

        if (index === len - 1) {
          obj[labelField] = restLabel || '其他';
        }
        return obj;
      });
    }
    return input;
  },
  first: (input) => input && input[0],
  nth: (input, nth = 0) => input && input[nth],
  last: (input) => input && (input.length ? input[input.length - 1] : null),
  minus: (input, step = 1) => (parseInt(input, 10) || 0) - parseInt(step, 10),
  plus: (input, step = 1) => (parseInt(input, 10) || 0) + parseInt(step, 10),
  count: (input: any) =>
    Array.isArray(input) || typeof input === 'string' ? input.length : 0,
  sum: (input, field) =>
    Array.isArray(input)
      ? input.reduce(
          (sum, item) =>
            sum + (parseFloat(field ? pickValues(field, item) : item) || 0),
          0,
        )
      : input,
  abs: (input: any) => (typeof input === 'number' ? Math.abs(input) : input),
  pick: (input, path = '&') =>
    Array.isArray(input) && !/^\d+$/.test(path)
      ? input.map((item, index) =>
          pickValues(path, createObject({ index }, item)),
        )
      : pickValues(path, input),
  pickIfExist: (input, path = '&') =>
    Array.isArray(input)
      ? input.map((item) => resolveVariable(path, item) || item)
      : resolveVariable(path, input) || input,
  str2date: function (input, inputFormat = 'X', outputFormat = 'X') {
    return input
      ? filterDate(input, this, inputFormat).format(outputFormat)
      : '';
  },
  asArray: (input) => (Array.isArray(input) ? input : input ? [input] : input),
  concat(input, ...args: any[]) {
    return Array.isArray(input)
      ? input.concat(...args.map((arg) => getStrOrVariable(arg, this)))
      : input;
  },
  filter: function (input, keys, expOrDirective, arg1) {
    if (!Array.isArray(input) || !keys || !expOrDirective) {
      return input;
    }

    let directive = expOrDirective;
    let fn: (value: any, key: string, item: any) => boolean = () => true;

    if (directive === 'isTrue') {
      fn = (value) => !!value;
    } else if (directive === 'isFalse') {
      fn = (value) => !value;
    } else if (directive === 'isExists') {
      fn = (value) => typeof value !== 'undefined';
    } else if (directive === 'equals' || directive === 'equal') {
      arg1 = arg1 ? getStrOrVariable(arg1, this) : '';
      fn = (value) => arg1 == value;
    } else if (directive === 'isIn') {
      let list: any = arg1 ? getStrOrVariable(arg1, this) : [];

      list = str2array(list);
      list = Array.isArray(list) ? list : list ? [list] : [];
      fn = (value) => (list.length ? !!~list.indexOf(value) : true);
    } else if (directive === 'notIn') {
      let list: Array<any> = arg1 ? getStrOrVariable(arg1, this) : [];
      list = str2array(list);
      list = Array.isArray(list) ? list : list ? [list] : [];
      fn = (value) => !~list.indexOf(value);
    } else {
      if (directive !== 'match') {
        directive = 'match';
        arg1 = expOrDirective;
      }
      arg1 = arg1 ? getStrOrVariable(arg1, this) : '';

      // 比对的值是空时直接返回。
      if (!arg1) {
        return input;
      }

      const reg = string2regExp(arg1, false);
      fn = (value) => reg.test(String(value));
    }

    keys = keys.split(/\s*,\s*/);
    return input.filter((item: any) =>
      keys.some((key: string) => fn(resolveVariable(key, item), key, item)),
    );
  },
  base64Encode(str) {
    return btoa(
      encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(
        match,
        p1,
      ) {
        return String.fromCharCode(('0x' + p1) as any);
      }),
    );
  },

  base64Decode(str) {
    return decodeURIComponent(
      atob(str)
        .split('')
        .map(function (c) {
          return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        })
        .join(''),
    );
  },

  lowerCase: (input) =>
    input && typeof input === 'string' ? input.toLowerCase() : input,
  upperCase: (input) =>
    input && typeof input === 'string' ? input.toUpperCase() : input,

  isTrue(input, trueValue, falseValue) {
    return getConditionValue(input, !!input, trueValue, falseValue, this);
  },
  isFalse(input, trueValue, falseValue) {
    return getConditionValue(input, !input, trueValue, falseValue, this);
  },
  isMatch(input, matchArg, trueValue, falseValue) {
    matchArg = getStrOrVariable(matchArg, this as any);
    return getConditionValue(
      input,
      matchArg && string2regExp(matchArg, false).test(String(input)),
      trueValue,
      falseValue,
      this,
    );
  },
  notMatch(input, matchArg, trueValue, falseValue) {
    matchArg = getStrOrVariable(matchArg, this as any);
    return getConditionValue(
      input,
      matchArg && !string2regExp(matchArg, false).test(String(input)),
      trueValue,
      falseValue,
      this,
    );
  },
  isEquals(input, equalsValue, trueValue, falseValue) {
    equalsValue = /^\d+$/.test(equalsValue)
      ? parseInt(equalsValue, 10)
      : getStrOrVariable(equalsValue, this as any);
    return getConditionValue(
      input,
      input === equalsValue,
      trueValue,
      falseValue,
      this,
    );
  },
  notEquals(input, equalsValue, trueValue, falseValue) {
    equalsValue = /^\d+$/.test(equalsValue)
      ? parseInt(equalsValue, 10)
      : getStrOrVariable(equalsValue, this as any);
    return getConditionValue(
      input,
      input !== equalsValue,
      trueValue,
      falseValue,
      this,
    );
  },
}
Example #25
Source File: RichHistoryQueriesTab.tsx    From grafana-chinese with Apache License 2.0 4 votes vote down vote up
export function RichHistoryQueriesTab(props: Props) {
  const {
    datasourceFilters,
    onSelectDatasourceFilters,
    queries,
    onChangeSortOrder,
    sortOrder,
    activeDatasourceOnly,
    retentionPeriod,
    exploreId,
    height,
  } = props;

  const [sliderRetentionFilter, setSliderRetentionFilter] = useState<[number, number]>([0, retentionPeriod]);

  const theme = useTheme();
  const styles = getStyles(theme, height);
  const datasourcesRetrievedFromQueryHistory = uniqBy(queries, 'datasourceName').map(d => d.datasourceName);
  const listOfDatasources = createDatasourcesList(datasourcesRetrievedFromQueryHistory);

  const listOfDatasourceFilters = datasourceFilters?.map(d => d.value);
  const filteredQueriesByDatasource = datasourceFilters
    ? queries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName))
    : queries;

  const sortedQueries = sortQueries(filteredQueriesByDatasource, sortOrder);
  const queriesWithinSelectedTimeline = sortedQueries?.filter(
    q =>
      q.ts < createRetentionPeriodBoundary(sliderRetentionFilter[0], true) &&
      q.ts > createRetentionPeriodBoundary(sliderRetentionFilter[1], false)
  );

  /* mappedQueriesToHeadings is an object where query headings (stringified dates/data sources)
   * are keys and arrays with queries that belong to that headings are values.
   */
  let mappedQueriesToHeadings = mapQueriesToHeadings(queriesWithinSelectedTimeline, sortOrder);

  return (
    <div className={styles.container}>
      <div className={styles.containerSlider}>
        <div className={styles.slider}>
          <div className="label-slider">
            Filter history <br />
            between
          </div>
          <div className="label-slider">{mapNumbertoTimeInSlider(sliderRetentionFilter[0])}</div>
          <div className="slider">
            <Slider
              tooltipAlwaysVisible={false}
              min={0}
              max={retentionPeriod}
              value={sliderRetentionFilter}
              orientation="vertical"
              formatTooltipResult={mapNumbertoTimeInSlider}
              reverse={true}
              onAfterChange={setSliderRetentionFilter as () => number[]}
            />
          </div>
          <div className="label-slider">{mapNumbertoTimeInSlider(sliderRetentionFilter[1])}</div>
        </div>
      </div>

      <div className={styles.containerContent}>
        <div className={styles.selectors}>
          {!activeDatasourceOnly && (
            <div aria-label="Filter datasources" className={styles.multiselect}>
              <Select
                isMulti={true}
                options={listOfDatasources}
                value={datasourceFilters}
                placeholder="Filter queries for specific data sources(s)"
                onChange={onSelectDatasourceFilters}
              />
            </div>
          )}
          <div aria-label="Sort queries" className={styles.sort}>
            <Select
              value={sortOrderOptions.filter(order => order.value === sortOrder)}
              options={sortOrderOptions}
              placeholder="Sort queries by"
              onChange={e => onChangeSortOrder(e.value as SortOrder)}
            />
          </div>
        </div>
        {Object.keys(mappedQueriesToHeadings).map(heading => {
          return (
            <div key={heading}>
              <div className={styles.heading}>
                {heading} <span className={styles.queries}>{mappedQueriesToHeadings[heading].length} queries</span>
              </div>
              {mappedQueriesToHeadings[heading].map((q: RichHistoryQuery) => {
                const idx = listOfDatasources.findIndex(d => d.label === q.datasourceName);
                return (
                  <RichHistoryCard
                    query={q}
                    key={q.ts}
                    exploreId={exploreId}
                    dsImg={listOfDatasources[idx].imgUrl}
                    isRemoved={listOfDatasources[idx].isRemoved}
                  />
                );
              })}
            </div>
          );
        })}
        <div className={styles.feedback}>
          Query history is a beta feature. The history is local to your browser and is not shared with others.
          <a href="https://github.com/grafana/grafana/issues/new/choose">Feedback?</a>
        </div>
      </div>
    </div>
  );
}
Example #26
Source File: relation.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
RelationModal = ({ visible, onCancel, versionInfo, mode }: IProps) => {
  const formRef = useRef({}) as MutableRefObject<FormInstance>;
  const [assetDetail, instance] = apiMarketStore.useStore((s) => [s.assetDetail.asset, s.instance]);
  const defaultAppID = mode === 'asset' ? assetDetail.appID : instance.appID || assetDetail.appID;
  const defaultProjectID = mode === 'asset' ? assetDetail.projectID : instance.projectID || assetDetail.projectID;
  const params = routeInfoStore.useStore((s) => s.params);
  const instanceType = get(instance, 'type', 'dice');
  const relationRef = useRef<{ projectList: PROJECT.Detail[]; appList: IApplication[] }>({
    projectList: [],
    appList: [],
  });
  const [state, updater, update] = useUpdate<IState>({
    serviceList: {},
    branchList: [],
    instanceList: [],
    instanceType: 'dice',
    chooseProjectID: undefined,
  });
  const { getAssetDetail, editAsset, editInstance } = apiMarketStore.effects;
  const getProjects = (query: { pageSize: number; pageNo: number; q?: string }) => {
    return getMyProject<Promise<API_MARKET.CommonResList<PROJECT.Detail[]>>>({ ...query }).then((res) => {
      if (res.success) {
        const { projectList } = relationRef.current;
        const list = res.data?.list || [];
        relationRef.current.projectList = uniqBy([...projectList, ...list], 'id');
        return { list, total: res.data.total };
      } else {
        return { list: [], total: 0 };
      }
    });
  };
  const chosenItemConvertProject = (selectItem: { label: string }) => {
    if (isEmpty(selectItem)) {
      return [];
    }
    if (!selectItem.label) {
      if (mode === 'instance' && instance.projectID) {
        return [{ value: instance.projectID, label: instance.projectName }];
      } else {
        return [{ value: assetDetail.projectID, label: assetDetail.projectName }];
      }
    }
    return selectItem;
  };
  const getApplications = React.useCallback(
    (query: { pageSize: number; pageNo: number; q?: string }) => {
      if (!state.chooseProjectID) {
        relationRef.current.appList = [];
        return;
      }
      return getApps<Promise<API_MARKET.CommonResList<IApplication[]>>>({
        ...query,
        projectId: state.chooseProjectID,
      }).then((res) => {
        if (res.success) {
          const list = res.data?.list || [];
          const { appList } = relationRef.current;
          relationRef.current.appList = uniqBy([...appList, ...list], 'id');
          return { list, total: res.data.total };
        } else {
          return { list: [], total: 0 };
        }
      });
    },
    [state.chooseProjectID],
  );
  const chosenItemConvertApp = (selectItem: { label: string }) => {
    if (isEmpty(selectItem)) {
      return [];
    }
    if (!selectItem.label) {
      if (mode === 'instance' && instance.appID) {
        return getAppDetail(instance.appID).then(({ data }) => {
          return [{ value: data.id, label: data.name }];
        });
      } else {
        return [{ value: assetDetail.appID, label: assetDetail.appName }];
      }
    }
    return selectItem;
  };
  const getAppInstances = React.useCallback(
    (appID: number) => {
      if (!appID) {
        update({
          serviceList: {},
          branchList: [],
          instanceList: [],
        });
        return;
      }
      getAppInstance<Promise<{ success: boolean; data: API_MARKET.AppInstanceItem[] }>>({ appID }).then((res) => {
        if (res.success) {
          const serviceList = groupBy(res.data || [], 'serviceName');
          let branchList: API_MARKET.AppInstanceItem[] = [];
          let instanceList: API_MARKET.AppInstanceItem[] = [];
          if (instance.serviceName) {
            branchList = serviceList[instance.serviceName] || [];
          }
          if (instance.runtimeID) {
            instanceList = branchList.filter((item) => item.runtimeID === instance.runtimeID);
          }
          update({
            serviceList,
            branchList,
            instanceList,
          });
        }
      });
    },
    [instance.runtimeID, instance.serviceName, update],
  );
  React.useEffect(() => {
    if (visible) {
      update({
        instanceType,
        chooseProjectID: defaultProjectID,
      });
      if (instanceType === 'dice' && defaultAppID && mode === 'instance') {
        getAppInstances(defaultAppID);
      }
    } else {
      relationRef.current = {
        projectList: [],
        appList: [],
      };
    }
  }, [assetDetail.appID, instanceType, defaultProjectID, mode, visible, update, defaultAppID, getAppInstances]);
  const handleChange = (name: string, value: number | API_MARKET.InstanceType, clearFields: string[]) => {
    const temp = {};
    clearFields.forEach((item) => {
      temp[item] = undefined;
    });
    switch (name) {
      case 'type':
        updater.instanceType(value as API_MARKET.InstanceType);
        temp.type = value;
        temp.url = instanceType !== value ? undefined : instance.url;
        if (value === 'dice') {
          if (defaultAppID) {
            getAppInstances(defaultAppID);
          }
        }
        break;
      case 'projectID':
        relationRef.current.appList = [];
        update({
          serviceList: {},
          branchList: [],
          instanceList: [],
          chooseProjectID: +value,
        });
        break;
      case 'appID':
        if (mode === 'instance') {
          getAppInstances(+value);
        }
        break;
      case 'serviceName':
        update({
          branchList: state.serviceList[value] || [],
          instanceList: [],
        });
        break;
      case 'runtimeID':
        update({
          instanceList: state.branchList.filter((item) => item.runtimeID === +value),
        });
        break;
      default:
        break;
    }
    formRef.current.setFieldsValue(temp);
  };
  const handleOk = async (data: any) => {
    if (mode === 'instance') {
      const instantiationID = instance.id;
      const { minor, swaggerVersion } = versionInfo;
      const { projectID, appID, type, url, serviceName, runtimeID } = data;
      const { workspace } = state.branchList.find((item) => item.runtimeID === +runtimeID) || {};
      const payload = {
        minor,
        swaggerVersion,
        assetID: params.assetID,
        projectID: type === 'dice' ? +projectID : undefined,
        appID: type === 'dice' ? +appID : undefined,
        type,
        url,
        serviceName,
        runtimeID: +runtimeID,
        workspace,
      } as API_MARKET.UpdateInstance;
      if (instantiationID) {
        payload.instantiationID = instantiationID;
      }
      await editInstance(payload);
      onCancel();
    } else if (mode === 'asset') {
      const { appList, projectList } = relationRef.current;
      const asset = pick(assetDetail, ['assetName', 'desc', 'logo', 'assetID']);
      let projectName: string | undefined;
      let appName: string | undefined;
      if (data.projectID) {
        projectName = get(
          projectList.find((item) => item.id === +data.projectID),
          'name',
        );
      }
      if (data.appID) {
        appName = get(
          appList.find((item) => item.id === +data.appID),
          'name',
        );
      }
      await editAsset({
        ...asset,
        projectID: +data.projectID,
        appID: +data.appID,
        assetID: params.assetID,
        projectName,
        appName,
      });
      onCancel();
      getAssetDetail({ assetID: params.assetID }, true);
    }
  };
  const fieldsList: IFormItem[] = [
    ...insertWhen(mode === 'instance', [
      {
        label: i18n.t('instance source'),
        name: 'type',
        type: 'radioGroup',
        required: true,
        initialValue: instanceType,
        itemProps: {
          onChange: (e: RadioChangeEvent) => {
            handleChange('type', e.target.value, []);
          },
        },
        options: [
          {
            name: i18n.t('internal'),
            value: 'dice',
          },
          {
            name: i18n.t('external'),
            value: 'external',
          },
        ],
      },
    ]),
    ...insertWhen(mode === 'asset' || (mode === 'instance' && state.instanceType === 'dice'), [
      {
        label: i18n.t('Project name'),
        name: 'projectID',
        required: false,
        initialValue: defaultProjectID,
        getComp: () => {
          return (
            <LoadMoreSelector
              chosenItemConvert={chosenItemConvertProject}
              getData={getProjects}
              dataFormatter={({ list, total }: { list: any[]; total: number }) => ({
                total,
                list: map(list, ({ id, name }) => {
                  return {
                    label: name,
                    value: id,
                  };
                }),
              })}
            />
          );
        },
        itemProps: {
          allowClear: true,
          placeholder: i18n.t('Please Select'),
          onChange: (v: number) => {
            handleChange('projectID', v, ['appID', 'serviceName', 'runtimeID', 'url']);
          },
        },
      },
      {
        label: i18n.t('dop:app name'),
        name: 'appID',
        initialValue: defaultAppID,
        required: false,
        getComp: () => {
          return (
            <LoadMoreSelector
              chosenItemConvert={chosenItemConvertApp}
              extraQuery={{ projectId: state.chooseProjectID }}
              getData={getApplications}
              dataFormatter={({ list, total }: { list: any[]; total: number }) => ({
                total,
                list: map(list, ({ id, name }) => {
                  return {
                    label: name,
                    value: id,
                  };
                }),
              })}
            />
          );
        },
        itemProps: {
          allowClear: true,
          placeholder: i18n.t('Please Select'),
          onChange: (v) => {
            handleChange('appID', v, ['serviceName', 'runtimeID', 'url']);
          },
        },
      },
    ]),
    ...insertWhen(mode === 'instance', [
      ...(state.instanceType === 'dice'
        ? [
            {
              label: i18n.t('Service name'),
              required: false,
              type: 'select',
              initialValue: get(instance, 'serviceName'),
              options: map(state.serviceList, (_v, k) => ({ name: k, value: k })),
              name: 'serviceName',
              itemProps: {
                allowClear: true,
                placeholder: i18n.t('Please Select'),
                onChange: (v) => {
                  handleChange('serviceName', v, ['runtimeID', 'url']);
                },
              },
            },
            {
              label: i18n.t('msp:deployment branch'),
              required: false,
              type: 'select',
              name: 'runtimeID',
              initialValue: get(instance, 'runtimeID'),
              options: map(state.branchList, ({ runtimeID, runtimeName }) => ({ name: runtimeName, value: runtimeID })),
              itemProps: {
                allowClear: true,
                placeholder: i18n.t('Please Select'),
                onChange: (v: number) => {
                  handleChange('runtimeID', v, ['url']);
                },
              },
            },
            {
              label: i18n.t('instance'),
              type: 'select',
              name: 'url',
              initialValue: instanceType === 'dice' ? get(instance, 'url') : undefined,
              required: false,
              itemProps: {
                allowClear: true,
              },
              getComp: () => (
                <Select placeholder={i18n.t('Please Select')}>
                  {state.instanceList.map(({ serviceAddr }) => {
                    return (serviceAddr || []).map((url) => (
                      <Select.Option key={url} value={url}>
                        {url}
                      </Select.Option>
                    ));
                  })}
                </Select>
              ),
            },
          ]
        : [
            {
              label: i18n.t('instance'),
              name: 'url',
              required: false,
              initialValue: instanceType === 'dice' ? undefined : get(instance, 'url'),
              rules: [{ pattern: regRules.url, message: i18n.t('Please enter address started with http') }],
            },
          ]),
    ]),
  ];
  return (
    <FormModal
      title={mode === 'asset' ? i18n.t('connection relation') : i18n.t('related instance')}
      fieldsList={fieldsList}
      ref={formRef}
      visible={visible}
      onCancel={onCancel}
      onOk={handleOk}
      modalProps={{
        destroyOnClose: true,
      }}
    />
  );
}
Example #27
Source File: atnReducer.ts    From alchemist with MIT License 4 votes vote down vote up
atnReducer = (state: IInspectorState, action: TAllActions): IInspectorState => {
	switch (action.type) {
		case "[ATN] CLEAR_ALL": {
			state = produce(state, draft => {
				draft.atnConverter.data = [];
			});
			break;
		}
		case "[ATN] SET_DATA": {
			state = produce(state, draft => {
				draft.atnConverter.data.push(...action.payload);
			});
			break;
		}
		
		case "[ATN] EXPAND_ACTION": {
			state = produce(state, draft => {
				const { expand, recursive, uuid } = action.payload;
				//const treePart = getTreePartUniversal(state, uuid);

			
				const indexOf = draft.atnConverter.expandedItems.findIndex(item => {
					if (item.length !== action.payload.uuid.length) {
						return false;
					}
					const res = (item[0] === action.payload.uuid[0] && item[1] === action.payload.uuid[1]);
					return res;
				});

				if (expand) {
					if (indexOf === -1) {
						draft.atnConverter.expandedItems.push(uuid);
						if (recursive && uuid.length === 1) {
							const found = getSetByUUID(state, uuid[0]);
							if (found) {
								const rest: TExpandedItem[] = found.actionItems.map(item => [item.__uuidParentSet__, item.__uuid__]);
								draft.atnConverter.expandedItems.push(...rest);								
							}
						}
					}
				} else {
					if (indexOf !== -1) {
						draft.atnConverter.expandedItems.splice(indexOf, 1);
						if (recursive && uuid.length === 1) {
							const found = getSetByUUID(state, uuid[0]);
							if (found) {
								const rest: string[] = found.actionItems.map(item => [item.__uuidParentSet__, item.__uuid__].join("|"));								
								rest.forEach(itm => {
									const index = draft.atnConverter.expandedItems.findIndex((a) => {
										return a.join("|") === itm;
									});
									draft.atnConverter.expandedItems.splice(index, 1);
								});
							}
						}
					}
				}
			});
			break;
		}
		
		case "[ATN] SET_DONT_SEND_DISABLED": {
			state = produce(state, draft => {
				draft.atnConverter.dontSendDisabled = action.payload;
			});
			break;
		}
		
		case "[ATN] SELECT_ACTION": {
			state = produce(state, draft => {
				const { operation, uuid } = action.payload;
				const { data } = state.atnConverter;
				if (operation === "none") {
					draft.atnConverter.selectedItems = [];
				} else if (operation === "replace" && uuid?.length) {
					draft.atnConverter.selectedItems = addChilds([uuid]);
				} else if (operation === "subtract" && uuid?.length) {
					const all = addChilds([uuid]).map(item => item.join("|"));

					all.forEach((itemFromAll) => {
						const foundIndex = draft.atnConverter.selectedItems.findIndex(itemFromDraft => (itemFromDraft.join("|") === itemFromAll));
						if (foundIndex > -1) {
							draft.atnConverter.selectedItems.splice(foundIndex, 1);
						}
					});
				} else if (operation === "add" && uuid?.length) {
					draft.atnConverter.selectedItems = [...state.atnConverter.selectedItems, ...addChilds([uuid])];
				}

				function addChilds(items: TSelectedItem[]): TSelectedItem[] {
					//let sorted = items.sort((a, b) => a.length - b.length);

					const sets = items.filter(i => i.length === 1);
					const actions = items.filter(i => i.length === 2);
					const commands = items.filter(i => i.length === 3);

					sets.forEach(s => {
						data.forEach(ss => {
							if (ss.__uuid__ === s[0]) {
								ss.actionItems.forEach(si => {
									actions.push([ss.__uuid__, si.__uuid__]);
									si.commands.forEach(sc => {
										commands.push([ss.__uuid__, si.__uuid__, sc.__uuid__]);
									});
								});
							}
						});
					});

					actions.forEach(a => {
						data.forEach(ss => ss.actionItems.forEach(si => {
							if (si.__uuid__ === a[1]) {
								si.commands.forEach(sc => {
									commands.push([ss.__uuid__, si.__uuid__, sc.__uuid__]);
								});
							}
						}));
					});

					const all = [...sets, ...actions, ...commands];
					const allUnique = uniqBy(all, (i) => i.join("|"));

					return allUnique;
				}

				/*
	
				const found = draft.descriptors.find(d => d.id === uuid);
				if (found && operation !== "none") {					
					if (operation === "add" || operation === "replace") {
						found.selected = true;
					} else if (operation === "subtract") {
						found.selected = false;
					} else if (operation === "addContinuous" || operation === "subtractContinuous") {
						const view = getDescriptorsListView({inspector:state});
						const lastSelectedItemIndex = view.map(item => item.id).indexOf(state.settings.lastSelectedItem ?? "n/a");
						const thisItemIndex = view.map(item => item.id).indexOf(uuid as string);
						if (lastSelectedItemIndex !== -1 && thisItemIndex !== -1) {
							const ids:string[] = [];
							for (let i = Math.min(lastSelectedItemIndex, thisItemIndex), end = Math.max(lastSelectedItemIndex, thisItemIndex); i <= end; i++){
								ids.push(view[i].id);
							}
							ids.forEach(id => {
								const f = draft.descriptors.find(item => item.id === id);
								if (f) { f.selected = operation === "addContinuous";}
							});
						}
					}
				}
				*/
				//
				draft.atnConverter.lastSelected = uuid || getInitialState().atnConverter.lastSelected;
			});
			break;
		}
			/*
			case "[ATN] PASS_SELECTED": {
				state = produce(state, draft => {
					const commands = selectedCommands({ inspector: state });
					
		
				});
				break;
			}
				*/

			return state;
	}

	return state;
}
Example #28
Source File: GetStatistics.ts    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export async function GetStatistics(
  categoryGroups: CategoryGroup[] = [],
  stories: Story[] = []
): Promise<any> {
  const storyList = getAllStoryListV2(categoryGroups, stories);
  const allCommonBricks = filter(storyList, (v) => v.type === "brick");
  const allCommonTemplates = reject(storyList, (v) => v.type === "brick");

  const commonBricksName = map(allCommonBricks, "id");
  const commonTemplatesName = map(allCommonTemplates, "id");
  const bootstrap = await AuthSdk.bootstrap();
  // 不统计iframe嵌套老站点的微应用
  const microAppsWithoutLegacy = reject(bootstrap.storyboards, [
    "app.legacy",
    "iframe",
  ]);
  const microAppsBricks = microAppsWithoutLegacy.map((microApp) => {
    const allBricksName = scanBricksInStoryboard(microApp, false);
    const allTemplatesName = scanTemplatesInStoryboard(microApp, false);
    const countAndSortAllBricks = reverse(
      sortBy(
        map(
          countBy(allBricksName, (item) => {
            return item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
              type: "brick",
              isCommon: ifCommon(k, commonBricksName),
            };
          }
        ),
        "count"
      )
    );
    const countAndSortAllTemplates = reverse(
      sortBy(
        map(
          countBy(allTemplatesName, (item) => {
            return item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
              type: "template",
              isCommon: ifCommon(k, commonTemplatesName),
            };
          }
        ),
        "count"
      )
    );
    microApp.countBricksAndTemplates = reverse(
      sortBy([...countAndSortAllBricks, ...countAndSortAllTemplates], "count")
    );
    microApp.countCommonBricksAndTemplates = filter(
      [...countAndSortAllBricks, ...countAndSortAllTemplates],
      "isCommon"
    ).length;

    const statisticsAll = [
      ...map(allBricksName, (v) => {
        return {
          type: "brick",
          id: v,
          isCommon: ifCommon(v, commonBricksName),
          app: microApp.app,
        };
      }),
      ...map(allTemplatesName, (v) => ({
        type: "template",
        id: v,
        isCommon: ifCommon(v, commonTemplatesName),
        app: microApp.app,
      })),
    ];
    microApp.statisticsAll = statisticsAll;
    microApp.commonUsedRate = getPercentage(
      filter(statisticsAll, (item) => item.isCommon).length /
        statisticsAll.length
    );
    return microApp;
  });
  const microAppsStatisticsMap = keyBy(microAppsBricks, "app.id");
  const microAppsSubMenu = {
    title: "微应用列表",
    menuItems: map(microAppsBricks, (item) => {
      return {
        text: item.app.name,
        to: `/developers/statistics/micro-app-statistics/${item.app.id}`,
      };
    }),
  };
  const microAppStatisticsRedirectTo = `${microAppsSubMenu.menuItems[0].to}`;
  const allMicroAppsBricksAndTemplate = flatten(
    map(microAppsBricks, "statisticsAll")
  );
  const allMicroAppsBricks = map(
    filter(allMicroAppsBricksAndTemplate, (item) => item.type === "brick"),
    "id"
  );
  const allMicroAppsTemplates = map(
    filter(allMicroAppsBricksAndTemplate, (item) => item.type === "template"),
    "id"
  );

  // 统计所有构件
  const allBricksAndTemplateGroupBy = groupBy(
    allMicroAppsBricksAndTemplate,
    (item) => item.type + "," + item.id
  );
  const countAllBricksAndTemplate = map(
    uniqBy(allMicroAppsBricksAndTemplate, (item) => item.type + "," + item.id),
    (v) => {
      return {
        id: v.id,
        type: v.type,
        isCommon: v.isCommon,
        count: allBricksAndTemplateGroupBy[v.type + "," + v.id].length,
        appList: map(
          uniqBy(
            allBricksAndTemplateGroupBy[v.type + "," + v.id],
            (v) => v.app.id
          ),
          "app"
        ),
      };
    }
  );

  // 排名前二十的构件
  let countCommonBricksUsed: any[] = reverse(
    sortBy(
      filter(
        map(
          countBy(allMicroAppsBricks, (item) => {
            return includes(commonBricksName, item) && item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
            };
          }
        ),
        (item) => item.id !== "false" && item.id !== "div"
      ),
      "count"
    )
  ).slice(0, 20);
  countCommonBricksUsed = map(countCommonBricksUsed, (item) => {
    const found = find(allCommonBricks, ["id", item.id]);
    if (found) {
      item.author = found.subTitle;
      item.type = found.type;
      item.url = "/developers/brick-book/" + item.type + "/" + item.id;
    }
    return item;
  });
  // 排名前二十的模板
  let countCommonTemplatesUsed: any[] = reverse(
    sortBy(
      filter(
        map(
          countBy(allMicroAppsTemplates, (item) => {
            return includes(commonTemplatesName, item) && item;
          }),
          (v, k) => {
            return {
              id: k,
              count: v,
            };
          }
        ),
        (item) => item.id !== "false"
      ),
      "count"
    )
  ).slice(0, 20);
  countCommonTemplatesUsed = map(countCommonTemplatesUsed, (item) => {
    const found = find(allCommonTemplates, ["id", item.id]);
    item.author = found.subTitle;
    item.type = found.type;
    item.url = "/developers/brick-book/" + item.type + "/" + item.id;
    return item;
  });
  const topBricksAndTemplates = reverse(
    sortBy([...countCommonBricksUsed, ...countCommonTemplatesUsed], "count")
  ).slice(0, 20);
  const result = {
    microAppsCount: bootstrap.storyboards.length,
    microAppsWithoutLegacyCount: microAppsWithoutLegacy.length,
    iframeMicroApps:
      bootstrap.storyboards.length - microAppsWithoutLegacy.length,
    allBricksAndTemplatesCount:
      uniq(allMicroAppsBricks).length + uniq(allMicroAppsTemplates).length,
    commonBricksAndTemplatesCount:
      commonBricksName.length + allCommonTemplates.length,
    commonBricksAndTemplatesUsedRate: getPercentage(
      (filter(allMicroAppsBricks, (item) => {
        return includes(commonBricksName, item);
      }).length +
        filter(allMicroAppsTemplates, (item) => {
          return includes(commonTemplatesName, item);
        }).length) /
        (allMicroAppsBricks.length + allMicroAppsTemplates.length)
    ),
    microAppsPieChart: {
      title: "微应用总数:" + bootstrap.storyboards.length,
      data: {
        legendList: ["全新微应用", "iframe嵌套微应用"],
        seriesData: [
          {
            name: "全新微应用",
            value: microAppsWithoutLegacy.length,
          },
          {
            name: "iframe嵌套微应用",
            value: bootstrap.storyboards.length - microAppsWithoutLegacy.length,
          },
        ],
      },
    },
    microAppsSubMenu,
    microAppStatisticsRedirectTo,
    microAppsStatisticsMap,
    topBricksAndTemplates,
    countAllBricksAndTemplate,
    packageNames: uniq(
      compact(
        [...allMicroAppsBricks, ...allMicroAppsTemplates].map(
          (v) => v.split(".")?.[0]
        )
      )
    ),
  };
  return result;
}
Example #29
Source File: UserOrUserGroupSelect.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function LegacyUserSelectFormItem(
  props: UserSelectFormItemProps,
  ref: React.Ref<HTMLDivElement>
): React.ReactElement {
  const selectRef = useRef();
  const [selectedValue, setSelectedValue] = useState([]);
  const staticValue = useRef([]);
  const userShowKey: string[] = getInstanceNameKeys(props.objectMap["USER"]);
  const userGroupShowKey: string[] = getInstanceNameKeys(
    props.objectMap["USER_GROUP"]
  );

  const { t } = useTranslation(NS_FORMS);

  const getLabel = (
    objectId: "USER" | "USER_GROUP",
    instanceData: any
  ): string => {
    const showKey = objectId === "USER" ? userShowKey : userGroupShowKey;
    if (Array.isArray(showKey)) {
      const showName = showKey
        .map((key, index) => {
          if (index === 0) {
            return instanceData[key];
          } else {
            return instanceData[key] ? "(" + instanceData[key] + ")" : "";
          }
        })
        .join("");
      return showName;
    } else {
      return instanceData[showKey];
    }
  };

  const getStaticLabel = (label: string) => (
    <div style={{ color: "var(--bg-color-button-link)" }}>{label}</div>
  );

  const isDifferent = () => {
    const userOfValues = props.value?.selectedUser || [];
    const userGroupOfValues = props.value?.selectedUserGroup || [];
    const userOfSelectedValue = map(
      filter(selectedValue, (item) => !item.key.startsWith(":")),
      "key"
    );

    const userGroupOfSelectedValue = map(
      filter(selectedValue, (item) => item.key.startsWith(":")),
      "key"
    );

    return (
      !isEqual([...userOfValues].sort(), [...userOfSelectedValue].sort()) ||
      !isEqual(
        [...userGroupOfValues].sort(),
        [...userGroupOfSelectedValue].sort()
      )
    );
  };

  const initializeStaticList = () => {
    return groupBy(props.staticList, (v) =>
      startsWith(v, ":") ? "userGroup" : "user"
    );
  };

  // 后台搜索中
  const [fetching, setFetching] = useState(false);

  const [searchValue, setSearchValue] = useState();
  const [userList, setUserList] = useState([]);
  const [userGroupList, setUserGroupList] = useState([]);

  const [modalVisible, setModalVisible] = useState(false);
  const [modalObjectId, setModalObjectId] = useState(
    props.optionsMode === "group" ? "USER_GROUP" : "USER"
  );

  const triggerChange = (changedValue: any) => {
    props.onChange?.(
      isEmpty(changedValue.selectedUser) &&
        isEmpty(changedValue.selectedUserGroup)
        ? null
        : changedValue
    );
  };

  useEffect(() => {
    const initializeSelectedValue = async () => {
      if (props.value) {
        let selectedUser: any[] = [];
        let selectedUserGroup: any[] = [];
        const staticKeys = initializeStaticList();
        const user = compact(
          uniq([].concat(staticKeys.user).concat(props.value.selectedUser))
        );

        const userGroup = compact(
          uniq(
            []
              .concat(staticKeys.userGroup)
              .concat(props.value.selectedUserGroup)
          )
        );

        if (
          (staticKeys.user &&
            some(
              staticKeys.user,
              (v) => !props.value?.selectedUser?.includes(v)
            )) ||
          (staticKeys.userGroup &&
            some(
              staticKeys.userGroup,
              (v) => !props.value?.selectedUserGroup?.includes(v)
            ))
        ) {
          triggerChange({
            selectedUser: user,
            selectedUserGroup: userGroup,
          });
        }
        const staticValueToSet = [];
        if (user.length && props.optionsMode !== "group") {
          selectedUser = (
            await InstanceApi_postSearch("USER", {
              query: {
                name: {
                  $in: user,
                },
              },

              page: 1,
              page_size: user.length,
              fields: {
                ...zipObject(
                  userShowKey,
                  map(userShowKey, (v) => true)
                ),

                name: true,
              },
            })
          ).list;
        }
        if (userGroup.length && props.optionsMode !== "user") {
          selectedUserGroup = (
            await InstanceApi_postSearch("USER_GROUP", {
              query: {
                instanceId: {
                  // 默认带为":"+instanceId,这里查询的时候去掉前面的冒号
                  $in: map(userGroup, (v) => v.slice(1)),
                },
              },

              page: 1,
              page_size: userGroup.length,
              fields: {
                ...zipObject(
                  userGroupShowKey,
                  map(userGroupShowKey, (v) => true)
                ),

                name: true,
              },
            })
          ).list;
        }
        let labelValue = [
          ...map(selectedUser, (v) => {
            const labelText = getLabel("USER", v);
            const result = {
              key: v.name,
              label: props.staticList?.includes(v.name)
                ? getStaticLabel(labelText)
                : labelText,
            };

            if (props.staticList?.includes(v.name)) {
              staticValueToSet.push(result);
            }
            return result;
          }),
          ...map(selectedUserGroup, (v) => {
            const labelText = getLabel("USER_GROUP", v);
            const result = {
              key: ":" + v.instanceId,
              label: props.staticList?.includes(":" + v.instanceId)
                ? getStaticLabel(labelText)
                : labelText,
            };

            if (props.staticList?.includes(":" + v.instanceId)) {
              staticValueToSet.push(result);
            }
            return result;
          }),
        ];

        labelValue = [
          ...staticValueToSet,
          ...filter(labelValue, (v) => !props.staticList?.includes(v.key)),
        ];

        setSelectedValue(labelValue);
        staticValue.current = staticValueToSet;
      }
    };
    if (isDifferent()) {
      initializeSelectedValue();
    }
  }, [props.value]);

  const fetchInstanceList = async (
    objectId: "USER" | "USER_GROUP",
    keyword: string
  ) => {
    const showKey = objectId === "USER" ? userShowKey : userGroupShowKey;
    const showKeyQuery = {
      $or: map(uniq([...showKey, "name"]), (v) => ({
        [v]: { $like: `%${keyword}%` },
      })),
      ...(props.hideInvalidUser
        ? {
            state: "valid",
          }
        : {}),
    };
    return (
      await InstanceApi_postSearch(objectId, {
        page: 1,
        page_size: 20,
        fields: {
          ...zipObject(
            showKey,
            map(showKey, (v) => true)
          ),

          name: true,
        },
        query:
          props.userQuery && objectId === "USER"
            ? {
                ...props.userQuery,
                ...showKeyQuery,
              }
            : props.userGroupQuery && objectId === "USER_GROUP"
            ? {
                ...props.userGroupQuery,
                ...showKeyQuery,
              }
            : props.query || showKeyQuery
            ? {
                ...props.query,
                ...showKeyQuery,
              }
            : showKeyQuery,
      })
    ).list;
  };

  const searchUser = async (value: string) => {
    setUserList(await fetchInstanceList("USER", value));
  };

  // 用户组在instanceId前面加上:
  const searchUserGroup = async (value: string) => {
    const result = await fetchInstanceList("USER_GROUP", value);
    setUserGroupList(
      result.map((v) => {
        v.instanceId = ":" + v.instanceId;
        return v;
      })
    );
  };

  const searchUserOrUserGroupInstances = async (value) => {
    setSearchValue(value);
    setFetching(true);
    await Promise.all([
      ...(props.optionsMode !== "group" ? [searchUser(value)] : []),
      ...(props.optionsMode !== "user" ? [searchUserGroup(value)] : []),
    ]);

    setFetching(false);
  };

  const handleSelectChange = (originValue) => {
    const value = filter(originValue, (item) => {
      return !find(props.staticList, (v) => v === item.key);
    });
    value.unshift(...staticValue.current);
    setSelectedValue(value);
    const resultValue = {
      selectedUser: map(
        reject(value, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),

      selectedUserGroup: map(
        filter(value, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),
    };

    triggerChange(resultValue);
    if (searchValue !== "") {
      searchUserOrUserGroupInstances("");
    }
  };

  const handleFocus = () => {
    if (isNil(searchValue) || searchValue !== "") {
      searchUserOrUserGroupInstances("");
    }
  };

  const openModal = () => {
    setModalVisible(true);
  };

  const closeModal = () => {
    setModalVisible(false);
  };

  const handleInstancesSelected = (
    value: Record<string, any>[],
    objectId: string
  ): void => {
    let labelValue: any[] = [];
    if (objectId === "USER") {
      labelValue = [
        ...map(value, (v) => {
          const labelText = getLabel("USER", v);
          return {
            key: v.name,
            label: props.staticList?.includes(v.name)
              ? getStaticLabel(labelText)
              : labelText,
          };
        }),
      ];
    } else {
      labelValue = [
        ...map(value, (v) => {
          const labelText = getLabel("USER_GROUP", v);
          return {
            key: ":" + v.instanceId,
            label: props.staticList?.includes(":" + v.instanceId)
              ? getStaticLabel(labelText)
              : labelText,
          };
        }),
      ];
    }
    const resultSelectedValue = uniqBy(
      [...selectedValue, ...labelValue],
      "key"
    );

    setSelectedValue(resultSelectedValue);
    const resultValue = {
      selectedUser: map(
        reject(resultSelectedValue, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),

      selectedUserGroup: map(
        filter(resultSelectedValue, (v) => {
          return startsWith(v.key, ":");
        }),
        "key"
      ),
    };

    triggerChange(resultValue);
  };

  const handleModalSelected = async (selectedKeys: string[]) => {
    if (selectedKeys?.length) {
      const instances = (
        await InstanceApi_postSearch(modalObjectId, {
          query: { instanceId: { $in: selectedKeys } },
          fields: { "*": true },
          page_size: selectedKeys.length,
        })
      ).list;
      handleInstancesSelected(instances, modalObjectId);
    }
    setModalVisible(false);
  };

  const toggleObjectId = () => {
    setModalObjectId(modalObjectId === "USER" ? "USER_GROUP" : "USER");
  };

  const title = (
    <div>
      {t(K.FILTER_FROM_CMDB, {
        type: modalObjectId === "USER" ? t(K.USERS) : t(K.USER_GROUPS),
      })}{" "}
      {props.optionsMode === "all" && (
        <Button type="link" onClick={toggleObjectId}>
          {t(K.SWITCH, {
            type: modalObjectId === "USER" ? t(K.USER_GROUPS) : t(K.USERS),
          })}{" "}
        </Button>
      )}
    </div>
  );

  // 快速选择我
  const addMeQuickly = async () => {
    const myUserName = getAuth().username;
    if (find(selectedValue, (v) => v.key === myUserName)) {
      // 如果已选择项中包含我,则不重新发起请求
      return;
    }
    const myUser = (
      await InstanceApi_postSearch("USER", {
        query: {
          name: {
            $eq: myUserName,
          },
        },

        page: 1,
        page_size: 1,
        fields: {
          ...zipObject(
            userShowKey,
            map(userShowKey, (v) => true)
          ),

          name: true,
        },
      })
    ).list;
    handleInstancesSelected(myUser, "USER");
  };

  const getRight = () => {
    const btnWidth =
      !props.hideAddMeQuickly && props.optionsMode !== "group" ? -34 : 0;
    const lineWidth =
      !props.hideAddMeQuickly &&
      props.optionsMode !== "group" &&
      !props.hideSelectByCMDB
        ? -1
        : 0;
    const iconWidth = props.hideSelectByCMDB ? 0 : -32;
    return btnWidth + lineWidth + iconWidth;
  };

  return (
    <div
      ref={ref}
      data-testid="wrapper"
      className={styles.UserOrUserGroupSelectContainer}
    >
      <Select
        className={styles.customSelect}
        ref={selectRef}
        allowClear={true}
        mode="multiple"
        labelInValue
        placeholder={props.placeholder}
        filterOption={false}
        value={selectedValue}
        onChange={handleSelectChange}
        onSearch={debounce((value) => {
          searchUserOrUserGroupInstances(value as string);
        }, 500)}
        onFocus={handleFocus}
        style={{ width: "100%" }}
        loading={fetching}
      >
        {props.optionsMode !== "group" && (
          <Select.OptGroup label={t(K.USERS_RESULT_LABEL)}>
            {userList.length > 0 ? (
              userList.map((d) => (
                <Select.Option value={d.name} key={d.name}>
                  {props.staticList?.includes(d.name)
                    ? getStaticLabel(getLabel("USER", d))
                    : getLabel("USER", d)}
                </Select.Option>
              ))
            ) : (
              <Select.Option value="empty-user" key="empty-user" disabled>
                {t(K.NO_DATA)}
              </Select.Option>
            )}
          </Select.OptGroup>
        )}

        {props.optionsMode !== "user" && (
          <Select.OptGroup label={t(K.USER_GROUPS_RESULT_LABEL)}>
            {userGroupList.length > 0 ? (
              userGroupList.map((d) => (
                <Select.Option value={d.instanceId} key={d.instanceId}>
                  {props.staticList?.includes(d.instanceId)
                    ? getStaticLabel(getLabel("USER_GROUP", d))
                    : getLabel("USER_GROUP", d)}
                </Select.Option>
              ))
            ) : (
              <Select.Option
                value="empty-user-group"
                key="empty-user-group"
                disabled
              >
                {t(K.NO_DATA)}
              </Select.Option>
            )}
          </Select.OptGroup>
        )}
      </Select>
      <div className={styles.extra} style={{ right: getRight() }}>
        {!props.hideAddMeQuickly && props.optionsMode !== "group" && (
          <Button
            type="link"
            onClick={addMeQuickly}
            style={{ fontSize: "16px" }}
          >
            <GeneralIcon icon={{ lib: "easyops", icon: "quick-add-me" }} />
          </Button>
        )}

        {!props.hideAddMeQuickly &&
          props.optionsMode !== "group" &&
          !props.hideSelectByCMDB && <Divider type="vertical" />}
        {!props.hideSelectByCMDB && (
          <Button
            type="link"
            icon={<SearchOutlined />}
            onClick={openModal}
          ></Button>
        )}
      </div>
      <InstanceListModal
        objectMap={props.objectMap}
        objectId={modalObjectId}
        visible={modalVisible}
        title={title}
        onSelected={handleModalSelected}
        onCancel={closeModal}
        showSizeChanger={true}
        {...(modalObjectId === "USER" && props.hideInvalidUser
          ? {
              presetConfigs: {
                query: {
                  state: "valid",
                },
              },
            }
          : {})}
      />
    </div>
  );
}