diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 922cd3ebddca4e14aa769469f973aa57d251619b..cf7207d260db695daf4b384838fc1f4bbb38019a 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { apolloProvider } from '~/graphql_shared/issuable_client'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; import { parseBoolean } from '~/lib/utils/common_utils'; import { getLocationHash } from '~/lib/utils/url_utility'; import NotesApp from './components/notes_app.vue'; @@ -59,7 +60,7 @@ export default () => { showTimelineViewToggle, reportAbusePath: notesDataset.reportAbusePath, newCommentTemplatePath: notesDataset.newCommentTemplatePath, - issuableId: noteableData.id, + resourceGlobalId: convertToGraphQLId(noteableData.noteableType, noteableData.id), }, data() { return { diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 8e00ff54c72511eede7ea9b27bd47578a062f0ae..4e827d71126f80bd00592025352e5f279e976482 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -39,7 +39,7 @@ export default { newCommentTemplatePath: { default: null, }, - issuableId: { default: null }, + resourceGlobalId: { default: null }, }, props: { previewMarkdown: { @@ -123,7 +123,7 @@ export default { }, showAiActions() { return ( - this.issuableId && + this.resourceGlobalId && this.glFeatures.openaiExperimentation && this.glFeatures.summarizeNotes && this.glFeatures.summarizeComments @@ -280,7 +280,7 @@ export default { </gl-button> </gl-popover> </template> - <ai-actions-dropdown v-if="showAiActions" :issuable-id="issuableId" /> + <ai-actions-dropdown v-if="showAiActions" :resource-global-id="resourceGlobalId" /> <toolbar-button tag="**" :button-title=" diff --git a/ee/app/assets/javascripts/vue_shared/components/markdown/ai_actions_dropdown.vue b/ee/app/assets/javascripts/vue_shared/components/markdown/ai_actions_dropdown.vue index 9e4cbc8a2f9071d8fa38821438b0d85535fb56c6..67c3ed6ea399da478e9ce1467484d3b29ce7ebae 100644 --- a/ee/app/assets/javascripts/vue_shared/components/markdown/ai_actions_dropdown.vue +++ b/ee/app/assets/javascripts/vue_shared/components/markdown/ai_actions_dropdown.vue @@ -1,5 +1,5 @@ <script> -import { GlCollapsibleListbox, GlButton, GlIcon, GlLoadingIcon } from '@gitlab/ui'; +import { GlButton, GlCollapsibleListbox, GlIcon, GlLoadingIcon } from '@gitlab/ui'; import { updateText } from '~/lib/utils/text_markdown'; import { fetchPolicies } from '~/lib/graphql'; import { __ } from '~/locale'; @@ -7,7 +7,7 @@ import { createAlert } from '~/alert'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import aiResponseSubscription from 'ee/graphql_shared/subscriptions/ai_completion_response.subscription.graphql'; import aiActionMutation from 'ee/graphql_shared/mutations/ai_action.mutation.graphql'; -import { TYPENAME_USER, TYPENAME_ISSUE } from '~/graphql_shared/constants'; +import { TYPENAME_USER } from '~/graphql_shared/constants'; export const MAX_REQUEST_TIMEOUT = 1000 * 15; // 15 seconds export const ACTIONS = { @@ -22,15 +22,15 @@ export default { GlIcon, }, props: { - issuableId: { - type: Number, + resourceGlobalId: { + type: String, required: true, }, }, data() { return { loading: false, - error: null, + errorAlert: null, aiCompletionResponse: {}, }; }, @@ -38,7 +38,7 @@ export default { subscriptionVariables() { return { userId: convertToGraphQLId(TYPENAME_USER, gon.current_user_id), - resourceId: convertToGraphQLId(TYPENAME_ISSUE, this.issuableId), + resourceId: this.resourceGlobalId, }; }, }, @@ -57,6 +57,9 @@ export default { variables() { return this.subscriptionVariables; }, + error(error) { + this.handleError(error); + }, result({ data }) { this.loading = false; @@ -65,7 +68,7 @@ export default { } if (data.error) { - this.handleError(); + this.handleError(new Error(data.error)); return; } @@ -93,6 +96,8 @@ export default { return; } + this.errorAlert?.dismiss(); + const input = this.getInputForAction(action); if (!input) { @@ -117,7 +122,7 @@ export default { if (action === ACTIONS.SUMMARIZE_COMMENTS) { return { summarizeComments: { - resourceId: convertToGraphQLId(TYPENAME_ISSUE, this.issuableId), + resourceId: this.resourceGlobalId, }, }; } @@ -125,7 +130,7 @@ export default { }, handleError(error) { const alertOptions = error ? { captureError: true, error } : {}; - createAlert({ message: __('Something went wrong'), ...alertOptions }); + this.errorAlert = createAlert({ message: __('Something went wrong'), ...alertOptions }); this.loading = false; }, }, diff --git a/ee/app/controllers/groups/epics_controller.rb b/ee/app/controllers/groups/epics_controller.rb index 8d4ea7ce2c116e9b1a7fbb6ff329b13c38c8bf5b..a355a7050581bb3299951fb5775b26e4cb4538d6 100644 --- a/ee/app/controllers/groups/epics_controller.rb +++ b/ee/app/controllers/groups/epics_controller.rb @@ -22,6 +22,9 @@ class Groups::EpicsController < Groups::ApplicationController push_frontend_feature_flag(:content_editor_on_issues, @group) push_frontend_feature_flag(:or_issuable_queries, @group) push_frontend_feature_flag(:saved_replies, current_user) + push_frontend_feature_flag(:summarize_comments, current_user) + push_licensed_feature(:summarize_notes, group) if group.licensed_feature_available?(:summarize_notes) + push_frontend_feature_flag(:openai_experimentation, current_user) end feature_category :portfolio_management diff --git a/ee/spec/frontend/vue_shared/components/markdown/ai_actions_dropdown_spec.js b/ee/spec/frontend/vue_shared/components/markdown/ai_actions_dropdown_spec.js index 74efb64aa2f44f3df4edeaddc6b3f5797791a98d..b1221fbf0c2d3c8dde010295c759c9c89349fd7c 100644 --- a/ee/spec/frontend/vue_shared/components/markdown/ai_actions_dropdown_spec.js +++ b/ee/spec/frontend/vue_shared/components/markdown/ai_actions_dropdown_spec.js @@ -24,7 +24,7 @@ jest.mock('~/lib/utils/text_markdown'); describe('AI actions dropdown component', () => { let wrapper; - const issuableId = 1; + const resourceGlobalId = 'gid://gitlab/Issue/1'; const userId = 99; let aiResponseSubscriptionHandler; let aiActionMutationHandler; @@ -52,7 +52,7 @@ describe('AI actions dropdown component', () => { attachTo: '#root', apolloProvider: mockApollo, propsData: { - issuableId, + resourceGlobalId, ...props, }, }); @@ -171,6 +171,20 @@ describe('AI actions dropdown component', () => { }), ); }); + + it('shows an error and logs to Sentry when the AI subscription fails', async () => { + const mockError = new Error('ding'); + + aiResponseSubscriptionHandler.error(mockError); + + expect(createAlert).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Something went wrong', + captureError: true, + error: mockError, + }), + ); + }); }); }); }); diff --git a/ee/spec/frontend/vue_shared/components/markdown/header_spec.js b/ee/spec/frontend/vue_shared/components/markdown/header_spec.js index 9b519542f92e7f58aeabb2e238d7c451d632c658..3daccbe4534bd0a1706180fd53846dfb1bb1d6f2 100644 --- a/ee/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/ee/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -25,7 +25,7 @@ describe('Markdown field header component', () => { createWrapper( {}, { - issuableId: 1, + resourceGlobalId: 'gid://gitlab/Issue/1', glFeatures: { openaiExperimentation: enabled, summarizeComments: enabled,