lodash#keyBy TypeScript Examples

The following examples show how to use lodash#keyBy. 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: group.service.ts    From wise-old-man with MIT License 6 votes vote down vote up
/**
 * Gets the stats for every member of a group (latest snapshot)
 */
async function getMembersStats(groupId: number): Promise<Snapshot[]> {
  // Fetch all memberships for the group
  const memberships = await Membership.findAll({
    attributes: ['playerId'],
    where: { groupId }
  });

  if (!memberships || memberships.length === 0) {
    return [];
  }

  const memberIds = memberships.map(m => m.playerId);

  const query = `
    SELECT s.*
    FROM (SELECT q."playerId", MAX(q."createdAt") AS max_date
          FROM public.snapshots q
          WHERE q."playerId" IN (${memberIds.join(',')})
          GROUP BY q."playerId"
          ) r
    JOIN public.snapshots s
      ON s."playerId" = r."playerId" AND s."createdAt" = r.max_date
  `;

  // Execute the query above, which returns the latest snapshot for each member
  const latestSnapshots = await sequelize.query(query, { type: QueryTypes.SELECT });

  // Formats the snapshots to a playerId:snapshot map, for easier lookup
  const snapshotMap = mapValues(keyBy(latestSnapshots, 'playerId'));

  return memberships
    .filter(({ playerId }) => playerId in snapshotMap)
    .map(({ playerId }) => Snapshot.build({ ...snapshotMap[playerId] }));
}
Example #2
Source File: delta.service.ts    From wise-old-man with MIT License 6 votes vote down vote up
/**
 * Gets the all the player deltas (gains), for every period.
 */
async function getPlayerDeltas(playerId: number) {
  const latest = await snapshotService.findLatest(playerId);
  const player = await playerService.findById(playerId);

  const periodDeltas = await Promise.all(
    PERIODS.map(async period => {
      const deltas = await getPlayerPeriodDeltas(playerId, period, latest, player);
      return { period, deltas };
    })
  );

  // Turn an array of deltas, into an object, using the period as a key,
  // then include only the deltas array in the final object, not the period fields
  return mapValues(keyBy(periodDeltas, 'period'), p => p.deltas);
}
Example #3
Source File: ExplicitLoaderImpl.ts    From type-graphql-dataloader with MIT License 6 votes vote down vote up
function directLoader<V>(
  relation: RelationMetadata,
  connection: Connection,
  grouper: string | ((entity: V) => any)
) {
  return async (ids: readonly any[]) => {
    const entities = keyBy(
      await connection
        .createQueryBuilder<V>(relation.type, relation.propertyName)
        .whereInIds(ids)
        .getMany(),
      grouper
    ) as Dictionary<V>;
    return ids.map((id) => entities[id]);
  };
}
Example #4
Source File: permissionUtil.ts    From amplication with Apache License 2.0 6 votes vote down vote up
export function preparePermissionsByAction(
  availableActions: PermissionAction[],
  permissions?: models.EntityPermission[] | null
): PermissionByActionName {
  let defaultGroups = Object.fromEntries(
    availableActions.map((action) => [
      action.action.toString(),
      getDefaultEntityPermission(action.action),
    ])
  );

  let groupedValues = keyBy(permissions, (permission) => permission.action);

  return {
    ...defaultGroups,
    ...groupedValues,
  };
}
Example #5
Source File: create-add-on.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
private getAddonVersions = () => {
    const { selectedAddon } = this.state;
    if (selectedAddon.addonName || selectedAddon.name) {
      this.props.getAddonVersions(selectedAddon.addonName || selectedAddon.name).then((data) => {
        this.setState({
          selectedAddonVersions: map(data, (item) => item.version),
          versionMap: keyBy(data, 'version'),
          selectedAddonPlans: Object.keys(data[0].spec.plan || { basic: {} }),
        });
      });
    }
  };
Example #6
Source File: search.repository.ts    From linkedin-private-api with MIT License 6 votes vote down vote up
private async fetchCompanies({
    skip = 0,
    limit = 10,
    keywords,
  }: {
    skip?: number;
    limit?: number;
    keywords?: string;
  }): Promise<CompanySearchHit[]> {
    const response = await this.client.request.search.searchBlended({
      skip,
      limit,
      keywords,
      filters: { resultType: LinkedInSearchType.COMPANIES },
    });

    const companies = response.included
      .filter(entity => entity.$type === MINI_COMPANY_TYPE)
      .map(company => ({
        ...company,
        companyId: company.entityUrn.replace('urn:li:fs_miniCompany:', ''),
      })) as MiniCompany[];

    const companiesByUrn = keyBy(companies, 'entityUrn');
    const searchHits = flatten(
      response.data.elements.filter(e => e.type === SearchResultType.SEARCH_HITS && e.elements).map(e => e.elements!),
    );

    return searchHits.map(searchHit => ({
      ...searchHit,
      company: companiesByUrn[searchHit.targetUrn],
    }));
  }
Example #7
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 6 votes vote down vote up
createSources = async (
  poiEntities: SiteSurveyPoint[],
  sourcesRepository: Repository<Sources>,
) => {
  // Create sources for each new poi
  const sources = poiEntities.map((poi) => {
    return {
      site: poi.site,
      poi,
      type: SourceType.HOBO,
    };
  });

  logger.log('Saving sources');
  const sourceEntities = await Promise.all(
    sources.map((source) =>
      sourcesRepository
        .findOne({
          relations: ['surveyPoint', 'site'],
          where: {
            site: source.site,
            surveyPoint: source.poi,
            type: source.type,
          },
        })
        .then((foundSource) => {
          if (foundSource) {
            return foundSource;
          }

          return sourcesRepository.save(source);
        }),
    ),
  );

  // Map surveyPoints to created sources. Hobo sources have a specified poi.
  return keyBy(sourceEntities, (o) => o.surveyPoint!.id);
}
Example #8
Source File: search.repository.ts    From linkedin-private-api with MIT License 6 votes vote down vote up
private async fetchPeople({
    skip = 0,
    limit = 10,
    filters = {},
    keywords,
  }: {
    skip?: number;
    limit?: number;
    filters?: PeopleSearchFilters;
    keywords?: string;
  } = {}): Promise<PeopleSearchHit[]> {
    const response = await this.client.request.search.searchBlended({
      keywords,
      skip,
      limit,
      filters: { ...filters, resultType: LinkedInSearchType.PEOPLE },
    });

    const profiles = keyBy(getProfilesFromResponse<GetBlendedSearchResponse>(response), 'entityUrn');
    const searchHits = flatten(
      response.data.elements.filter(e => e.type === SearchResultType.SEARCH_HITS && e.elements).map(e => e.elements!),
    );

    return searchHits.map(searchHit => ({
      ...searchHit,
      profile: profiles[searchHit.targetUrn],
    }));
  }
Example #9
Source File: helpers.ts    From aqualink-app with MIT License 6 votes vote down vote up
mapOceanSenseData = (
  response: OceanSenseDataResponse
): OceanSenseData =>
  mapValues(
    keyBy(
      map(OceanSenseKeysList, (oceanSenseKey) => ({
        key: oceanSenseKey,
        value: mapOceanSenseMetric(response, oceanSenseKey),
      })),
      "key"
    ),
    "value"
  ) as OceanSenseData
Example #10
Source File: profile.repository.ts    From linkedin-private-api with MIT License 6 votes vote down vote up
getProfilesFromResponse = <T extends { included: (LinkedInMiniProfile | { $type: string })[] }>(
  response: T,
): Record<ProfileId, MiniProfile> => {
  const miniProfiles = filter(response.included, p => p.$type === MINI_PROFILE_TYPE) as LinkedInMiniProfile[];

  const transformedMiniProfiles = miniProfiles.map((miniProfile: LinkedInMiniProfile) => transformMiniProfile(miniProfile));

  return keyBy(transformedMiniProfiles, 'profileId');
}
Example #11
Source File: invitation.repository.ts    From linkedin-private-api with MIT License 6 votes vote down vote up
parseInvitationResponse =
  <T extends GetSentInvitationResponse | GetReceivedInvitationResponse>(
    idField: typeof TO_MEMBER_FIELD | typeof FROM_MEMBER_FIELD,
  ) =>
  (response: T): Invitation[] => {
    const results = response.included || [];
    const profiles = keyBy(getProfilesFromResponse<T>(response), 'entityUrn');
    const invitations = results.filter(r => r.$type === INVITATION_TYPE && !!r[idField]) as LinkedInInvitation[];

    return orderBy(
      invitations.map(invitation => ({
        ...invitation,
        profile: profiles[invitation[idField]],
      })),
      'sentTime',
      'desc',
    );
  }
Example #12
Source File: index.tsx    From next-basics with GNU General Public License v3.0 6 votes vote down vote up
protected _render(): void {
    // istanbul ignore else
    if (this.isConnected) {
      const objectMap = keyBy(this.objectList, "objectId");

      ReactDOM.render(
        <BrickWrapper>
          <CmdbInstanceSelectPanelWrapper
            formElement={this.getFormElement()}
            name={this.name}
            labelTooltip={this.labelTooltip}
            required={this.required}
            message={this.message}
            validator={this.validator}
            notRender={this.notRender}
            objectMap={objectMap}
            objectId={this.objectId}
            instanceIdList={this.value}
            onChange={this._handleChange}
            onChangeV2={this._handleChangeV2}
            labelCol={this.labelCol}
            wrapperCol={this.wrapperCol}
            label={this.label}
            addButtonText={this.addButtonText}
            instanceQuery={this.instanceQuery}
            fields={this.fields}
            addInstancesModalPageSize={this.addInstancesModalPageSize}
            showSizeChanger={this.showSizeChanger}
            pageSizeOptions={this.pageSizeOptions}
          />
        </BrickWrapper>,
        this
      );
    }
  }
Example #13
Source File: index.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
protected _render(): void {
    // istanbul ignore else
    if (this.isConnected && this.objectList) {
      const objectMap = keyBy(this.objectList, "objectId");
      const mutableProps = {
        value: this.value,
      };
      if (this.mergeUseAndUserGroup) {
        mutableProps.value = this._handleMergeUseAndUserGroup(
          mutableProps.value
        );
      }
      ReactDOM.render(
        <BrickWrapper>
          <UserOrUserGroupSelect
            formElement={this.getFormElement()}
            name={this.name}
            label={this.label}
            labelTooltip={this.labelTooltip}
            objectMap={objectMap}
            message={this.message}
            required={this.required}
            validator={this.validator}
            notRender={this.notRender}
            placeholder={this.placeholder}
            value={mutableProps.value as any}
            hideAddMeQuickly={this.hideAddMeQuickly}
            hideSelectByCMDB={this.hideSelectByCMDB}
            onChange={this._handleChange}
            optionsMode={this.optionsMode}
            helpBrick={this.helpBrick}
            labelBrick={this.labelBrick}
            labelCol={this.labelCol}
            wrapperCol={this.wrapperCol}
            staticList={this.staticList}
            mergeUseAndUserGroup={this.mergeUseAndUserGroup}
            query={this.query}
            userQuery={this.userQuery}
            userGroupQuery={this.userGroupQuery}
            hideInvalidUser={this.hideInvalidUser}
          />
        </BrickWrapper>,
        this
      );
    }
  }
Example #14
Source File: group.service.ts    From wise-old-man with MIT License 5 votes vote down vote up
/**
 * Gets the group hiscores for a specific metric.
 * All members which HAVE SNAPSHOTS will included and sorted by rank.
 */
async function getHiscores(groupId: number, metric: string, pagination: Pagination) {
  if (!metric || !METRICS.includes(metric as Metric)) {
    throw new BadRequestError(`Invalid metric: ${metric}.`);
  }

  // Fetch all memberships for the group
  const memberships = await Membership.findAll({
    attributes: ['playerId'],
    where: { groupId },
    include: [{ model: Player }]
  });

  if (!memberships || memberships.length === 0) {
    return [];
  }

  const valueKey = getMetricValueKey(metric as Metric);
  const rankKey = getMetricRankKey(metric as Metric);
  const measure = getMetricMeasure(metric as Metric);
  const memberIds = memberships.map(m => m.player.id);

  const query = `
    SELECT s.*
    FROM (SELECT q."playerId", MAX(q."createdAt") AS max_date
          FROM public.snapshots q
          WHERE q."playerId" IN (${memberIds.join(',')})
          GROUP BY q."playerId"
          ) r
    JOIN public.snapshots s
      ON s."playerId" = r."playerId" AND s."createdAt" = r.max_date
    ORDER BY s."${valueKey}" DESC
    LIMIT :limit
    OFFSET :offset
  `;

  // Execute the query above, which returns the latest snapshot for each member
  const latestSnapshots = await sequelize.query(query, {
    type: QueryTypes.SELECT,
    replacements: { ...pagination }
  });

  // Formats the experience snapshots to a key:value map.
  // Example: { '1623': { rank: 350567, experience: 6412215 } }
  const experienceMap = mapValues(keyBy(latestSnapshots, 'playerId'), d => {
    const data = {
      rank: parseInt(d[rankKey], 10),
      [measure]: parseInt(d[valueKey], 10)
    };

    if (isSkill(metric as Metric)) {
      data.level = metric === Metrics.OVERALL ? getTotalLevel(d as Snapshot) : getLevel(data.experience);
    }

    return data;
  });

  // Format all the members, add each experience to its respective player, and sort them by exp
  return memberships
    .filter(({ playerId }: any) => experienceMap[playerId] && experienceMap[playerId].rank > 0)
    .map(({ player }: any) => ({ player: player.toJSON(), ...experienceMap[player.id] }))
    .sort((a, b) => b[measure] - a[measure]);
}
Example #15
Source File: search.repository.ts    From linkedin-private-api with MIT License 5 votes vote down vote up
private async fetchJobs({
    skip = 0,
    limit = 10,
    filters = {},
    keywords,
  }: {
    skip?: number;
    limit?: number;
    filters?: JobSearchFilters;
    keywords?: string;
  } = {}): Promise<JobSearchHit[]> {
    const response = await this.client.request.search.searchJobs({
      filters,
      keywords,
      skip,
      limit,
    });

    const jobPostings = response?.included?.filter(element => element.$type === JOB_POSTING_TYPE) as LinkedInJobPosting[];
    const companies = response?.included?.filter(element => element.$type === BASE_COMPANY_TYPE) as LinkedInBaseCompany[];

    const keyedPostings = keyBy(jobPostings, 'entityUrn');
    const keyedCompanies = keyBy(companies, 'entityUrn');

    const searchHits = response?.data?.elements.map(searchHit => {
      const jobPosting = keyedPostings[searchHit.hitInfo.jobPosting];
      const company = keyedCompanies[jobPosting.companyDetails.company];

      const populatedPosting = {
        ...jobPosting,
        companyDetails: { ...jobPosting.companyDetails, company },
      };

      return {
        ...searchHit,
        hitInfo: {
          ...searchHit.hitInfo,
          jobPosting: populatedPosting,
        },
      };
    });

    return searchHits;
  }
Example #16
Source File: mods-state.ts    From ow-mod-manager with MIT License 5 votes vote down vote up
remoteModMap = selector({
  key: 'RemoteModMap',
  get: ({ get }) => {
    const filter = get(settingsState).alphaMode ? alphaFilter : regularFilter;
    return keyBy(get(remoteModList).filter(filter), 'uniqueName');
  },
})
Example #17
Source File: mods-state.ts    From ow-mod-manager with MIT License 5 votes vote down vote up
localModMap = selector({
  key: 'LocalModMap',
  get: ({ get }) => {
    const filter = get(settingsState).alphaMode ? alphaFilter : regularFilter;
    return keyBy(get(localModList).filter(filter), 'uniqueName');
  },
})
Example #18
Source File: upload-hobo-data.ts    From aqualink-app with MIT License 5 votes vote down vote up
parseHoboData = async (
  poiEntities: SiteSurveyPoint[],
  dbIdToCSVId: Record<number, number>,
  rootPath: string,
  poiToSourceMap: Dictionary<Sources>,
  timeSeriesRepository: Repository<TimeSeries>,
) => {
  // Parse hobo data
  const parsedData = poiEntities.map((poi) => {
    const colonyId = poi.name.split(' ')[1].padStart(3, '0');
    const dataFile = COLONY_DATA_FILE.replace('{}', colonyId);
    const colonyFolder = COLONY_FOLDER_PREFIX + colonyId;
    const siteFolder = FOLDER_PREFIX + dbIdToCSVId[poi.site.id];
    const filePath = path.join(rootPath, siteFolder, colonyFolder, dataFile);
    const headers = [undefined, 'id', 'dateTime', 'bottomTemperature'];
    const castFunction = castCsvValues(
      ['id'],
      ['bottomTemperature'],
      ['dateTime'],
    );
    return parseCSV<Data>(filePath, headers, castFunction).map((data) => ({
      timestamp: data.dateTime,
      value: data.bottomTemperature,
      source: poiToSourceMap[poi.id],
      metric: Metric.BOTTOM_TEMPERATURE,
    }));
  });

  // Find the earliest date of data
  const startDates = parsedData.reduce((acc, data) => {
    const minimum = minBy(data, (o) => o.timestamp);

    if (!minimum) {
      return acc;
    }

    return acc.concat(minimum);
  }, []);

  const groupedStartedDates = keyBy(startDates, (o) => o.source.site.id);

  // Start a backfill for each site
  const siteDiffDays: [number, number][] = Object.keys(groupedStartedDates).map(
    (siteId) => {
      const startDate = groupedStartedDates[siteId];
      if (!startDate) {
        return [parseInt(siteId, 10), 0];
      }

      const start = moment(startDate.timestamp);
      const end = moment();
      const diff = Math.min(end.diff(start, 'd'), 200);

      return [startDate.source.site.id, diff];
    },
  );

  const bottomTemperatureData = parsedData.flat();

  // Data are to much to added with one bulk insert
  // So we need to break them in batches
  const batchSize = 1000;
  logger.log(`Saving time series data in batches of ${batchSize}`);
  const inserts = chunk(bottomTemperatureData, batchSize).map((batch) => {
    return timeSeriesRepository
      .createQueryBuilder('time_series')
      .insert()
      .values(batch)
      .onConflict('ON CONSTRAINT "no_duplicate_data" DO NOTHING')
      .execute();
  });

  // 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`);
  });

  return siteDiffDays;
}
Example #19
Source File: sensors.service.ts    From aqualink-app with MIT License 5 votes vote down vote up
private async getClosestTimeSeriesData(
    diveDate: Date,
    siteId: number,
    metrics: Metric[],
    sourceTypes: SourceType[],
    surveyPointId?: number,
  ) {
    const surveyPointCondition = surveyPointId
      ? `source.survey_point_id = ${surveyPointId}`
      : 'source.survey_point_id IS NULL';
    // We will use this many times in our query, so we declare it as constant
    const diff = `(time_series.timestamp::timestamp - '${diveDate.toISOString()}'::timestamp)`;

    // First get all sources needed to avoid inner join later
    const sources = await this.sourcesRepository
      .createQueryBuilder('source')
      .where('source.type IN (:...sourceTypes)', { sourceTypes })
      .andWhere('source.site_id = :siteId', { siteId })
      .andWhere(surveyPointCondition)
      .getMany();

    if (!sources.length) {
      return {};
    }

    // Create map from source_id to source entity
    const sourceMap = keyBy(sources, (source) => source.id);

    // Grab all data at an interval of +/- 24 hours around the diveDate
    // Order (descending) those data by the absolute time distance between the data and the survey diveDate
    // This way the closest data point for each metric for each source type will be the last row
    const timeSeriesData: TimeSeriesData[] = await this.timeSeriesRepository
      .createQueryBuilder('time_series')
      .select('time_series.timestamp', 'timestamp')
      .addSelect('time_series.value', 'value')
      .addSelect('time_series.metric', 'metric')
      .addSelect('time_series.source_id', 'source')
      .where(`${diff} < INTERVAL '1 d'`)
      .andWhere(`${diff} > INTERVAL '-1 d'`)
      .andWhere('time_series.metric IN (:...metrics)', { metrics })
      .andWhere('time_series.source_id IN (:...sourceIds)', {
        sourceIds: Object.keys(sourceMap),
      })
      .orderBy(
        `time_series.source_id, metric, (CASE WHEN ${diff} < INTERVAL '0' THEN (-${diff}) ELSE ${diff} END)`,
        'DESC',
      )
      .getRawMany();

    // Group the data by source id
    const groupedData = groupBy(timeSeriesData, (o) => o.source);

    return Object.keys(groupedData).reduce<SensorDataDto>((data, key) => {
      return {
        ...data,
        // Replace source id by source using the mapped source object
        // Keep only timestamps and value from the resulting objects
        [sourceMap[key].type]: mapValues(
          // Use key by to group the data by metric and keep only the last entry, i.e. the closest one
          keyBy(groupedData[key], (grouped) => grouped.metric),
          (v) => ({ timestamp: v.timestamp, value: v.value }),
        ),
      };
    }, {});
  }
Example #20
Source File: sensors.service.ts    From aqualink-app with MIT License 5 votes vote down vote up
async findSensors(): Promise<
    (Site & { sensorPosition: GeoJSON; sensorType: SensorType })[]
  > {
    const sites = await this.siteRepository.find({
      where: { sensorId: Not(IsNull()) },
    });

    // Get spotter data and add site id to distinguish them
    const spotterData = await Bluebird.map(
      sites,
      (site) => {
        if (site.sensorId === null) {
          console.warn(`Spotter for site ${site.id} appears null.`);
        }
        return getSpotterData(site.sensorId!).then((data) => {
          return {
            id: site.id,
            ...data,
          };
        });
      },
      { concurrency: 10 },
    );

    // Group spotter data by site id for easier search
    const siteIdToSpotterData: Record<number, SpotterData & { id: number }> =
      keyBy(spotterData, (o) => o.id);

    // Construct final response
    return sites.map((site) => {
      const data = siteIdToSpotterData[site.id];
      const longitude = getLatestData(data.longitude)?.value;
      const latitude = getLatestData(data.latitude)?.value;
      const sitePosition = site.polygon as Point;

      // If no longitude or latitude is provided by the spotter fallback to the site coordinates
      return {
        ...site,
        applied: site.applied,
        sensorPosition: createPoint(
          longitude || sitePosition.coordinates[0],
          latitude || sitePosition.coordinates[1],
        ),
        sensorType: SensorType.SofarSpotter,
      };
    });
  }
Example #21
Source File: invitation-repository.spec.ts    From linkedin-private-api with MIT License 4 votes vote down vote up
describe('getReceivedInvitations', () => {
  const requestUrl = new URL('relationships/invitationViews', linkedinApiUrl).toString();
  const reqParams = {
    start: 0,
    count: 100,
    q: 'receivedInvitation',
  };

  it('should fetch first page of invitations', async () => {
    const { response, resultInvitations } = createGetInvitationsResponse(10);
    const keyedInvitations = keyBy(resultInvitations, 'entityUrn');

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const invitationScroller = client.invitation.getReceivedInvitations();
    const invitations = await invitationScroller.scrollNext();

    expect(invitations.length).toEqual(10);
    invitations.forEach((invitation: any) =>
      expect(omit(invitation, ['profile'])).toEqual(keyedInvitations[invitation.entityUrn]),
    );
  });

  it('should sort invitations by sentTime descending', async () => {
    const { response } = createGetInvitationsResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const invitationScroller = client.invitation.getReceivedInvitations();
    const invitations = await invitationScroller.scrollNext();

    expect(invitations.length).toEqual(10);
    expect(invitations).toEqual(orderBy(invitations, 'createdAt', 'desc'));
  });

  it('should allow override skip and limit', async () => {
    const skip = 2;
    const limit = 1;
    const { response, resultInvitations } = createGetInvitationsResponse(1);

    when(axios.get(requestUrl, { params: { ...reqParams, start: skip, count: limit } })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const invitationScroller = client.invitation.getReceivedInvitations({ skip, limit });
    const invitations = await invitationScroller.scrollNext();

    expect(invitations.length).toEqual(1);
    expect(omit(invitations[0], ['profile'])).toEqual(resultInvitations[0]);
  });

  it('should add sender profile on the result', async () => {
    const { response, resultInvitations, resultProfiles } = createGetInvitationsResponse(1);
    const profileId = resultProfiles[0].entityUrn.replace('urn:li:fs_miniProfile:', '');

    resultInvitations[0]['*fromMember'] = resultProfiles[0].entityUrn;
    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const invitationScroller = client.invitation.getReceivedInvitations();
    const invitations = await invitationScroller.scrollNext();
    const invitation = invitations[0];

    expect(invitation.profile.entityUrn).toEqual(resultProfiles[0].entityUrn);
    expect(invitation.profile.profileId).toEqual(profileId);
  });

  it('should be able to scroll invitations using scroller', async () => {
    const { response: firstPageResponse } = createGetInvitationsResponse(10);
    const { response: secondPageResponse } = createGetInvitationsResponse(10);
    const { response: thirdPageResponse } = createGetInvitationsResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 100 } })).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 200 } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const invitationScroller = client.invitation.getReceivedInvitations();
    const firstPageInvitations = await invitationScroller.scrollNext();
    const secondPageInvitations = await invitationScroller.scrollNext();
    const thirdPageInvitations = await invitationScroller.scrollNext();

    expect(firstPageInvitations.length).toEqual(10);
    expect(secondPageInvitations.length).toEqual(10);
    expect(thirdPageInvitations.length).toEqual(10);

    expect(firstPageInvitations).not.toEqual(secondPageInvitations);
    expect(firstPageInvitations).not.toEqual(thirdPageInvitations);
    expect(secondPageInvitations).not.toEqual(thirdPageInvitations);

    [...firstPageInvitations, ...secondPageInvitations, ...thirdPageInvitations].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.relationships.invitation.Invitation');
    });
  });

  it('should be able to scroll invitations using scroller', async () => {
    const { response: firstPageResponse } = createGetInvitationsResponse(10);
    const { response: secondPageResponse } = createGetInvitationsResponse(10);
    const { response: thirdPageResponse } = createGetInvitationsResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 100 } })).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 200 } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const invitationScroller = client.invitation.getReceivedInvitations();
    const firstPageInvitations = await invitationScroller.scrollNext();
    const secondPageInvitations = await invitationScroller.scrollNext();
    const thirdPageInvitations = await invitationScroller.scrollNext();

    expect(firstPageInvitations.length).toEqual(10);
    expect(secondPageInvitations.length).toEqual(10);
    expect(thirdPageInvitations.length).toEqual(10);

    expect(firstPageInvitations).not.toEqual(secondPageInvitations);
    expect(firstPageInvitations).not.toEqual(thirdPageInvitations);
    expect(secondPageInvitations).not.toEqual(thirdPageInvitations);

    [...firstPageInvitations, ...secondPageInvitations, ...thirdPageInvitations].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.relationships.invitation.Invitation');
    });
  });

  it('should be able to override scroller starting point and number of invitations per scroll', async () => {
    const skip = 2;
    const limit = 5;

    const { response: firstPageResponse } = createGetInvitationsResponse(limit);
    const { response: secondPageResponse } = createGetInvitationsResponse(limit);
    const { response: thirdPageResponse } = createGetInvitationsResponse(5);

    when(axios.get(requestUrl, { params: { ...reqParams, start: skip, count: limit } })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: skip + limit, count: limit } })).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: skip + limit * 2, count: limit } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const invitationsScroller = client.invitation.getReceivedInvitations({ skip, limit });
    const firstPageInvitations = await invitationsScroller.scrollNext();
    const secondPageInvitations = await invitationsScroller.scrollNext();
    const thirdPageInvitations = await invitationsScroller.scrollNext();

    expect(firstPageInvitations.length).toEqual(limit);
    expect(secondPageInvitations.length).toEqual(limit);
    expect(thirdPageInvitations.length).toEqual(limit);

    expect(firstPageInvitations).not.toEqual(secondPageInvitations);
    expect(firstPageInvitations).not.toEqual(thirdPageInvitations);
    expect(secondPageInvitations).not.toEqual(thirdPageInvitations);

    [...firstPageInvitations, ...secondPageInvitations, ...thirdPageInvitations].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.relationships.invitation.Invitation');
    });
  });

  it('should be able to scroll to previous response pages', async () => {
    const { response: firstPageResponse } = createGetInvitationsResponse(10);
    const { response: secondPageResponse } = createGetInvitationsResponse(10);
    const { response: thirdPageResponse } = createGetInvitationsResponse(10);

    when(axios.get(requestUrl, { params: reqParams }), { times: 2 }).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 100 } }), {
      times: 2,
    }).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 200 } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const invitationsScroller = client.invitation.getReceivedInvitations();
    const firstPageInvitations = await invitationsScroller.scrollNext();
    const secondPageInvitations = await invitationsScroller.scrollNext();
    const thirdPageInvitations = await invitationsScroller.scrollNext();
    const fourthPageInvitations = await invitationsScroller.scrollBack();
    const fifthPageInvitations = await invitationsScroller.scrollBack();

    expect(firstPageInvitations.length).toEqual(10);
    expect(secondPageInvitations.length).toEqual(10);
    expect(thirdPageInvitations.length).toEqual(10);

    expect(firstPageInvitations).not.toEqual(secondPageInvitations);
    expect(firstPageInvitations).not.toEqual(thirdPageInvitations);
    expect(secondPageInvitations).not.toEqual(thirdPageInvitations);
    expect(fourthPageInvitations).toEqual(secondPageInvitations);
    expect(fifthPageInvitations).toEqual(firstPageInvitations);

    [...firstPageInvitations, ...secondPageInvitations, ...thirdPageInvitations].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.relationships.invitation.Invitation');
    });
  });

  it('should be return empty array if trying to scroll back from the starting point', async () => {
    const { response: firstPageResponse } = createGetInvitationsResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const invitationsScroller = client.invitation.getReceivedInvitations();
    await invitationsScroller.scrollNext();
    const invitations = await invitationsScroller.scrollBack();

    expect(invitations).toEqual([]);
  });

  it('should be not scroll after end of results', async () => {
    const { response: firstPageResponse } = createGetInvitationsResponse(10);
    const { response: secondPageResponse } = createGetInvitationsResponse(10);
    const { response: thirdPageResponse } = createGetInvitationsResponse(10);

    thirdPageResponse.included = [];

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 100 } }), {
      times: 2,
    }).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, start: 200 } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const invitationsScroller = client.invitation.getReceivedInvitations();
    const firstPageInvitations = await invitationsScroller.scrollNext();
    const secondPageInvitations = await invitationsScroller.scrollNext();
    const thirdPageInvitations = await invitationsScroller.scrollNext();
    const fourthPageInvitations = await invitationsScroller.scrollNext();
    const fifthPageInvitations = await invitationsScroller.scrollBack();

    expect(firstPageInvitations.length).toEqual(10);
    expect(secondPageInvitations.length).toEqual(10);
    expect(thirdPageInvitations.length).toEqual(0);
    expect(fourthPageInvitations.length).toEqual(0);

    expect(firstPageInvitations).not.toEqual(secondPageInvitations);
    expect(firstPageInvitations).not.toEqual(thirdPageInvitations);
    expect(secondPageInvitations).not.toEqual(thirdPageInvitations);
    expect(fifthPageInvitations).toEqual(secondPageInvitations);

    [...firstPageInvitations, ...secondPageInvitations].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.relationships.invitation.Invitation');
    });
  });
});
Example #22
Source File: fetchAllRewarders.ts    From rewarder-list with GNU Affero General Public License v3.0 4 votes vote down vote up
fetchAllRewarders = async (network: Network): Promise<void> => {
  const provider = makeProvider(network);
  const quarry = QuarrySDK.load({ provider });
  const allRewarders = await quarry.programs.Mine.account.rewarder.all();
  const allQuarries = await quarry.programs.Mine.account.quarry.all();

  const { tokens, tokenLists } = await fetchAllTokens(network);

  const dir = `${__dirname}/../../data/${network}/`;
  await fs.mkdir(dir, { recursive: true });

  // addresses of each quarry
  const allQuarriesJSON = allQuarries.map((q) => {
    const stakedTokenInfo = tokens[q.account.tokenMintKey.toString()];
    return {
      rewarder: q.account.rewarder.toString(),
      quarry: q.publicKey.toString(),
      stakedToken: {
        mint: q.account.tokenMintKey.toString(),
        decimals: q.account.tokenMintDecimals,
      },
      index: q.account.index,
      slug: stakedTokenInfo?.symbol.toLowerCase() ?? q.account.index.toString(),
      cached: {
        index: q.account.index,
        famineTs: q.account.famineTs.toString(),
        lastUpdateTs: q.account.lastUpdateTs.toString(),
        rewardsPerTokenStored: q.account.rewardsPerTokenStored.toString(),
        rewardsShare: q.account.rewardsShare.toString(),
        numMiners: q.account.numMiners.toString(),
        totalTokensDeposited: q.account.totalTokensDeposited.toString(),
      },
    };
  });

  const allRewarderQuarries = mapValues(
    groupBy(allQuarriesJSON, (q) => q.rewarder),
    (v) => {
      return v
        .map(({ rewarder: _rewarder, ...rest }) => rest)
        .sort((a, b) => (a.cached.index < b.cached.index ? -1 : 1));
    }
  );

  const allRewardersList = allRewarders.map((rewarder) => {
    const quarries = allRewarderQuarries[rewarder.publicKey.toString()] ?? [];
    if (rewarder.account.numQuarries !== quarries.length) {
      console.warn(
        `Expected ${
          rewarder.account.numQuarries
        } quarries on rewarder ${rewarder.publicKey.toString()}; got ${
          quarries.length
        }`
      );
    }

    const rewardsTokenMint = rewarder.account.rewardsTokenMint.toString();
    const rewardsTokenInfo: TokenInfo | null = tokens[rewardsTokenMint] ?? null;
    if (!rewardsTokenInfo) {
      console.warn(
        `rewards token ${rewardsTokenMint} not found in any of the token lists`
      );
    }

    return {
      rewarder: rewarder.publicKey.toString(),
      authority: rewarder.account.authority.toString(),
      rewardsToken: {
        mint: rewardsTokenMint,
        decimals: rewardsTokenInfo?.decimals ?? -1,
      },
      mintWrapper: rewarder.account.mintWrapper.toString(),
      quarries,
    };
  });

  const allRewardersJSON = mapValues(
    keyBy(allRewardersList, (r) => r.rewarder),
    ({ rewarder: _rewarder, quarries, ...info }) => ({
      ...info,
      quarries: quarries.map(
        ({ cached: _cached, ...quarryInfo }) => quarryInfo
      ),
    })
  );

  const allRewardersJSONWithCache = mapValues(
    keyBy(allRewardersList, (r) => r.rewarder),
    ({ rewarder: _rewarder, quarries, ...info }) => ({
      ...info,
      quarries,
    })
  );

  // tmp-token-list
  await fs.writeFile(`.tmp.token-list.json`, stringify(tokenLists));

  // rewarders without the cached values
  await fs.writeFile(`${dir}/all-rewarders.json`, stringify(allRewardersJSON));

  // quarries with cached values -- go in their own files
  await fs.mkdir(`${dir}/rewarders`, { recursive: true });
  for (const [rewarderKey, rewarderInfo] of Object.entries(
    allRewardersJSONWithCache
  )) {
    const rewardsToken = tokens[rewarderInfo.rewardsToken.mint];

    await fs.mkdir(`${dir}/rewarders/${rewarderKey}`, { recursive: true });
    await fs.writeFile(
      `${dir}/rewarders/${rewarderKey}/meta.json`,
      stringify({ ...rewarderInfo, rewardsTokenInfo: rewardsToken })
    );
  }

  console.log(
    `Fetched ${allQuarriesJSON.length} quarries across ${
      Object.keys(allRewarders).length
    } rewarders on ${network}.`
  );
}
Example #23
Source File: audit.mixin.ts    From loopback4-audit-log with MIT License 4 votes vote down vote up
export function AuditRepositoryMixin<
  M extends Entity,
  ID,
  Relations extends object,
  UserID,
  R extends MixinTarget<EntityCrudRepository<M, ID, Relations>>,
