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 |
/**
* 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 |
/**
* 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
/**
* 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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;
}