Commit d652fffc authored by Mark Florian's avatar Mark Florian

Merge branch '228742-convert-history-notes-to-graphql' into 'master'

Fetch discussions using GraphQL

See merge request gitlab-org/gitlab!68180
parents 5e040296 02de2063
query vulnerabilityDiscussions(
$id: VulnerabilityID!
$after: String
$before: String
$first: Int
$last: Int
) {
vulnerability(id: $id) {
id
discussions(after: $after, before: $before, first: $first, last: $last) {
nodes {
id
replyId
}
}
}
}
<script> <script>
import { GlIcon } from '@gitlab/ui'; import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Api from 'ee/api'; import Api from 'ee/api';
import vulnerabilityDiscussionsQuery from 'ee/security_dashboard/graphql/queries/vulnerability_discussions.query.graphql';
import MergeRequestNote from 'ee/vue_shared/security_reports/components/merge_request_note.vue'; import MergeRequestNote from 'ee/vue_shared/security_reports/components/merge_request_note.vue';
import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue'; import SolutionCard from 'ee/vue_shared/security_reports/components/solution_card.vue';
import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants'; import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { TYPE_VULNERABILITY } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll'; import Poll from '~/lib/utils/poll';
...@@ -27,6 +30,7 @@ export default { ...@@ -27,6 +30,7 @@ export default {
HistoryEntry, HistoryEntry,
RelatedIssues, RelatedIssues,
RelatedJiraIssues, RelatedJiraIssues,
GlLoadingIcon,
GlIcon, GlIcon,
StatusDescription, StatusDescription,
}, },
...@@ -44,14 +48,53 @@ export default { ...@@ -44,14 +48,53 @@ export default {
}, },
data() { data() {
return { return {
discussionsDictionary: {}, notesLoading: true,
discussions: [],
lastFetchedAt: null, lastFetchedAt: null,
}; };
}, },
computed: { apollo: {
discussions() { discussions: {
return Object.values(this.discussionsDictionary); query: vulnerabilityDiscussionsQuery,
variables() {
return { id: convertToGraphQLId(TYPE_VULNERABILITY, this.vulnerability.id) };
},
update: ({ vulnerability }) => {
if (!vulnerability) {
return [];
}
return vulnerability.discussions.nodes.map((d) => ({ ...d, notes: [] }));
},
result({ error }) {
if (!this.poll && !error) {
this.createNotesPoll();
if (!Visibility.hidden()) {
this.poll.makeRequest();
}
Visibility.change(() => {
if (Visibility.hidden()) {
this.poll.stop();
} else {
this.poll.restart();
}
});
}
},
error() {
this.notesLoading = false;
createFlash({
message: s__(
'VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later.',
),
});
},
}, },
},
computed: {
noteDictionary() { noteDictionary() {
return this.discussions return this.discussions
.flatMap((x) => x.notes) .flatMap((x) => x.notes)
...@@ -94,56 +137,19 @@ export default { ...@@ -94,56 +137,19 @@ export default {
}; };
}, },
}, },
created() {
this.fetchDiscussions();
},
updated() { updated() {
this.$nextTick(() => { this.$nextTick(() => {
initUserPopovers(this.$el.querySelectorAll('.js-user-link')); initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
}); });
}, },
beforeDestroy() { beforeDestroy() {
if (this.poll) this.poll.stop(); if (this.poll) {
this.poll.stop();
}
}, },
methods: { methods: {
dateToSeconds(date) { findDiscussion(id) {
return Date.parse(date) / 1000; return this.discussions.find((d) => d.id === id);
},
fetchDiscussions() {
// note: this direct API call will be replaced when migrating the vulnerability details page to GraphQL
// related epic: https://gitlab.com/groups/gitlab-org/-/epics/3657
axios
.get(this.vulnerability.discussionsUrl)
.then(({ data, headers: { date } }) => {
this.discussionsDictionary = data.reduce((acc, discussion) => {
acc[discussion.id] = convertObjectPropsToCamelCase(discussion, { deep: true });
return acc;
}, {});
this.lastFetchedAt = this.dateToSeconds(date);
if (!this.poll) this.createNotesPoll();
if (!Visibility.hidden()) {
// delays the initial request by 6 seconds
this.poll.makeDelayedRequest(6 * 1000);
}
Visibility.change(() => {
if (Visibility.hidden()) {
this.poll.stop();
} else {
this.poll.restart();
}
});
})
.catch(() => {
createFlash({
message: s__(
'VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later.',
),
});
});
}, },
createNotesPoll() { createNotesPoll() {
// note: this polling call will be replaced when migrating the vulnerability details page to GraphQL // note: this polling call will be replaced when migrating the vulnerability details page to GraphQL
...@@ -159,48 +165,46 @@ export default { ...@@ -159,48 +165,46 @@ export default {
successCallback: ({ data: { notes, last_fetched_at: lastFetchedAt } }) => { successCallback: ({ data: { notes, last_fetched_at: lastFetchedAt } }) => {
this.updateNotes(convertObjectPropsToCamelCase(notes, { deep: true })); this.updateNotes(convertObjectPropsToCamelCase(notes, { deep: true }));
this.lastFetchedAt = lastFetchedAt; this.lastFetchedAt = lastFetchedAt;
this.notesLoading = false;
}, },
errorCallback: () => errorCallback: () => {
this.notesLoading = false;
createFlash({ createFlash({
message: __('Something went wrong while fetching latest comments.'), message: __('Something went wrong while fetching latest comments.'),
}), });
},
}); });
}, },
updateNotes(notes) { updateNotes(notes) {
let isVulnerabilityStateChanged = false; let shallEmitVulnerabilityChangedEvent;
notes.forEach((note) => { notes.forEach((note) => {
const discussion = this.findDiscussion(note.discussionId);
// If the note exists, update it. // If the note exists, update it.
if (this.noteDictionary[note.id]) { if (this.noteDictionary[note.id]) {
const updatedDiscussion = { ...this.discussionsDictionary[note.discussionId] }; discussion.notes = discussion.notes.map((curr) => (curr.id === note.id ? note : curr));
updatedDiscussion.notes = updatedDiscussion.notes.map((curr) =>
curr.id === note.id ? note : curr,
);
this.discussionsDictionary[note.discussionId] = updatedDiscussion;
} }
// If the note doesn't exist, but the discussion does, add the note to the discussion. // If the note doesn't exist, but the discussion does, add the note to the discussion.
else if (this.discussionsDictionary[note.discussionId]) { else if (discussion) {
const updatedDiscussion = { ...this.discussionsDictionary[note.discussionId] }; discussion.notes.push(note);
updatedDiscussion.notes.push(note);
this.discussionsDictionary[note.discussionId] = updatedDiscussion;
} }
// If the discussion doesn't exist, create it. // If the discussion doesn't exist, create it.
else { else {
const newDiscussion = { this.discussions.push({
id: note.discussionId, id: note.discussionId,
replyId: note.discussionId, replyId: note.discussionId,
notes: [note], notes: [note],
}; });
this.$set(this.discussionsDictionary, newDiscussion.id, newDiscussion);
// If the vulnerability status has changed, the note will be a system note. // If the vulnerability status has changed, the note will be a system note.
// Emit an event that tells the header to refresh the vulnerability.
if (note.system === true) { if (note.system === true) {
isVulnerabilityStateChanged = true; shallEmitVulnerabilityChangedEvent = true;
} }
} }
}); });
// Emit an event that tells the header to refresh the vulnerability.
if (isVulnerabilityStateChanged) { if (shallEmitVulnerabilityChangedEvent) {
this.$emit('vulnerability-state-change'); this.$emit('vulnerability-state-change');
} }
}, },
...@@ -243,7 +247,8 @@ export default { ...@@ -243,7 +247,8 @@ export default {
</div> </div>
</div> </div>
<hr /> <hr />
<ul v-if="discussions.length" ref="historyList" class="notes discussion-body"> <gl-loading-icon v-if="notesLoading" />
<ul v-else-if="discussions.length" class="notes discussion-body">
<history-entry <history-entry
v-for="discussion in discussions" v-for="discussion in discussions"
:key="discussion.id" :key="discussion.id"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment