Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.6.0",
"version": "1.7.0",
"main": "index.ts",
"license": "BUSL-1.1",
"scripts": {
Expand Down
46 changes: 17 additions & 29 deletions src/models/eventsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createProjectEventsByIdLoader } from '../dataLoaders';
import RedisHelper from '../redisHelper';
import ChartDataService from '../services/chartDataService';
import { getMidnightWithTimezoneOffset, getUTCMidnight } from '../utils/dates';
import { normalizeReleaseName } from '../utils/releaseName';

const Factory = require('./modelFactory');
const mongo = require('../mongo');
Expand Down Expand Up @@ -770,10 +771,7 @@ class EventsFactory extends Factory {
for (const repetition of repetitions) {
const event = this._composeEventWithRepetition(eventOriginal, repetition);

result.repetitions.push({
...event,
projectId: this.projectId,
});
result.repetitions.push(event);
}

const isLastPortion = result.nextCursor === null;
Expand All @@ -787,12 +785,7 @@ class EventsFactory extends Factory {
* Get only 'repetitions' fields from event to fit Repetition scheme
* @type {EventRepetitionSchema}
*/
const firstRepetition = {
...eventOriginal,
originalTimestamp: eventOriginal.timestamp,
originalEventId: eventOriginal._id,
projectId: this.projectId,
};
const firstRepetition = this._composeEventWithRepetition(eventOriginal);

result.repetitions.push(firstRepetition);
}
Expand All @@ -814,14 +807,7 @@ class EventsFactory extends Factory {
if (repetitionId.toString() === originalEventId.toString()) {
const originalEvent = await this.eventsDataLoader.load(originalEventId);

/**
* All events have same type with originalEvent id
*/
originalEvent.originalEventId = originalEventId;
originalEvent.originalTimestamp = originalEvent.timestamp;
originalEvent.projectId = this.projectId;

return originalEvent || null;
return originalEvent ? this._composeEventWithRepetition(originalEvent) : null;
}

/**
Expand Down Expand Up @@ -1104,23 +1090,25 @@ class EventsFactory extends Factory {
* @returns {Event} event merged with repetition
*/
_composeEventWithRepetition(event, repetition) {
const original = {
...event,
originalTimestamp: event.timestamp,
originalEventId: event._id,
firstSeenInRelease: normalizeReleaseName(event.payload && event.payload.release),
resolvedInRelease: normalizeReleaseName(event.resolvedInRelease),
regressionInRelease: normalizeReleaseName(event.regressionInRelease),
projectId: this.projectId,
};

if (!repetition) {
return {
...event,
originalTimestamp: event.timestamp,
originalEventId: event._id,
projectId: this.projectId,
};
return original;
}

return {
...event,
...original,
_id: repetition._id,
originalTimestamp: event.timestamp,
originalEventId: event._id,
timestamp: repetition.timestamp,
payload: composeEventPayloadByRepetition(event.payload, repetition),
projectId: this.projectId,
payload: composeEventPayloadByRepetition(event.payload == null ? {} : event.payload, repetition),
};
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/resolvers/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
const { askAiService } = require('../services/askAi');
const { UserInputError } = require('apollo-server-express');
const { ObjectId } = require('mongodb');
const { normalizeReleaseName } = require('../utils/releaseName');

/**
* See all types and fields here {@see ../typeDefs/event.graphql}
Expand All @@ -23,6 +24,31 @@ module.exports = {
},
},
Event: {
/**
* Returns the original release, preserving null for repetitions of legacy events.
*
* @param {Object} event - original event or composed repetition
* @returns {string|null}
*/
firstSeenInRelease({ firstSeenInRelease, payload }) {
return normalizeReleaseName(firstSeenInRelease !== undefined ? firstSeenInRelease : payload && payload.release);
},

/**
* @param {Object} event - event with stored lifecycle metadata
* @returns {string|null} normalized resolution release
*/
resolvedInRelease({ resolvedInRelease }) {
return normalizeReleaseName(resolvedInRelease);
},

/**
* @param {Object} event - event with stored lifecycle metadata
* @returns {string|null} normalized regression release
*/
regressionInRelease({ regressionInRelease }) {
return normalizeReleaseName(regressionInRelease);
},

/**
* Returns repetitions portion of the event
Expand Down
15 changes: 15 additions & 0 deletions src/typeDefs/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,21 @@ type Event {
"""
release: Release

"""
Release of the original event, regardless of the selected repetition
"""
firstSeenInRelease: String

"""
Release in which the event was resolved
"""
resolvedInRelease: String

"""
Release in which the event regressed
"""
regressionInRelease: String

"""
Event repetitions portion
"""
Expand Down
19 changes: 19 additions & 0 deletions src/utils/releaseName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Normalize absent or invalid release names without changing opaque identifiers.
*
* @param value - raw release name
* @returns the original identifier, a finite number as a string, or null
*/
export function normalizeReleaseName(value: unknown): string | null {
if (typeof value === 'number') {
return Number.isFinite(value) ? String(value) : null;
}

if (typeof value !== 'string') {
return null;
}

const name = value.trim().toLowerCase();

return name === '' || name === 'null' || name === 'undefined' || name === 'nan' ? null : value;
}
Loading
Loading