From 15c9db446f457e72e5df8ca139002daba569da8e Mon Sep 17 00:00:00 2001 From: alisawavezen12 Date: Wed, 28 Oct 2026 16:37:53 +0300 Subject: [PATCH 1/8] Add event release lifecycle status and details --- package.json | 2 +- schema.graphql | 9 ++ src/api/fragments.ts | 3 + src/components/event/EventHeader.vue | 12 ++ .../event/EventReleaseStatus.test.ts | 91 ++++++++++++ src/components/event/EventReleaseStatus.vue | 81 ++++++++++ src/components/event/Overview.vue | 15 +- .../event/details/DetailsRelease.test.ts | 138 ++++++++++++++++++ .../event/details/DetailsRelease.vue | 54 ++++--- src/components/project/EventItem.vue | 13 ++ src/i18n/messages/en.json | 10 ++ src/i18n/messages/ru.json | 10 ++ src/types/events.ts | 15 ++ vitest.config.ts | 17 ++- 14 files changed, 442 insertions(+), 28 deletions(-) create mode 100644 src/components/event/EventReleaseStatus.test.ts create mode 100644 src/components/event/EventReleaseStatus.vue create mode 100644 src/components/event/details/DetailsRelease.test.ts diff --git a/package.json b/package.json index 226209634..7d7731979 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.so", - "version": "1.3.1", + "version": "1.3.2", "private": true, "packageManager": "yarn@4.18.0", "scripts": { diff --git a/schema.graphql b/schema.graphql index 34e34b328..4c3b0c056 100644 --- a/schema.graphql +++ b/schema.graphql @@ -392,6 +392,15 @@ type Event { """Release data""" 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""" repetitionsPortion(cursor: String = null, limit: Int = 10): RepetitionsPortion! diff --git a/src/api/fragments.ts b/src/api/fragments.ts index a3b0b4d81..a65795382 100644 --- a/src/api/fragments.ts +++ b/src/api/fragments.ts @@ -55,6 +55,9 @@ export const EVENT_FRAGMENT = ` timestamp originalTimestamp originalEventId + firstSeenInRelease + resolvedInRelease + regressionInRelease payload { title type diff --git a/src/components/event/EventHeader.vue b/src/components/event/EventHeader.vue index 652d6a469..20c307de3 100644 --- a/src/components/event/EventHeader.vue +++ b/src/components/event/EventHeader.vue @@ -58,6 +58,11 @@ {{ !loading ? event.payload.type || 'Application error' : $t('event.loading') }} +

{{ trimmedTitle }}

@@ -158,6 +163,7 @@ import { JavaScriptAddons } from '@hawk.so/types'; import { Button as CdxButton, usePopover } from '@codexteam/ui/vue'; import EventActionsMenu from './EventActionsMenu.vue'; import AiSuggestionDialog from '../modals/AiSuggestionDialog.vue'; +import EventReleaseStatus from './EventReleaseStatus.vue'; export default defineComponent({ name: 'EventHeader', @@ -171,6 +177,7 @@ export default defineComponent({ EntityImage, ProjectBadge, AiSuggestionDialog, + EventReleaseStatus, }, mixins: [projectBadges], props: { @@ -418,6 +425,11 @@ export default defineComponent({ letter-spacing: 0.21px; } + &__release-status { + max-width: 100%; + margin-top: 10px; + } + &__title { margin: 10px 0 15px; overflow: hidden; diff --git a/src/components/event/EventReleaseStatus.test.ts b/src/components/event/EventReleaseStatus.test.ts new file mode 100644 index 000000000..776026c16 --- /dev/null +++ b/src/components/event/EventReleaseStatus.test.ts @@ -0,0 +1,91 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { createApp, h, nextTick, reactive, type App } from 'vue'; +import { createI18n } from 'vue-i18n'; +import en from '../../i18n/messages/en.json'; +import ru from '../../i18n/messages/ru.json'; +import type { HawkEvent } from '../../types/events'; +import EventReleaseStatus from './EventReleaseStatus.vue'; + +type ReleaseState = Pick; + +let app: App | undefined; +let host: HTMLDivElement; + +afterEach(() => { + app?.unmount(); + host.remove(); +}); + +/** + * Mount the badge with the real translations and reactive event state. + * @param event - lifecycle fields from the event + * @param locale - UI language + * @returns badge container + */ +function mount(event: ReleaseState, locale = 'en'): HTMLDivElement { + host = document.createElement('div'); + document.body.append(host); + app = createApp({ render: () => h(EventReleaseStatus, { event }) }); + app.use(createI18n({ legacy: false, + locale, + messages: { en, + ru } })); + app.mount(host); + + return host; +} + +describe('EventReleaseStatus', () => { + it.each([ + {}, + { firstSeenInRelease: null, + resolvedInRelease: null, + regressionInRelease: null }, + ])('hides the badge without lifecycle data: %j', (event) => { + expect(mount(event).querySelector('.event-release-status')).toBeNull(); + expect(host.textContent).toBe(''); + }); + + it.each([ + ['en', 'firstSeen', { firstSeenInRelease: 'build/start#1' }, 'First seen in build/start#1'], + ['ru', 'firstSeen', { firstSeenInRelease: 'build/start#1' }, 'Впервые в build/start#1'], + ['en', 'resolved', { firstSeenInRelease: 'z-original', + resolvedInRelease: 'a-fixed' }, 'Resolved in a-fixed'], + ['ru', 'resolved', { firstSeenInRelease: 'z-original', + resolvedInRelease: 'a-fixed' }, 'Исправлено в a-fixed'], + ['en', 'regression', { firstSeenInRelease: 'z-original', + resolvedInRelease: 'z-fixed', + regressionInRelease: 'a/again#2' }, 'Regression in a/again#2'], + ['ru', 'regression', { firstSeenInRelease: 'z-original', + resolvedInRelease: 'z-fixed', + regressionInRelease: 'a/again#2' }, 'Регрессия в a/again#2'], + ] as const)('renders %s %s with lifecycle precedence, not release sorting', (locale, status, event, label) => { + const badge = mount(event, locale).querySelector('.event-release-status'); + + expect(badge?.textContent?.trim()).toBe(label); + expect(badge?.getAttribute('title')).toBe(label); + expect(badge?.classList.contains(`event-release-status--${status}`)).toBe(true); + expect(host.querySelectorAll('.event-release-status')).toHaveLength(1); + }); + + it('returns to resolved when a regression is cleared reactively', async () => { + const event = reactive({ + firstSeenInRelease: 'original', + resolvedInRelease: 'z-fixed', + regressionInRelease: 'a-regression', + }); + + mount(event); + expect(host.textContent?.trim()).toBe('Regression in a-regression'); + + event.regressionInRelease = null; + await nextTick(); + + const badge = host.querySelector('.event-release-status'); + + expect(badge?.textContent?.trim()).toBe('Resolved in z-fixed'); + expect(badge?.getAttribute('title')).toBe('Resolved in z-fixed'); + expect(badge?.classList.contains('event-release-status--resolved')).toBe(true); + expect(badge?.classList.contains('event-release-status--regression')).toBe(false); + }); +}); diff --git a/src/components/event/EventReleaseStatus.vue b/src/components/event/EventReleaseStatus.vue new file mode 100644 index 000000000..45ff472f7 --- /dev/null +++ b/src/components/event/EventReleaseStatus.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/components/event/Overview.vue b/src/components/event/Overview.vue index 3560fbee5..93fbc0582 100644 --- a/src/components/event/Overview.vue +++ b/src/components/event/Overview.vue @@ -79,9 +79,12 @@ :addons="addonsFiltered" />
0; }, + /** + * Whether any occurrence or lifecycle release is available. + */ + hasReleaseInformation(): boolean { + return Boolean(this.event.payload.release || this.event.firstSeenInRelease + || this.event.resolvedInRelease || this.event.regressionInRelease); + }, + /** * Returns true if the event has no backtrace, context and addons * @@ -232,7 +243,7 @@ export default defineComponent({ const noContext = !this.hasContext; const noBreadcrumbs = !this.hasBreadcrumbs; - return noBacktrace && noAddons && noContext && noBreadcrumbs; + return noBacktrace && noAddons && noContext && noBreadcrumbs && !this.hasReleaseInformation; }, }, methods: { diff --git a/src/components/event/details/DetailsRelease.test.ts b/src/components/event/details/DetailsRelease.test.ts new file mode 100644 index 000000000..99f632e24 --- /dev/null +++ b/src/components/event/details/DetailsRelease.test.ts @@ -0,0 +1,138 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { createApp, h, type App } from 'vue'; +import { createI18n } from 'vue-i18n'; +import { createMemoryHistory, createRouter, type Router } from 'vue-router'; +import en from '../../../i18n/messages/en.json'; +import ru from '../../../i18n/messages/ru.json'; +import DetailsRelease from './DetailsRelease.vue'; + +type ReleaseProps = Partial>; + +let app: App | undefined; +let host: HTMLDivElement; + +afterEach(() => { + app?.unmount(); + host.remove(); +}); + +/** + * Use a memory router to check real link encoding without loading the app routes. + * @param props - payload and lifecycle releases + * @param locale - UI language + * @returns router for resolving rendered links + */ +async function mount(props: ReleaseProps, locale = 'en'): Promise { + const router = createRouter({ + history: createMemoryHistory(), + routes: [ + { path: '/project/:projectId/events/:eventId', + name: 'event', + component: DetailsRelease }, + { path: '/project/:projectId/releases/:release', + name: 'project-release', + component: { render: () => null } }, + ], + }); + + await router.push('/project/project-42/events/event-1'); + await router.isReady(); + + host = document.createElement('div'); + document.body.append(host); + app = createApp({ render: () => h(DetailsRelease, props) }); + app.use(createI18n({ legacy: false, + locale, + messages: { en, + ru } })); + app.use(router); + app.mount(host); + + return router; +} + +/** + * Read visible row labels and release values rather than component internals. + * @returns rendered release rows + */ +function rows(): (string | undefined)[][] { + return Array.from(host.querySelectorAll('.event-details__content-block'), row => [ + row.querySelector('.event-details__key')?.textContent?.trim(), + row.querySelector('a')?.textContent?.trim(), + ]); +} + +describe('DetailsRelease', () => { + it.each([ + ['en', 'Release', 'First seen in release', 'Resolved in release', 'Regressed in release'], + ['ru', 'Релиз', 'Впервые в релизе', 'Исправлено в релизе', 'Регрессия в релизе'], + ])('preserves every %s lifecycle link and the selected repetition release', async (locale, releaseLabel, firstSeenLabel, resolvedLabel, regressionLabel) => { + const releases = ['original/build#1', 'selected/build#2', 'fixed/build#3', 'again/build#4']; + const router = await mount({ + release: releases[1], + firstSeenInRelease: releases[0], + resolvedInRelease: releases[2], + regressionInRelease: releases[3], + }, locale); + + expect(rows()).toEqual([ + [firstSeenLabel, releases[0]], + [releaseLabel, releases[1]], + [resolvedLabel, releases[2]], + [regressionLabel, releases[3]], + ]); + + const links = host.querySelectorAll('a'); + + expect(links).toHaveLength(releases.length); + links.forEach((link, index) => { + const release = releases[index]; + const href = link.getAttribute('href'); + + expect(link.getAttribute('title')).toBe(release); + expect(href).toBe(`/project/project-42/releases/${encodeURIComponent(release)}`); + const target = router.resolve(href!); + + expect(target.name).toBe('project-release'); + expect(target.params).toEqual({ projectId: 'project-42', + release }); + expect(target.hash).toBe(''); + }); + }); + + it('does not duplicate the payload release when it is the first-seen release', async () => { + await mount({ + release: 'original', + firstSeenInRelease: 'original', + resolvedInRelease: 'fixed', + regressionInRelease: 'again', + }); + + expect(rows()).toEqual([ + ['First seen in release', 'original'], + ['Resolved in release', 'fixed'], + ['Regressed in release', 'again'], + ]); + }); + + it('shows a legacy payload release without lifecycle data', async () => { + await mount({ release: 'legacy/build#1' }); + + expect(rows()).toEqual([['Release', 'legacy/build#1']]); + expect(host.querySelector('a')?.getAttribute('href')).toBe('/project/project-42/releases/legacy%2Fbuild%231'); + }); + + it('shows lifecycle releases without a payload release', async () => { + await mount({ + firstSeenInRelease: 'original', + resolvedInRelease: 'fixed', + regressionInRelease: 'again', + }); + + expect(rows()).toEqual([ + ['First seen in release', 'original'], + ['Resolved in release', 'fixed'], + ['Regressed in release', 'again'], + ]); + }); +}); diff --git a/src/components/event/details/DetailsRelease.vue b/src/components/event/details/DetailsRelease.vue index 73c9112e9..65236775b 100644 --- a/src/components/event/details/DetailsRelease.vue +++ b/src/components/event/details/DetailsRelease.vue @@ -4,17 +4,21 @@ {{ $t("event.release") }} @@ -360,7 +364,7 @@ export default defineComponent({ white-space: nowrap; } - tr:not(:first-child){ + tbody tr { cursor: pointer; &:hover { diff --git a/src/components/event/details/DetailsRelease.test.ts b/src/components/event/details/DetailsRelease.test.ts index fa3b06664..44de309f0 100644 --- a/src/components/event/details/DetailsRelease.test.ts +++ b/src/components/event/details/DetailsRelease.test.ts @@ -64,8 +64,8 @@ function rows(): (string | undefined)[][] { describe('DetailsRelease', () => { it.each([ - ['en', 'Occurred in', 'Introduced in', 'Potentially resolved in', 'Regressed in'], - ['ru', 'Произошло в', 'Появилось в', 'Потенциально исправлено в', 'Регрессия в'], + ['en', 'Current occurrence in', 'Introduced in', 'Potentially resolved in', 'Regressed in'], + ['ru', 'Текущее событие в', 'Появилось в', 'Потенциально исправлено в', 'Регрессия в'], ])('preserves every %s lifecycle link and the selected repetition release', async (locale, releaseLabel, firstSeenLabel, resolvedLabel, regressionLabel) => { const releases = ['original/build#1', 'selected/build#2', 'fixed/build#3', 'again/build#4']; const router = await mount({ @@ -102,7 +102,7 @@ describe('DetailsRelease', () => { }); }); - it('does not duplicate the payload release when it is the first-seen release', async () => { + it('keeps the current occurrence even when its release matches the first appearance', async () => { await mount({ release: 'original', firstSeenInRelease: 'original', @@ -112,12 +112,13 @@ describe('DetailsRelease', () => { expect(rows()).toEqual([ ['Introduced in', 'original'], + ['Current occurrence in', 'original'], ['Potentially resolved in', 'fixed'], ['Regressed in', 'again'], ]); }); - it('shows a regressed repetition once under the regression label', async () => { + it('keeps the current occurrence even when its release matches the regression', async () => { await mount({ release: 'again', firstSeenInRelease: 'original', @@ -127,6 +128,7 @@ describe('DetailsRelease', () => { expect(rows()).toEqual([ ['Появилось в', 'original'], + ['Текущее событие в', 'again'], ['Потенциально исправлено в', 'fixed'], ['Регрессия в', 'again'], ]); @@ -138,7 +140,7 @@ describe('DetailsRelease', () => { expect(rows()).toEqual([ ['Introduced in', 'original'], - ['Occurred in', 'repeated'], + ['Current occurrence in', 'repeated'], ]); }); @@ -163,7 +165,7 @@ describe('DetailsRelease', () => { it('shows a legacy payload release without lifecycle data', async () => { await mount({ release: 'legacy/build#1' }); - expect(rows()).toEqual([['Occurred in', 'legacy/build#1']]); + expect(rows()).toEqual([['Current occurrence in', 'legacy/build#1']]); expect(host.querySelector('a')?.getAttribute('href')).toBe('/project/project-42/releases/legacy%2Fbuild%231'); }); diff --git a/src/components/event/details/DetailsRelease.vue b/src/components/event/details/DetailsRelease.vue index 90996ca3f..da7550c73 100644 --- a/src/components/event/details/DetailsRelease.vue +++ b/src/components/event/details/DetailsRelease.vue @@ -62,7 +62,7 @@ export default defineComponent({ }, computed: { /** - * Keep the selected repetition's release when it differs from the first occurrence. + * Keep the current occurrence separate from lifecycle milestones, even when their releases match. */ releases(): { label: string; release: string; }[] { @@ -70,9 +70,7 @@ export default defineComponent({ { label: 'event.releaseDetails.firstSeen', release: this.firstSeenInRelease }, { label: 'event.releaseDetails.occurrence', - release: [this.firstSeenInRelease, this.resolvedInRelease, this.regressionInRelease].includes(this.release) - ? null - : this.release }, + release: this.release }, { label: 'event.releaseDetails.resolved', release: this.resolvedInRelease }, { label: 'event.releaseDetails.regression', diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 52bf58604..b2caae649 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -600,7 +600,7 @@ "regression": "The event reappeared in release {release} after it was considered fixed." }, "releaseDetails": { - "occurrence": "Occurred in", + "occurrence": "Current occurrence in", "firstSeen": "Introduced in", "resolved": "Potentially resolved in", "regression": "Regressed in" diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 38fc84f66..37c047fdf 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -594,7 +594,7 @@ "regression": "Событие вернулось в релизе {release} после предполагаемого исправления." }, "releaseDetails": { - "occurrence": "Произошло в", + "occurrence": "Текущее событие в", "firstSeen": "Появилось в", "resolved": "Потенциально исправлено в", "regression": "Регрессия в" From 499a020886aa870da722ab799fa28bf7ef75ebaf Mon Sep 17 00:00:00 2001 From: alisawavezen12 Date: Fri, 30 Oct 2026 02:03:27 +0300 Subject: [PATCH 6/8] Cache project releases in Vuex and fetch lazily Derive the latest release from the cached, timestamp-sorted releases list, independent of event queries. Share concurrent requests and prevent stale requests from repopulating the cache after a store reset. Add coverage for caching, failures, and reset races. --- schema.graphql | 2 - src/api/events/index.ts | 12 +- src/api/events/queries.ts | 2 - src/api/projects/index.js | 2 +- src/components/event/EventHeader.vue | 9 +- src/components/project/EventItem.vue | 8 +- src/components/project/EventsList.vue | 9 +- src/components/project/Releases.vue | 10 +- src/store/modules/events/index.ts | 6 +- src/store/modules/projects/actionTypes.js | 5 + src/store/modules/projects/index.js | 62 +++++ src/store/modules/projects/releases.test.ts | 254 ++++++++++++++++++++ src/types/events.ts | 9 - 13 files changed, 348 insertions(+), 42 deletions(-) create mode 100644 src/store/modules/projects/releases.test.ts diff --git a/schema.graphql b/schema.graphql index 8e76fdba8..fc0770ea1 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1104,8 +1104,6 @@ type Project { """User who created project""" uidAdded: User! - """Most recently created meaningful release, even if no events occurred in it""" - latestRelease: String """Project's Event""" event(eventId: ID!, originalEventId: ID!): Event diff --git a/src/api/events/index.ts b/src/api/events/index.ts index 9b8c333fc..bb10bb10f 100644 --- a/src/api/events/index.ts +++ b/src/api/events/index.ts @@ -74,10 +74,7 @@ export const getEvent = withDemoMock( return null; } - return { - ...project.event, - latestRelease: project.latestRelease ?? null, - }; + return project.event; }, '/src/api/events/mocks/getEvent.mock.ts' ); @@ -124,11 +121,8 @@ export const fetchDailyEventsPortion = withDemoMock( response.errors.forEach(e => console.error(e)); } - return { - ...(project?.dailyEventsPortion ?? { nextCursor: null, - dailyEvents: [] }), - latestRelease: project?.latestRelease ?? null, - }; + return project?.dailyEventsPortion ?? { nextCursor: null, + dailyEvents: [] }; }, '/src/api/events/mocks/fetchDailyEventsPortion.mock.ts' ); diff --git a/src/api/events/queries.ts b/src/api/events/queries.ts index 022c1a695..0b1bbb4ca 100644 --- a/src/api/events/queries.ts +++ b/src/api/events/queries.ts @@ -7,7 +7,6 @@ import { EVENT_FRAGMENT } from '../fragments'; export const QUERY_EVENT = ` query Event($projectId: ID!, $eventId: ID!, $originalEventId: ID!) { project(projectId: $projectId) { - latestRelease event(eventId: $eventId, originalEventId: $originalEventId) { ...Event } @@ -28,7 +27,6 @@ export const QUERY_PROJECT_DAILY_EVENTS = ` $assignee: ID ) { project(projectId: $projectId) { - latestRelease dailyEventsPortion(sort: $sort, filters: $filters, search: $search, release: $release, assignee: $assignee, nextCursor: $cursor) { nextCursor { groupingTimestampBoundary diff --git a/src/api/projects/index.js b/src/api/projects/index.js index 49b8d4a51..59a309ba3 100644 --- a/src/api/projects/index.js +++ b/src/api/projects/index.js @@ -291,7 +291,7 @@ export const fetchProjectReleases = withDemoMock( const response = await api.call(QUERY_PROJECT_RELEASES, { projectId }); if (response.errors?.length) { - response.errors.forEach(console.error); + throw new Error(response.errors.map(error => error.message).join('; ')); } return response.data.project.releases; diff --git a/src/components/event/EventHeader.vue b/src/components/event/EventHeader.vue index 53ae040df..205d5a370 100644 --- a/src/components/event/EventHeader.vue +++ b/src/components/event/EventHeader.vue @@ -61,7 +61,7 @@
@@ -157,6 +157,7 @@ import EntityImage from '../utils/EntityImage.vue'; import { HawkEvent, HawkEventBacktraceFrame } from '@/types/events'; import { TOGGLE_EVENT_MARK } from '@/store/modules/events/actionTypes'; +import { FETCH_PROJECT_RELEASES } from '@/store/modules/projects/actionTypes'; import { Project } from '@/types/project'; import { Workspace } from '@/types/workspaces'; import { projectBadges } from '../../mixins/projectBadges'; @@ -211,7 +212,13 @@ export default defineComponent({ isAiSuggestionOpen: false as boolean, }; }, + created() { + this.$store.dispatch(FETCH_PROJECT_RELEASES, this.projectId).catch(console.error); + }, computed: { + latestRelease(): string | null { + return this.$store.getters.getLatestProjectRelease(this.projectId); + }, /** * Event location got from the first backtrace frame * Or got from a url if the backtrace is empty diff --git a/src/components/project/EventItem.vue b/src/components/project/EventItem.vue index 65ac4850c..7e66f40a4 100644 --- a/src/components/project/EventItem.vue +++ b/src/components/project/EventItem.vue @@ -104,10 +104,7 @@ export default { UiCheckbox, }, props: { - latestRelease: { - type: String, - default: null, - }, + /** * True when at least one row is selected (all rows show checkboxes) */ @@ -156,6 +153,9 @@ export default { }, emits: ['show-event-overview', 'toggle-row-select', 'on-assignee-icon-click'], computed: { + latestRelease() { + return this.$store.getters.getLatestProjectRelease(this.projectId); + }, /** * Return true if user visited current event * diff --git a/src/components/project/EventsList.vue b/src/components/project/EventsList.vue index b74590f74..9ce4a8d42 100644 --- a/src/components/project/EventsList.vue +++ b/src/components/project/EventsList.vue @@ -42,7 +42,7 @@ :affected-users-count="dailyEventInfo.affectedUsers" class="events-list__event" :event="getEvent(dailyEventInfo.eventId)" - :latest-release="latestRelease" + :is-selection-mode-active="selectionModeActive" :is-row-selected="isRowSelected(dailyEventInfo.eventId)" @on-assignee-icon-click="onAssigneeIconClick(dailyEventInfo.eventId, $event)" @@ -99,6 +99,7 @@ import { prettyDate } from '@/utils/filters'; import AssigneesList from '../event/AssigneesList'; import { mapGetters } from 'vuex'; import { FETCH_PROJECT_OVERVIEW } from '../../store/modules/events/actionTypes'; +import { FETCH_PROJECT_RELEASES } from '../../store/modules/projects/actionTypes'; import SearchField from '../forms/SearchField'; import EmptyState from '../utils/EmptyState.vue'; import UiSelect from '../utils/UiSelect.vue'; @@ -193,7 +194,6 @@ export default { * Pagination cursor for next dailyEvents portion */ dailyEventsNextCursor: null, - latestRelease: null, /** * Current search query (controlled input for SearchField) */ @@ -252,6 +252,7 @@ export default { }; }, created() { + this.$store.dispatch(FETCH_PROJECT_RELEASES, this.projectId).catch(console.error); this.loadMoreEvents(true); const SEARCH_MAX_LENGTH = 50; @@ -402,7 +403,7 @@ export default { try { const search = this.getProjectSearch(this.projectId) || ''; - const { nextCursor, dailyEventsWithEventsLinked, latestRelease } = await this.$store.dispatch(FETCH_PROJECT_OVERVIEW, { + const { nextCursor, dailyEventsWithEventsLinked } = await this.$store.dispatch(FETCH_PROJECT_OVERVIEW, { projectId: this.projectId, nextCursor: this.dailyEventsNextCursor, search, @@ -410,7 +411,6 @@ export default { assignee: this.selectedAssigneeId || undefined, }); - this.latestRelease = latestRelease; this.dailyEventsNextCursor = nextCursor; this.noMore = this.dailyEventsNextCursor === null; @@ -437,7 +437,6 @@ export default { this.dailyEventsNextCursor = null; this.noMore = false; this.dailyEvents = []; - this.latestRelease = null; this.loadMoreEvents(true); }, /** diff --git a/src/components/project/Releases.vue b/src/components/project/Releases.vue index 5016f0304..35b7690c9 100644 --- a/src/components/project/Releases.vue +++ b/src/components/project/Releases.vue @@ -122,7 +122,7 @@