diff --git a/app/assets/javascripts/ci/job_details/index.js b/app/assets/javascripts/ci/job_details/index.js index 7e14572b71740a05cba1d896de2658973513826d..a8c0803f7635682111b9bd9cbfaea95193ac00ec 100644 --- a/app/assets/javascripts/ci/job_details/index.js +++ b/app/assets/javascripts/ci/job_details/index.js @@ -34,6 +34,7 @@ export const initJobDetails = () => { pipelineTestReportUrl, logViewerPath, duoFeaturesEnabled, + jobGid, } = el.dataset; const fullScreenAPIAvailable = document.fullscreenEnabled; @@ -58,6 +59,7 @@ export const initJobDetails = () => { aiRootCauseAnalysisAvailable: parseBoolean(aiRootCauseAnalysisAvailable), duoFeaturesEnabled: parseBoolean(duoFeaturesEnabled), pipelineTestReportUrl, + jobGid, }, render(h) { return h(JobApp, { diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb index 3575616f5946a0c83935922d61f562c0d8a1011b..9d1b31bfe5094157d648322f216a5567795dace3 100644 --- a/app/helpers/ci/jobs_helper.rb +++ b/app/helpers/ci/jobs_helper.rb @@ -14,7 +14,8 @@ def jobs_data(project, build) "runner_settings_url" => project_runners_path(build.project, anchor: 'js-runners-settings'), "retry_outdated_job_docs_url" => help_page_path('ci/pipelines/settings', anchor: 'retry-outdated-jobs'), "pipeline_test_report_url" => test_report_project_pipeline_path(project, build.pipeline), - "log_viewer_path" => viewer_project_job_path(project, build) + "log_viewer_path" => viewer_project_job_path(project, build), + "job_gid" => build.to_gid.to_s } end diff --git a/ee/app/assets/javascripts/ci/job_details/components/job_log_top_bar.vue b/ee/app/assets/javascripts/ci/job_details/components/job_log_top_bar.vue index 7b9cae29ba8ca956121e1065fa20f18fb0d673bb..8e6c2f152fe62f6a3d244d7ad9990a8ffeccebd3 100644 --- a/ee/app/assets/javascripts/ci/job_details/components/job_log_top_bar.vue +++ b/ee/app/assets/javascripts/ci/job_details/components/job_log_top_bar.vue @@ -2,10 +2,14 @@ import { GlButton } from '@gitlab/ui'; // eslint-disable-next-line no-restricted-imports import { mapState } from 'vuex'; +import { createAlert } from '~/alert'; +import { s__ } from '~/locale'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import chatMutation from 'ee/ai/graphql/chat.mutation.graphql'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { TYPENAME_CI_BUILD } from '~/graphql_shared/constants'; import CeJobLogTopBar from '~/ci/job_details/components/job_log_top_bar.vue'; +import { helpCenterState } from '~/super_sidebar/constants'; import RootCauseAnalysis from './sidebar/root_cause_analysis/root_cause_analysis_app.vue'; export default { @@ -15,7 +19,7 @@ export default { RootCauseAnalysis, }, mixins: [glFeatureFlagMixin()], - inject: ['aiRootCauseAnalysisAvailable', 'duoFeaturesEnabled'], + inject: ['aiRootCauseAnalysisAvailable', 'duoFeaturesEnabled', 'jobGid'], props: { size: { type: Number, @@ -71,7 +75,16 @@ export default { return ( this.glFeatures.aiBuildFailureCause && this.aiRootCauseAnalysisAvailable && - this.duoFeaturesEnabled + this.duoFeaturesEnabled && + !this.glFeatures.rootCauseAnalysisDuo + ); + }, + rootCauseAnalysisDuoIsAvailable() { + return ( + this.glFeatures.aiBuildFailureCause && + this.aiRootCauseAnalysisAvailable && + this.duoFeaturesEnabled && + this.glFeatures.rootCauseAnalysisDuo ); }, jobFailed() { @@ -84,6 +97,9 @@ export default { jobId() { return convertToGraphQLId(TYPENAME_CI_BUILD, this.job.id); }, + duoDrawerOpen() { + return helpCenterState.showTanukiBotChatDrawer; + }, ...mapState(['job', 'isLoading']), }, methods: { @@ -105,6 +121,25 @@ export default { handleExitFullscreen() { this.$emit('exitFullscreen'); }, + callDuo() { + helpCenterState.showTanukiBotChatDrawer = true; + + this.$apollo + .mutate({ + mutation: chatMutation, + variables: { + question: '/rca', + resourceId: this.jobGid, + }, + }) + .catch((error) => { + createAlert({ + message: s__('AI|An error occurred while troubleshooting the failed job.'), + captureError: true, + error, + }); + }); + }, }, }; </script> @@ -139,12 +174,22 @@ export default { <gl-button v-if="rootCauseAnalysisIsAvailable && jobFailed" icon="tanuki-ai" - class="gl-mr-2" + class="gl-mr-3" data-testid="rca-button" @click="toggleDrawer" > {{ s__('Jobs|Troubleshoot') }} </gl-button> + <gl-button + v-if="rootCauseAnalysisDuoIsAvailable && jobFailed" + :disabled="duoDrawerOpen" + icon="tanuki-ai" + class="gl-mr-3" + data-testid="rca-duo-button" + @click="callDuo" + > + {{ s__('Jobs|Troubleshoot') }} + </gl-button> </template> </ce-job-log-top-bar> </div> diff --git a/ee/spec/features/projects/jobs/root_cause_analysis_job_page_spec.rb b/ee/spec/features/projects/jobs/root_cause_analysis_job_page_spec.rb index f724416207f4ff56b63ea808ac1f1d524ab5b41c..e15552744369571189e8a5c2df3c9294b5806499 100644 --- a/ee/spec/features/projects/jobs/root_cause_analysis_job_page_spec.rb +++ b/ee/spec/features/projects/jobs/root_cause_analysis_job_page_spec.rb @@ -18,45 +18,71 @@ sign_in(user) end - context 'with failed jobs' do + context 'when root_cause_analysis_duo feature flag is turned off' do before do - allow(failed_job).to receive(:debug_mode?).and_return(false) + stub_feature_flags(root_cause_analysis_duo: false) + end - visit(project_job_path(project, failed_job)) + context 'with failed jobs' do + before do + allow(failed_job).to receive(:debug_mode?).and_return(false) - wait_for_requests - end + visit(project_job_path(project, failed_job)) + + wait_for_requests + end - it 'does display rca button' do - expect(page).to have_selector("[data-testid='rca-button']") + it 'does display rca button' do + expect(page).to have_selector("[data-testid='rca-button']") + end end - end - context 'with successful jobs' do - before do - allow(passed_job).to receive(:debug_mode?).and_return(false) + context 'with successful jobs' do + before do + allow(passed_job).to receive(:debug_mode?).and_return(false) + + visit(project_job_path(project, passed_job)) - visit(project_job_path(project, passed_job)) + wait_for_requests + end - wait_for_requests + it 'does not display rca button' do + expect(page).not_to have_selector("[data-testid='rca-button']") + end end - it 'does not display rca button' do - expect(page).not_to have_selector("[data-testid='rca-button']") + context 'without duo_features_enabled permissions' do + before do + project.update!(duo_features_enabled: false) + + visit(project_job_path(project, passed_job)) + + wait_for_requests + end + + it 'does not display rca button' do + expect(page).not_to have_selector("[data-testid='rca-button']") + end end end - context 'without duo_features_enabled permissions' do + context 'when root_cause_analysis_duo feature flag is turned on' do before do - project.update!(duo_features_enabled: false) + stub_feature_flags(root_cause_analysis_duo: true) + end - visit(project_job_path(project, passed_job)) + context 'with failed jobs' do + before do + allow(failed_job).to receive(:debug_mode?).and_return(false) - wait_for_requests - end + visit(project_job_path(project, failed_job)) + + wait_for_requests + end - it 'does not display rca button' do - expect(page).not_to have_selector("[data-testid='rca-button']") + it 'does display rca with duo button' do + expect(page).to have_selector("[data-testid='rca-duo-button']") + end end end end diff --git a/ee/spec/frontend/ci/job_details/components/sidebar/job_log_top_bar_spec.js b/ee/spec/frontend/ci/job_details/components/sidebar/job_log_top_bar_spec.js index e13e17de787de54dcab3d06be26eb6a2a43075ab..7d0542cc83dc16579eb8ea0f9791ca0e9b68f58f 100644 --- a/ee/spec/frontend/ci/job_details/components/sidebar/job_log_top_bar_spec.js +++ b/ee/spec/frontend/ci/job_details/components/sidebar/job_log_top_bar_spec.js @@ -26,6 +26,8 @@ describe('EE JobLogTopBar', () => { provide: { aiRootCauseAnalysisAvailable: true, duoFeaturesEnabled: true, + rootCauseAnalysisDuo: false, + jobGid: 'gid://gitlab/Ci::Build/123', }, }); }; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0c0ce35f0722aeccf417b6d6302fd68dd1b43e1f..3fe2abe6933a2697740acf8b2aeae7e3d6b36b8b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2168,6 +2168,9 @@ msgstr "" msgid "AI|An error occurred while explaining the code." msgstr "" +msgid "AI|An error occurred while troubleshooting the failed job." +msgstr "" + msgid "AI|Apply AI-generated description" msgstr "" diff --git a/spec/helpers/ci/jobs_helper_spec.rb b/spec/helpers/ci/jobs_helper_spec.rb index 35b002599d668dc36349a7f15cd13cdf166a4016..ab012e3a9c6e1599756ed41321964282f0e1c95a 100644 --- a/spec/helpers/ci/jobs_helper_spec.rb +++ b/spec/helpers/ci/jobs_helper_spec.rb @@ -29,7 +29,8 @@ "runner_settings_url" => "/#{project.full_path}/-/runners#js-runners-settings", "retry_outdated_job_docs_url" => "/help/ci/pipelines/settings#retry-outdated-jobs", "pipeline_test_report_url" => "/#{project.full_path}/-/pipelines/#{job.pipeline.id}/test_report", - "log_viewer_path" => "/#{project.full_path}/-/jobs/#{job.id}/viewer" + "log_viewer_path" => "/#{project.full_path}/-/jobs/#{job.id}/viewer", + "job_gid" => "gid://gitlab/Ci::Build/#{job.id}" }) end