>(superClass: R, opts: IAuditMixinOptions) {
  class MixedRepository extends superClass implements IAuditMixin<UserID> {
    getAuditLogRepository: () => Promise<AuditLogRepository>;
    getCurrentUser?: () => Promise<{id?: UserID}>;

    /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
    // @ts-ignore
    async create(
      dataObject: DataObject<M>,
      options?: AuditOptions,
    ): Promise<M> {
      const created = await super.create(dataObject, options);
      if (this.getCurrentUser && !options?.noAudit) {
        const user = await this.getCurrentUser();
        const auditRepo = await this.getAuditLogRepository();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const extras: any = Object.assign({}, opts);
        delete extras.actionKey;
        const audit = new AuditLog({
          actedAt: new Date(),
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          actor: (user?.id as any)?.toString() ?? '0',
          action: Action.INSERT_ONE,
          after: created.toJSON(),
          entityId: created.getId(),
          actedOn: this.entityClass.modelName,
          actionKey: opts.actionKey,
          ...extras,
        });
        auditRepo.create(audit).catch(() => {
          console.error(
            `Audit failed for data => ${JSON.stringify(audit.toJSON())}`,
          );
        });
      }
      return created;
    }

    /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
    // @ts-ignore
    async createAll(
      dataObjects: DataObject<M>[],
      options?: AuditOptions,
    ): Promise<M[]> {
      const created = await super.createAll(dataObjects, options);
      if (this.getCurrentUser && !options?.noAudit) {
        const user = await this.getCurrentUser();
        const auditRepo = await this.getAuditLogRepository();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const extras: any = Object.assign({}, opts);
        delete extras.actionKey;
        const audits = created.map(
          data =>
            new AuditLog({
              actedAt: new Date(),
              // eslint-disable-next-line @typescript-eslint/no-explicit-any
              actor: (user?.id as any).toString() ?? '0',
              action: Action.INSERT_MANY,
              after: data.toJSON(),
              entityId: data.getId(),
              actedOn: this.entityClass.modelName,
              actionKey: opts.actionKey,
              ...extras,
            }),
        );
        auditRepo.createAll(audits).catch(() => {
          const auditsJson = audits.map(a => a.toJSON());
          console.error(
            `Audit failed for data => ${JSON.stringify(auditsJson)}`,
          );
        });
      }
      return created;
    }

    /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
    // @ts-ignore
    async updateAll(
      dataObject: DataObject<M>,
      where?: Where<M>,
      options?: AuditOptions,
    ): Promise<Count> {
      if (options?.noAudit) {
        return super.updateAll(dataObject, where, options);
      }
      const toUpdate = await this.find({where});
      const beforeMap = keyBy(toUpdate, d => d.getId());
      const updatedCount = await super.updateAll(dataObject, where, options);
      const updated = await this.find({where});

      if (this.getCurrentUser) {
        const user = await this.getCurrentUser();
        const auditRepo = await this.getAuditLogRepository();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const extras: any = Object.assign({}, opts);
        delete extras.actionKey;
        const audits = updated.map(
          data =>
            new AuditLog({
              actedAt: new Date(),
              // eslint-disable-next-line @typescript-eslint/no-explicit-any
              actor: (user?.id as any).toString() ?? '0',
              action: Action.UPDATE_MANY,
              before: (beforeMap[data.getId()] as Entity).toJSON(),
              after: data.toJSON(),
              entityId: data.getId(),
              actedOn: this.entityClass.modelName,
              actionKey: opts.actionKey,
              ...extras,
            }),
        );
        auditRepo.createAll(audits).catch(() => {
          const auditsJson = audits.map(a => a.toJSON());
          console.error(
            `Audit failed for data => ${JSON.stringify(auditsJson)}`,
          );
        });
      }

      return updatedCount;
    }

    /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
    // @ts-ignore
    async deleteAll(where?: Where<M>, options?: AuditOptions): Promise<Count> {
      if (options?.noAudit) {
        return super.deleteAll(where, options);
      }
      const toDelete = await this.find({where});
      const beforeMap = keyBy(toDelete, d => d.getId());
      const deletedCount = await super.deleteAll(where, options);

      if (this.getCurrentUser) {
        const user = await this.getCurrentUser();
        const auditRepo = await this.getAuditLogRepository();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const extras: any = Object.assign({}, opts);
        delete extras.actionKey;
        const audits = toDelete.map(
          data =>
            new AuditLog({
              actedAt: new Date(),
              // eslint-disable-next-line @typescript-eslint/no-explicit-any
              actor: (user?.id as any).toString() ?? '0',
              action: Action.DELETE_MANY,
              before: (beforeMap[data.getId()] as Entity).toJSON(),
              entityId: data.getId(),
              actedOn: this.entityClass.modelName,
              actionKey: opts.actionKey,
              ...extras,
            }),
        );
        auditRepo.createAll(audits).catch(() => {
          const auditsJson = audits.map(a => a.toJSON());
          console.error(
            `Audit failed for data => ${JSON.stringify(auditsJson)}`,
          );
        });
      }

      return deletedCount;
    }

    /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
    // @ts-ignore
    async updateById(
      id: ID,
      data: DataObject<M>,
      options?: AuditOptions,
    ): Promise<void> {
      if (options?.noAudit) {
        return super.updateById(id, data, options);
      }
      const before = await this.findById(id);
      // loopback repository internally calls updateAll so we don't want to create another log
      if (options) {
        options.noAudit = true;
      } else {
        options = {noAudit: true};
      }
      await super.updateById(id, data, options);
      const after = await this.findById(id);

      if (this.getCurrentUser) {
        const user = await this.getCurrentUser();
        const auditRepo = await this.getAuditLogRepository();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const extras: any = Object.assign({}, opts);
        delete extras.actionKey;
        const auditLog = new AuditLog({
          actedAt: new Date(),
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          actor: (user?.id as any).toString() ?? '0',
          action: Action.UPDATE_ONE,
          before: before.toJSON(),
          after: after.toJSON(),
          entityId: before.getId(),
          actedOn: this.entityClass.modelName,
          actionKey: opts.actionKey,
          ...extras,
        });

        auditRepo.create(auditLog).catch(() => {
          console.error(
            `Audit failed for data => ${JSON.stringify(auditLog.toJSON())}`,
          );
        });
      }
    }

    /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
    // @ts-ignore
    async replaceById(
      id: ID,
      data: DataObject<M>,
      options?: AuditOptions,
    ): Promise<void> {
      if (options?.noAudit) {
        return super.replaceById(id, data, options);
      }
      const before = await this.findById(id);
      await super.replaceById(id, data, options);
      const after = await this.findById(id);

      if (this.getCurrentUser) {
        const user = await this.getCurrentUser();
        const auditRepo = await this.getAuditLogRepository();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const extras: any = Object.assign({}, opts);
        delete extras.actionKey;
        const auditLog = new AuditLog({
          actedAt: new Date(),
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          actor: (user?.id as any).toString() ?? '0',
          action: Action.UPDATE_ONE,
          before: before.toJSON(),
          after: after.toJSON(),
          entityId: before.getId(),
          actedOn: this.entityClass.modelName,
          actionKey: opts.actionKey,
          ...extras,
        });

        auditRepo.create(auditLog).catch(() => {
          console.error(
            `Audit failed for data => ${JSON.stringify(auditLog.toJSON())}`,
          );
        });
      }
    }

    /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
    // @ts-ignore
    async deleteById(id: ID, options?: AuditOptions): Promise<void> {
      if (options?.noAudit) {
        return super.deleteById(id, options);
      }
      const before = await this.findById(id);
      await super.deleteById(id, options);

      if (this.getCurrentUser) {
        const user = await this.getCurrentUser();
        const auditRepo = await this.getAuditLogRepository();
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const extras: any = Object.assign({}, opts);
        delete extras.actionKey;
        const auditLog = new AuditLog({
          actedAt: new Date(),
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          actor: (user?.id as any).toString() ?? '0',
          action: Action.DELETE_ONE,
          before: before.toJSON(),
          entityId: before.getId(),
          actedOn: this.entityClass.modelName,
          actionKey: opts.actionKey,
          ...extras,
        });

        auditRepo.create(auditLog).catch(() => {
          console.error(
            `Audit failed for data => ${JSON.stringify(auditLog.toJSON())}`,
          );
        });
      }
    }
  }
  return MixedRepository;
}
Example #24
Source File: RelatedFieldsMigrationFix.tsx    From amplication with Apache License 2.0 4 votes vote down vote up
RelatedFieldsMigrationFix = ({ match }: Props) => {
  const applicationId = match.params.application;
  const pendingChangesContext = useContext(PendingChangesContext);

  useNavigationTabs(
    applicationId,
    NAVIGATION_KEY,
    match.url,
    "Fix Entity Relations"
  );

  const { data, loading, error, refetch } = useQuery<TData>(GET_LOOKUP_FIELDS, {
    variables: {
      appId: applicationId,
    },
  });

  const [createDefaultRelatedEntity, { error: createError }] = useMutation<{
    createDefaultRelatedField: models.EntityField;
  }>(CREATE_DEFAULT_RELATED_ENTITY, {
    onCompleted: (createData) => {
      refetch();
      pendingChangesContext.addEntity(
        createData.createDefaultRelatedField.properties.relatedEntityId
      );

      const entity = data?.app.entities.find((entity) =>
        entity.fields?.some(
          (field) => field.id === createData.createDefaultRelatedField.id
        )
      );
      if (entity) {
        pendingChangesContext.addEntity(entity.id);
      }
    },
  });

  const handleRelatedFieldFormSubmit = useCallback(
    (relatedFieldValues: FormValues) => {
      createDefaultRelatedEntity({
        variables: {
          fieldId: relatedFieldValues.fieldId,
          relatedFieldDisplayName: relatedFieldValues.relatedFieldDisplayName,
          relatedFieldName: camelCase(
            relatedFieldValues.relatedFieldDisplayName
          ),
        },
      }).catch(console.error);
    },
    [createDefaultRelatedEntity]
  );

  const entityDictionary = useMemo(() => {
    return keyBy(data?.app.entities, (entity) => entity.id);
  }, [data]);

  const fieldDictionary = useMemo(() => {
    const allFields =
      data?.app.entities.flatMap((entity) => entity.fields || []) || [];

    const d = keyBy(allFields, (field) => field.permanentId);
    console.log(d);
    return d;
  }, [data]);

  const errorMessage =
    (error && formatError(error)) || (createError && formatError(createError));

  return (
    <PageContent className={CLASS_NAME}>
      <h2>New Release Updates</h2>
      <div className={`${CLASS_NAME}__message`}>
        Version 0.3.2 includes big improvements in how we manage related
        entities. The changes require your attention. <br />
        Following is a list of all the entities in your app, please provide the
        missing names for each of your existing relation fields.
        <span className={`${CLASS_NAME}__highlight`}>
          {" "}
          It will only take you a minute!
        </span>
      </div>
      {loading && <CircularProgress />}
      {data?.app.entities?.map((entity) => (
        <Panel className={`${CLASS_NAME}__entity`} key={entity.id}>
          <PanelHeader>{entity.displayName}</PanelHeader>
          <div className={`${CLASS_NAME}__entity__fields`}>
            {entity.fields && entity.fields.length
              ? entity.fields?.map((field) => (
                  <EntityRelationFieldsChart
                    key={field.id}
                    fixInPlace
                    applicationId={applicationId}
                    entityId={entity.id}
                    entityName={entity.displayName}
                    field={field}
                    relatedField={
                      fieldDictionary[field.properties.relatedFieldId]
                    }
                    relatedEntityName={
                      entityDictionary[field.properties.relatedEntityId]
                        .displayName
                    }
                    onSubmit={handleRelatedFieldFormSubmit}
                  />
                ))
              : "No relation fields"}
          </div>
        </Panel>
      ))}
      <Snackbar
        open={Boolean(error) || Boolean(createError)}
        message={errorMessage}
      />
    </PageContent>
  );
}
Example #25
Source File: _common-custom-alarm.ts    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
createCustomAlarmStore = (scope: CustomAlarmScope) => {
  const serviceMap = {
    org: orgCustomAlarmService,
    'micro-service': mspCustomAlarmService,
  };
  const {
    getCustomAlarms,
    switchCustomAlarm,
    deleteCustomAlarm,
    getCustomMetrics,
    getCustomAlarmDetail,
    getCustomAlarmTargets,
    createCustomAlarm,
    editCustomAlarm,
    getPreviewMetaData,
  } = serviceMap[scope];

  const defaultPaging = {
    pageNo: 1,
    pageSize: PAGINATION.pageSize,
    total: 0,
  };

  const initState: IState = {
    customAlarms: [],
    customMetricMap: {} as COMMON_CUSTOM_ALARM.CustomMetricMap,
    customAlarmDetail: {} as COMMON_CUSTOM_ALARM.CustomAlarmDetail,
    customAlarmTargets: [],
    customAlarmPaging: defaultPaging,
  };

  const customAlarmStore = createStore({
    name: `${scope}CustomAlarm`,
    state: initState,
    effects: {
      async getCustomAlarms(
        { call, update, getParams },
        payload?: Omit<COMMON_CUSTOM_ALARM.IPageParam, 'tenantGroup'>,
      ) {
        const { tenantGroup } = getParams();
        const { list: customAlarms } = await call(
          getCustomAlarms,
          { ...payload, tenantGroup },
          { paging: { key: 'customAlarmPaging' } },
        );
        update({ customAlarms });
      },
      async getCustomAlarmDetail({ call, update, getParams }, id: number) {
        const { tenantGroup } = getParams();
        const customAlarmDetail: COMMON_CUSTOM_ALARM.CustomAlarmDetail = await call(getCustomAlarmDetail, {
          id,
          tenantGroup,
        });
        update({ customAlarmDetail });
      },
      async switchCustomAlarm({ call, select, getParams }, payload: { id: number; enable: boolean }) {
        const { tenantGroup } = getParams();
        await call(
          switchCustomAlarm,
          { ...payload, tenantGroup },
          { successMsg: i18n.t('status switched successfully') },
        );
        const { pageSize, pageNo } = select((s) => s.customAlarmPaging);
        customAlarmStore.effects.getCustomAlarms({ pageNo, pageSize });
      },
      async deleteCustomAlarm({ call, getParams }, id: number) {
        const { tenantGroup } = getParams();
        await call(deleteCustomAlarm, { id, tenantGroup }, { successMsg: i18n.t('deleted successfully') });
        customAlarmStore.effects.getCustomAlarms();
      },
      async getCustomMetrics({ call, update, getParams }) {
        const { tenantGroup } = getParams();
        const data: COMMON_CUSTOM_ALARM.CustomMetrics = await call(getCustomMetrics, tenantGroup);
        if (isEmpty(data)) return;
        const { metrics, filterOperators, functionOperators, ...restData } = data;
        const metricMap = keyBy(
          map(metrics, (item) => {
            const fieldMap = keyBy(item.fields, 'field.key');
            const tagMap = keyBy(item.tags, 'tag.key');
            return { ...item, fieldMap, tagMap };
          }),
          'name.key',
        );
        const filterOperatorMap = keyBy(filterOperators, 'key');
        const functionOperatorMap = keyBy(functionOperators, 'key');
        update({ customMetricMap: { ...restData, filterOperatorMap, functionOperatorMap, metricMap } });
      },
      async getCustomAlarmTargets({ call, update, getParams }) {
        const { tenantGroup } = getParams();
        const { targets: customAlarmTargets } = await call(getCustomAlarmTargets, tenantGroup);
        update({ customAlarmTargets });
      },
      async createCustomAlarm({ call, getParams }, payload: Omit<COMMON_CUSTOM_ALARM.CustomAlarmQuery, 'tenantGroup'>) {
        const { tenantGroup } = getParams();
        await call(createCustomAlarm, { ...payload, tenantGroup }, { successMsg: i18n.t('created successfully') });
        customAlarmStore.effects.getCustomAlarms();
      },
      async editCustomAlarm({ call, getParams }, payload: Omit<COMMON_CUSTOM_ALARM.CustomAlarmQuery, 'tenantGroup'>) {
        const { tenantGroup } = getParams();
        await call(editCustomAlarm, { ...payload, tenantGroup }, { successMsg: i18n.t('updated successfully') });
        customAlarmStore.effects.getCustomAlarms();
      },
      async getPreviewMetaData(
        { call, getParams },
        payload: Omit<COMMON_CUSTOM_ALARM.CustomAlarmQuery, 'tenantGroup'>,
      ) {
        const { tenantGroup } = getParams();
        const metaData = await call(getPreviewMetaData, { ...payload, tenantGroup });
        return metaData;
      },
    },
    reducers: {
      clearCustomAlarms(state) {
        state.customAlarms = [];
      },
      clearCustomAlarmDetail(state) {
        state.customAlarmDetail = {} as COMMON_CUSTOM_ALARM.CustomAlarmDetail;
      },
    },
  });

  return customAlarmStore;
}
Example #26
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
CustomAlarm = ({ scopeType }: { scopeType: string }) => {
  const customAlarmStore = customAlarmStoreMap[scopeType];
  const monitorMetaDataStore = monitorMetaDataStoreMap[scopeType];
  const [switchCustomAlarmLoading, getPreviewMetaDataLoading, getCustomAlarmsLoading, getCustomAlarmDetailLoading] =
    useLoading(customAlarmStore, [
      'switchCustomAlarm',
      'getPreviewMetaData',
      'getCustomAlarms',
      'getCustomAlarmDetail',
    ]);
  const [extraLoading] = useLoading(monitorMetaDataStore, ['getMetaData']);
  const [metaGroups, metaConstantMap, metaMetrics] = monitorMetaDataStore.useStore((s: any) => [
    s.metaGroups,
    s.metaConstantMap,
    s.metaMetrics,
  ]);
  const { getMetaGroups, getMetaData } = monitorMetaDataStore.effects;
  const {
    fields,
    tags,
    metric,
    filters: defaultFilters,
  } = React.useMemo(() => (metaMetrics || [])[0] || {}, [metaMetrics]);
  const { types, filters } = React.useMemo(() => metaConstantMap, [metaConstantMap]);
  const fieldsMap = React.useMemo(() => keyBy(fields, 'key'), [fields]);

  const [customAlarms, customAlarmPaging, customMetricMap, customAlarmDetail, customAlarmTargets] =
    customAlarmStore.useStore((s: any) => [
      s.customAlarms,
      s.customAlarmPaging,
      s.customMetricMap,
      s.customAlarmDetail,
      s.customAlarmTargets,
    ]);
  const {
    getCustomAlarms,
    switchCustomAlarm,
    deleteCustomAlarm,
    getCustomMetrics,
    getCustomAlarmDetail,
    getCustomAlarmTargets,
    createCustomAlarm,
    editCustomAlarm,
  } = customAlarmStore.effects;
  const { clearCustomAlarmDetail } = customAlarmStore.reducers;
  const { total, pageSize, pageNo } = customAlarmPaging;

  useMount(() => {
    getMetaGroups();
    getCustomMetrics();
    getCustomAlarmTargets();
  });

  const [
    { modalVisible, editingFilters, editingFields, selectedMetric, activedFormData, previewerKey, layout, searchValue },
    updater,
    update,
  ] = useUpdate({
    layout: [],
    modalVisible: false,
    editingFilters: [],
    editingFields: [],
    selectedMetric: undefined as any,
    activedFormData: {},
    previewerKey: undefined,
    searchValue: '',
  });

  React.useEffect(() => {
    updater.selectedMetric(metric);
  }, [metric, updater]);

  React.useEffect(() => {
    if (isEmpty(customAlarmDetail)) return;
    const { rules } = customAlarmDetail;
    const { activedMetricGroups } = rules[0];
    getMetaData({ groupId: activedMetricGroups[activedMetricGroups.length - 1] });
  }, [customAlarmDetail, getMetaData]);

  React.useEffect(() => {
    const { rules, notifies } = customAlarmDetail;
    if (isEmpty(rules) || isEmpty(notifies)) return;

    const { functions } = rules[0];
    update({
      editingFields: map(functions, (item) => {
        const aggregations = get(types[get(fieldsMap[item.field], 'type')], 'aggregations');
        return {
          ...item,
          uniKey: uniqueId(),
          aggregations,
          aggregatorType: get(find(aggregations, { aggregation: item.aggregator }), 'result_type'),
        };
      }),
    });
  }, [customAlarmDetail, fieldsMap, types, update]);

  React.useEffect(() => {
    const { name, rules, notifies, id } = customAlarmDetail;
    if (isEmpty(rules) || isEmpty(notifies)) return;

    const { window, metric: _metric, filters: _filters, group, activedMetricGroups } = rules[0];
    const { title, content, targets } = notifies[0];
    update({
      editingFilters: map(_filters, (item) => ({ ...item, uniKey: uniqueId() })),
      activedFormData: {
        id,
        name,
        rule: {
          activedMetricGroups,
          window,
          metric: _metric,
          group,
        },
        notify: {
          title,
          content,
          targets: filter(targets, (target) => target !== 'ticket'),
        },
      },
      selectedMetric: _metric,
    });
  }, [customAlarmDetail, update]);

  React.useEffect(() => {
    getCustomAlarms({ name: searchValue, pageNo: 1 });
  }, [searchValue]);

  const handlePageChange: TableProps<COMMON_CUSTOM_ALARM.CustomAlarms>['onChange'] = (paging) => {
    const { current, pageSize: size } = paging;
    getCustomAlarms({ pageNo: current, pageSize: size, name: searchValue });
  };
  const handleDeleteAlarm = (id: number) => {
    confirm({
      title: i18n.t('are you sure you want to delete this item?'),
      content: i18n.t('the item will be permanently deleted!'),
      onOk() {
        deleteCustomAlarm(id);
      },
    });
  };

  const handleEnableRule = (enable: string, record: COMMON_CUSTOM_ALARM.CustomAlarms) => {
    switchCustomAlarm({
      enable: enable === 'enable',
      id: record.id,
    }).then(() => {
      getCustomAlarms({ pageNo, pageSize, name: searchValue });
    });
  };

  const columns: Array<ColumnProps<COMMON_CUSTOM_ALARM.CustomAlarms>> = [
    {
      title: i18n.t('Name'),
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: i18n.t('Status'),
      dataIndex: 'enable',
      onCell: () => ({ style: { minWidth: 100, maxWidth: 300 } }),
      render: (enable: boolean, record) => (
        <Dropdown
          trigger={['click']}
          overlay={
            <Menu
              onClick={(e) => {
                handleEnableRule(e.key, record);
              }}
            >
              <Menu.Item key="enable">
                <Badge text={i18n.t('Enable')} status="success" />
              </Menu.Item>
              <Menu.Item key="unable">
                <Badge text={i18n.t('unable')} status="default" />
              </Menu.Item>
            </Menu>
          }
        >
          <div
            onClick={(e) => e.stopPropagation()}
            className="group flex items-center justify-between px-2 cursor-pointer absolute top-0 left-0 bottom-0 right-0 hover:bg-default-04"
          >
            <Badge text={enable ? i18n.t('Enable') : i18n.t('unable')} status={enable ? 'success' : 'default'} />
            <ErdaIcon type="caret-down" size={20} fill="black-3" className="opacity-0 group-hover:opacity-100" />
          </div>
        </Dropdown>
      ),
    },
    {
      title: i18n.t('Indicator'),
      dataIndex: 'metric',
      key: 'metric',
    },
    {
      title: i18n.t('Period'),
      dataIndex: 'window',
      key: 'window',
      render: (value: number) => `${value} ${i18n.t('min')}`,
    },
    {
      title: i18n.t('Notification method'),
      dataIndex: 'notifyTargets',
      key: 'notifyTargets',
      render: (value: string[]) => `${value.join('、')}`,
    },
    {
      title: i18n.t('Creator'),
      dataIndex: 'creator',
      render: (text: string) => <UserInfo id={text} />,
    },
  ];

  const filterColumns = [
    {
      title: i18n.t('label'),
      dataIndex: 'tag',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Filter) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(tag) => {
            handleEditEditingFilters(uniKey, [
              { key: 'tag', value: tag },
              { key: 'value', value: undefined },
            ]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(tags, ({ key, name }) => (
            <Select.Option key={key} value={key}>
              {name}
            </Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('operation'),
      dataIndex: 'operator',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Filter) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(operator) => {
            handleEditEditingFilters(uniKey, [{ key: 'operator', value: operator }]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(filters, ({ operation, name }) => (
            <Select.Option key={operation}>{name}</Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Expected value'),
      dataIndex: 'value',
      render: (value: any, { uniKey }: COMMON_CUSTOM_ALARM.Filter) => {
        let expectedValEle = (
          <Input
            defaultValue={value}
            onBlur={(e: any) => {
              handleEditEditingFilters(uniKey, [{ key: 'value', value: e.target.value }]);
            }}
          />
        );
        const selectedFilter = find(editingFilters, { uniKey }) || ({} as any);
        const { values: _values } = find(tags, { key: selectedFilter.tag }) || ({} as any);
        if (!isEmpty(_values)) {
          expectedValEle = (
            <Select
              dropdownMatchSelectWidth={false}
              showSearch
              className="w-full"
              value={value}
              onSelect={(v: any) => {
                handleEditEditingFilters(uniKey, [{ key: 'value', value: v }]);
              }}
              getPopupContainer={() => document.body}
            >
              {map(_values, ({ value: v, name }) => (
                <Select.Option key={v} value={v}>
                  {name}
                </Select.Option>
              ))}
            </Select>
          );
        }
        return expectedValEle;
      },
    },
  ];

  const filteredTableActions: IActions<COMMON_CUSTOM_ALARM.Filter> = {
    render: (record) => [
      {
        title: i18n.t('Delete'),
        onClick: () => {
          handleRemoveEditingFilter(record.uniKey);
        },
      },
    ],
  };

  const getFieldColumns = (form: FormInstance) => [
    {
      title: i18n.t('Field'),
      dataIndex: 'field',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Field) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(field: any) => {
            handleEditEditingFields(uniKey, [
              { key: 'field', value: field },
              { key: 'aggregations', value: get(types[get(fieldsMap[field], 'type')], 'aggregations') },
            ]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(fields, ({ key, name }) => (
            <Select.Option key={key} value={key}>
              <Tooltip title={name}>{name}</Tooltip>
            </Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Alias'),
      dataIndex: 'alias',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Field) => (
        <Input
          defaultValue={value}
          onBlur={(e: any) => {
            handleEditEditingFields(uniKey, [{ key: 'alias', value: e.target.value }]);
          }}
        />
      ),
    },
    {
      title: i18n.t('cmp:Aggregation'),
      dataIndex: 'aggregator',
      render: (value: string, { uniKey, aggregations }: COMMON_CUSTOM_ALARM.Field) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(aggregator: any) => {
            handleEditEditingFields(uniKey, [
              { key: 'aggregator', value: aggregator },
              { key: 'aggregatorType', value: get(find(aggregations, { aggregation: aggregator }), 'result_type') },
            ]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(aggregations, ({ aggregation, name }) => (
            <Select.Option key={aggregation}>{name}</Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('Operations'),
      dataIndex: 'operator',
      render: (value: string, { uniKey, aggregatorType }: COMMON_CUSTOM_ALARM.Field) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(operator) => {
            handleEditEditingFields(uniKey, [{ key: 'operator', value: operator }]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(get(types[aggregatorType], 'operations'), ({ operation, name }) => (
            <Select.Option key={operation}>{name}</Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Default threshold'),
      dataIndex: 'value',
      fixed: 'right',
      render: (value: any, { uniKey, aggregatorType }: COMMON_CUSTOM_ALARM.Field) => {
        let valueEle = null;
        switch (aggregatorType) {
          case DataType.STRING:
          case DataType.STRING_ARRAY:
            valueEle = (
              <Input
                defaultValue={value}
                onBlur={(e: any) => {
                  handleEditEditingFields(uniKey, [{ key: 'value', value: e.target.value }]);
                }}
              />
            );
            break;
          case DataType.NUMBER:
          case DataType.NUMBER_ARRAY:
            valueEle = (
              <InputNumber
                min={0}
                defaultValue={value}
                onChange={(v: any) => {
                  debounceEditEditingFields(uniKey, [{ key: 'value', value: v }]);
                }}
              />
            );
            break;
          case DataType.BOOL:
          case DataType.BOOL_ARRAY:
            valueEle = (
              <Switch
                checkedChildren="true"
                unCheckedChildren="false"
                defaultChecked={value}
                onClick={(v: boolean) => {
                  handleEditEditingFields(uniKey, [{ key: 'value', value: v }]);
                }}
              />
            );
            break;
          default:
            break;
        }
        return valueEle;
      },
    },
  ];

  const fieldsTableActions: IActions<COMMON_CUSTOM_ALARM.Field> = {
    render: (record) => [
      {
        title: i18n.t('Delete'),
        onClick: () => {
          handleRemoveEditingField(record.uniKey);
        },
      },
    ],
  };

  const handleAddEditingFilters = () => {
    updater.editingFilters([
      {
        uniKey: uniqueId(),
        // tag: customMetricMap.metricMap[selectedMetric].tags[0].tag.key,
        tag: undefined,
        // operator: keys(customMetricMap.filterOperatorMap)[0],
        operator: undefined,
      },
      ...editingFilters,
    ]);
  };

  const handleAddEditingFields = () => {
    updater.editingFields([
      {
        uniKey: uniqueId(),
        field: undefined,
        alias: undefined,
        aggregator: undefined,
        operator: undefined,
      },
      ...editingFields,
    ]);
  };

  const editRule = (rules: any, uniKey: any, items: Array<{ key: string; value: any }>) => {
    if (!uniKey) return;
    const _rules = cloneDeep(rules);
    const rule = find(_rules, { uniKey });
    const index = findIndex(_rules, { uniKey });
    const rest = reduce(items, (acc, { key, value }) => ({ ...acc, [key]: value }), {});
    const newRule = {
      uniKey,
      ...rule,
      ...rest,
    } as any;

    // // 标签、字段对应不同的 value 类型,改变标签或字段就重置 value
    // if (['tag', 'field'].includes(item.key)) {
    //   newRule = { ...newRule, value: undefined };
    // }

    fill(_rules, newRule, index, index + 1);

    return _rules;
  };

  const handleShowNotifySample = () => {
    Modal.info({
      title: i18n.t('cmp:Template Sample'),
      content: <span className="prewrap">{customMetricMap.notifySample}</span>,
    });
  };

  const handleEditEditingFilters = (uniKey: any, items: Array<{ key: string; value: any }>) => {
    updater.editingFilters(editRule(editingFilters, uniKey, items));
  };

  const handleEditEditingFields = (uniKey: any, items: Array<{ key: string; value: any }>) => {
    updater.editingFields(editRule(editingFields, uniKey, items));
  };

  const debounceEditEditingFields = debounce(handleEditEditingFields, 500);

  const handleRemoveEditingFilter = (uniKey: string | undefined) => {
    updater.editingFilters(filter(editingFilters, (item) => item.uniKey !== uniKey));
  };

  const handleRemoveEditingField = (uniKey: string | undefined) => {
    updater.editingFields(filter(editingFields, (item) => item.uniKey !== uniKey));
  };

  const extraKeys = ['uniKey', 'aggregations', 'aggregatorType'];
  const openModal = (id?: number) => {
    id && getCustomAlarmDetail(id);
    updater.modalVisible(true);
  };

  const closeModal = () => {
    updater.editingFields([]);
    updater.editingFilters([]);
    updater.activedFormData({});
    updater.modalVisible(false);
    updater.previewerKey(undefined);
    clearCustomAlarmDetail();
  };

  const someValueEmpty = (data: any[], key: string) => {
    return some(data, (item) => isEmpty(toString(item[key])));
  };

  const beforeSubmit = (data: any) => {
    return new Promise((resolve, reject) => {
      if (isEmpty(editingFields)) {
        message.warning(i18n.t('cmp:field rules are required'));
        return reject();
      }
      if (someValueEmpty(editingFilters, 'value')) {
        message.warning(i18n.t('cmp:The expected value of filter rule is required.'));
        return reject();
      }
      if (someValueEmpty(editingFields, 'alias')) {
        message.warning(i18n.t('cmp:field rule alias is required'));
        return reject();
      }
      if (uniqBy(editingFields, 'alias').length !== editingFields.length) {
        message.warning(i18n.t('cmp:field rule alias cannot be repeated'));
        return reject();
      }
      if (someValueEmpty(editingFields, 'value')) {
        message.warning(i18n.t('cmp:field rule threshold is required'));
        return reject();
      }
      resolve(data);
    });
  };

  const handleUpdateCustomAlarm = (value: { name: string; rule: any; notify: any }) => {
    const _notify = merge({}, value.notify, { targets: [...(value.notify.targets || []), 'ticket'] });
    const payload = {
      name: value.name,
      rules: [
        {
          ...value.rule,
          metric: selectedMetric,
          functions: map(editingFields, (item) => omit(item, extraKeys)),
          filters: map(editingFilters, (item) => omit(item, extraKeys)),
        },
      ],
      notifies: [_notify],
    };
    if (isEmpty(activedFormData)) {
      createCustomAlarm(payload);
    } else {
      editCustomAlarm({ id: activedFormData.id, ...payload });
    }
    closeModal();
  };

  const BasicForm = ({ form }: { form: FormInstance }) => {
    const fieldsList = [
      {
        label: i18n.t('Name'),
        name: 'name',
        itemProps: {
          maxLength: 50,
        },
      },
    ];
    return <RenderPureForm list={fieldsList} form={form} formItemLayout={formItemLayout} />;
  };

  const RuleForm = ({ form }: { form: FormInstance }) => {
    let fieldsList = [
      {
        label: `${i18n.t('Period')} (${i18n.t('common:minutes')})`,
        name: ['rule', 'window'],
        type: 'inputNumber',
        itemProps: {
          min: 0,
          precision: 0,
          className: 'w-full',
        },
      },
      {
        label: i18n.t('Indicator'),
        name: ['rule', 'activedMetricGroups'],
        type: 'cascader',
        options: metaGroups,
        itemProps: {
          className: 'w-full',
          showSearch: true,
          placeholder: i18n.t('cmp:Please select the index group'),
          onChange: (v: any) => {
            getMetaData({ groupId: v[v.length - 1] }).then(() => {
              form.setFieldsValue({
                rule: {
                  group: undefined,
                },
              });
              update({
                editingFilters: [],
                editingFields: [],
                previewerKey: undefined,
              });
            });
          },
        },
      },
    ];
    if (selectedMetric) {
      fieldsList = concat(
        fieldsList,
        {
          label: i18n.t('cmp:Filter rule'),
          name: ['rule', 'filters'],
          required: false,
          getComp: () => (
            <>
              <Button
                ghost
                className="mb-2"
                type="primary"
                disabled={someValueEmpty(editingFilters, 'value')}
                onClick={handleAddEditingFilters}
              >
                {i18n.t('cmp:Add-filter-rules')}
              </Button>
              <ErdaTable
                hideHeader
                className="filter-rule-table"
                rowKey="uniKey"
                dataSource={editingFilters}
                columns={filterColumns}
                actions={filteredTableActions}
                scroll={undefined}
              />
            </>
          ),
        },
        {
          label: i18n.t('cmp:Grouping rule'),
          name: ['rule', 'group'],
          required: true,
          type: 'select',
          options: map(tags, ({ key, name }) => ({ value: key, name })),
          itemProps: {
            mode: 'multiple',
            allowClear: true,
            className: 'w-full',
          },
        },
        {
          label: i18n.t('cmp:Field rule'),
          name: ['rule', 'functions'],
          required: false,
          getComp: () => (
            <>
              <Button
                className="mb-2"
                type="primary"
                ghost
                disabled={someValueEmpty(editingFields, 'value')}
                onClick={handleAddEditingFields}
              >
                {i18n.t('cmp:Add-field-rules')}
              </Button>
              <ErdaTable
                hideHeader
                className="field-rule-table"
                rowKey="uniKey"
                dataSource={editingFields}
                actions={fieldsTableActions}
                columns={getFieldColumns(form)}
                scroll={undefined}
              />
            </>
          ),
        },
      );
    }
    return <RenderPureForm list={fieldsList} form={form} formItemLayout={formItemLayout} />;
  };

  const NotifyForm = ({ form }: { form: FormInstance }) => {
    const Comp = () => (
      <>
        <Button
          className="mb-2"
          type="primary"
          ghost
          disabled={isEmpty(customMetricMap.notifySample)}
          onClick={handleShowNotifySample}
        >
          {i18n.t('cmp:Template Sample')}
        </Button>
        <MarkdownEditor
          value={form.getFieldValue(['notify', 'content'])}
          onBlur={(value) => {
            form.setFieldsValue({
              notify: {
                ...(form.getFieldValue('notify') || {}),
                content: value,
              },
            });
          }}
          placeholder={i18n.t('cmp:Refer to the sample to input content')}
          maxLength={512}
        />
      </>
    );

    const fieldsList = [
      {
        label: i18n.t('cmp:Notification method'),
        name: ['notify', 'targets'],
        type: 'select',
        required: false,
        options: map(
          filter(customAlarmTargets, ({ key }) => key !== 'ticket'),
          ({ key, display }) => ({ value: key, name: display }),
        ),
        itemProps: {
          mode: 'multiple',
          allowClear: true,
          className: 'w-full',
        },
      },
      {
        label: i18n.t('cmp:Message title'),
        name: ['notify', 'title'],
        itemProps: {
          maxLength: 128,
          placeholder: i18n.t('cmp:message title rules template', { interpolation: { suffix: '>', prefix: '<' } }),
        },
      },
      {
        label: i18n.t('cmp:Message content'),
        name: ['notify', 'content'],
        getComp: () => <Comp />,
      },
    ];
    return <RenderPureForm list={fieldsList} form={form} formItemLayout={formItemLayout} />;
  };

  const CustomAlarmForm = ({ form }: any) => {
    if (isEmpty(customMetricMap) || isEmpty(customAlarmTargets)) return null;
    return (
      <div className="custom-alarm-form">
        <BasicForm form={form} />
        <div className="title font-bold text-base">{i18n.t('cmp:Trigger rule')}</div>
        <RuleForm form={form} />
        <div className="title font-bold text-base">{i18n.t('cmp:Message template')}</div>
        <NotifyForm form={form} />
      </div>
    );
  };

  const customRender = (content: JSX.Element) => (
    <div className="flex justify-between items-center">
      <div className="flex-1">{content}</div>
      <IF check={!!previewerKey}>
        <div className="custom-alarm-previewer px-4">
          <Spin spinning={getPreviewMetaDataLoading}>
            <BoardGrid.Pure layout={layout} />
          </Spin>
        </div>
      </IF>
    </div>
  );

  const actions: IActions<COMMON_CUSTOM_ALARM.CustomAlarms> = {
    render: (record: COMMON_CUSTOM_ALARM.CustomAlarms) => renderMenu(record),
  };

  const renderMenu = (record: COMMON_CUSTOM_ALARM.CustomAlarms) => {
    const { editAlarmRule, deleteAlarmRule } = {
      editAlarmRule: {
        title: i18n.t('Edit'),
        onClick: () => openModal(record.id),
      },
      deleteAlarmRule: {
        title: i18n.t('Delete'),
        onClick: () => handleDeleteAlarm(record.id),
      },
    };

    return [editAlarmRule, deleteAlarmRule];
  };

  const handleChange = React.useCallback(
    debounce((value) => {
      updater.searchValue(value);
    }, 1000),
    [],
  );

  return (
    <div className="custom-alarm">
      <TopButtonGroup>
        <Button type="primary" onClick={() => openModal()}>
          {i18n.t('cmp:Add Custom Rule')}
        </Button>
      </TopButtonGroup>
      <ErdaTable
        slot={
          <Input
            size="small"
            className="w-[200px] bg-black-06 border-none ml-0.5"
            allowClear
            prefix={<ErdaIcon size="16" fill={'default-3'} type="search" />}
            onChange={(e) => {
              handleChange(e.target.value);
            }}
            placeholder={i18n.t('search by {name}', { name: i18n.t('Name').toLowerCase() })}
          />
        }
        loading={getCustomAlarmsLoading || switchCustomAlarmLoading}
        dataSource={customAlarms}
        columns={columns}
        rowKey="id"
        onChange={handlePageChange}
        pagination={{ current: pageNo, pageSize, total }}
        actions={actions}
      />
      <FormModal
        name={i18n.t('cmp:custom rule')}
        loading={getCustomAlarmDetailLoading || extraLoading}
        visible={modalVisible}
        width={1200}
        modalProps={{ bodyStyle: { height: '550px', overflow: 'auto' } }}
        PureForm={CustomAlarmForm}
        formData={activedFormData}
        customRender={customRender}
        onOk={handleUpdateCustomAlarm}
        beforeSubmit={beforeSubmit}
        onCancel={closeModal}
      />
    </div>
  );
}
Example #27
Source File: sst-backfill.ts    From aqualink-app with MIT License 4 votes vote down vote up
async function main() {
  const connection = await createConnection(dbConfig);
  const siteRepository = connection.getRepository(Site);
  const dailyDataRepository = connection.getRepository(DailyData);
  const sourcesRepository = connection.getRepository(Sources);
  const timeSeriesRepository = connection.getRepository(TimeSeries);
  const selectedSites = await siteRepository.find({
    where:
      sitesToProcess.length > 0
        ? {
            id: In(sitesToProcess),
          }
        : {},
  });

  const dailyDataEntities = selectedSites.reduce(
    (entities: DailyData[], site) => {
      console.log(`Processing site ${site.name || site.id}...`);
      const allYearEntities = yearsArray.reduce(
        (yearEntities: DailyData[], year) => {
          console.log(`Processing year ${year}...`);
          const [longitude, latitude] = (site.polygon as Point).coordinates;
          const data = getNOAAData(year, longitude, latitude);
          const yearEntitiesForSite = data.map(
            ({ date, satelliteTemperature }) =>
              ({
                site: { id: site.id },
                date,
                satelliteTemperature,
              } as DailyData),
          );
          return yearEntities.concat(yearEntitiesForSite);
        },
        [],
      );
      return entities.concat(allYearEntities);
    },
    [],
  );

  const sources = await Promise.all(
    selectedSites.map((site) => {
      return getNOAASource(site, sourcesRepository);
    }),
  );

  const siteToSource: Record<number, Sources> = keyBy(
    sources,
    (source) => source.site.id,
  );

  await Bluebird.map(dailyDataEntities, async (entity) => {
    try {
      await dailyDataRepository.save(entity);
    } catch (err) {
      if (err.constraint === 'no_duplicated_date') {
        console.debug(
          `Data already exists for this date ${entity.date.toDateString()}`,
        );
      } else {
        console.error(err);
      }
    }

    if (!entity.satelliteTemperature) {
      return;
    }

    await insertSiteDataToTimeSeries(
      [
        {
          value: entity.satelliteTemperature,
          timestamp: entity.date.toISOString(),
        },
      ],
      Metric.SATELLITE_TEMPERATURE,
      siteToSource[entity.site.id],
      timeSeriesRepository,
    );
  });

  // Update materialized view
  console.log('Refreshing materialized view latest_data');
  await connection.query('REFRESH MATERIALIZED VIEW latest_data');

  connection.close();
  process.exit(0);
}
Example #28
Source File: message-repository.spec.ts    From linkedin-private-api with MIT License 4 votes vote down vote up
describe('getMessages', () => {
  const reqParams = {
    keyVersion: 'LEGACY_INBOX',
  };

  it('should fetch first page of messages', async () => {
    const { response, resultMessages } = createGetMessagesResponse(10);
    const keyedConversations = keyBy(resultMessages, 'entityUrn');

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(10);
    messages.forEach((message: any) => {
      expect(omit(message, ['text', 'sentFrom'])).toEqual(keyedConversations[message.entityUrn]);
    });
  });

  it('should sort messages by createdAt descending', async () => {
    const { response } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(10);
    expect(messages).toEqual(orderBy(messages, 'createdAt', 'desc'));
  });

  it('should fetch first page of messages before specific date', async () => {
    const now = new Date();
    const { response, resultMessages } = createGetMessagesResponse(10);
    const keyedConversations = keyBy(resultMessages, 'entityUrn');

    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: now.getTime() } })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId, createdBefore: now });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(10);
    messages.forEach((message: any) => {
      expect(omit(message, ['text', 'sentFrom'])).toEqual(keyedConversations[message.entityUrn]);
    });
  });

  it('should add the text message in the root of the result ', async () => {
    const { response, resultMessages } = createGetMessagesResponse(1);
    const mockedResultMessage = resultMessages[0];

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(1);
    expect(messages[0].text).toEqual(mockedResultMessage.eventContent?.attributedBody.text);
  });

  it('should add sentFrom on the result', async () => {
    const { response, resultMessages, resultProfiles } = createGetMessagesResponse(1);
    const profileId = resultProfiles[0].entityUrn.replace('urn:li:fs_miniProfile:', '');

    const participantId = `urn:li:fs_messagingMember:(${faker.datatype.number()},${profileId})`;
    resultMessages[0]['*from'] = participantId;

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages[0].sentFrom.entityUrn).toEqual(resultProfiles[0].entityUrn);
    expect(messages[0].sentFrom.profileId).toEqual(profileId);
  });

  it('should be able to scroll messages using scroller', async () => {
    const { response: firstPageResponse, resultMessages: firstPageMockedMessages } = createGetMessagesResponse(10);
    const { response: secondPageResponse, resultMessages: secondPageMockedMessages } = createGetMessagesResponse(10);
    const { response: thirdPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[9].createdAt } })).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: secondPageMockedMessages[9].createdAt } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const firstPageMessages = await messagesScroller.scrollNext();
    const secondPageMessages = await messagesScroller.scrollNext();
    const thirdPageMessages = await messagesScroller.scrollNext();

    expect(firstPageMessages.length).toEqual(10);
    expect(secondPageMessages.length).toEqual(10);
    expect(thirdPageMessages.length).toEqual(10);

    expect(firstPageMessages).not.toEqual(secondPageMessages);
    expect(firstPageMessages).not.toEqual(thirdPageMessages);
    expect(secondPageMessages).not.toEqual(thirdPageMessages);

    [...firstPageMessages, ...firstPageMessages, ...thirdPageMessages].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be able to restart scroller position', async () => {
    const { response: firstPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });

    const firstPageMessages = await messagesScroller.scrollNext();

    messagesScroller.restart();

    const secondPageMessages = await messagesScroller.scrollNext();

    expect(firstPageMessages.length).toEqual(10);
    expect(firstPageMessages).toEqual(secondPageMessages);

    firstPageMessages.forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be able to override scroller starting point', async () => {
    const createdBefore = new Date();

    const { response: firstPageResponse, resultMessages: firstPageMockedMessages } = createGetMessagesResponse(10);
    const { response: secondPageResponse, resultMessages: secondPageMockedMessages } = createGetMessagesResponse(10);
    const { response: thirdPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: createdBefore.getTime() } })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[9].createdAt } })).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: secondPageMockedMessages[9].createdAt } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId, createdBefore });
    const firstPageMessages = await messagesScroller.scrollNext();
    const secondPageMessages = await messagesScroller.scrollNext();
    const thirdPageMessages = await messagesScroller.scrollNext();

    expect(firstPageMessages.length).toEqual(10);
    expect(secondPageMessages.length).toEqual(10);
    expect(thirdPageMessages.length).toEqual(10);

    expect(firstPageMessages).not.toEqual(secondPageMessages);
    expect(firstPageMessages).not.toEqual(thirdPageMessages);
    expect(secondPageMessages).not.toEqual(thirdPageMessages);

    [...firstPageMessages, ...firstPageMessages, ...thirdPageMessages].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be able to scroll to previous response pages', async () => {
    const { response: firstPageResponse, resultMessages: firstPageMockedMessages } = createGetMessagesResponse(10);
    const { response: secondPageResponse, resultMessages: secondPageMockedMessages } = createGetMessagesResponse(10);
    const { response: thirdPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });
    when(
      axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[0].createdAt! + 1000 } }),
    ).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[9].createdAt } }), {
      times: 2,
    }).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: secondPageMockedMessages[9].createdAt } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const firstPageMessages = await messagesScroller.scrollNext();
    const secondPageMessages = await messagesScroller.scrollNext();
    const thirdPageMessages = await messagesScroller.scrollNext();
    const fourthPageMessages = await messagesScroller.scrollBack();
    const fifthPageMessages = await messagesScroller.scrollBack();

    expect(firstPageMessages.length).toEqual(10);
    expect(secondPageMessages.length).toEqual(10);
    expect(thirdPageMessages.length).toEqual(10);

    expect(firstPageMessages).not.toEqual(secondPageMessages);
    expect(firstPageMessages).not.toEqual(thirdPageMessages);
    expect(secondPageMessages).not.toEqual(thirdPageMessages);
    expect(fourthPageMessages).toEqual(secondPageMessages);
    expect(fifthPageMessages).toEqual(firstPageMessages);

    [...firstPageMessages, ...firstPageMessages, ...thirdPageMessages].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be return empty array if trying to scroll back from the starting point', async () => {
    const { response: firstPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    await messagesScroller.scrollNext();
    const messages = await messagesScroller.scrollBack();

    expect(messages).toEqual([]);
  });
});
Example #29
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;
}