diff --git a/.rubocop_todo/rspec/redundant_around.yml b/.rubocop_todo/rspec/redundant_around.yml
deleted file mode 100644
index 7966acaabb4..00000000000
--- a/.rubocop_todo/rspec/redundant_around.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-# Cop supports --autocorrect.
-RSpec/RedundantAround:
- Exclude:
- - 'spec/spec_helper.rb'
diff --git a/Gemfile b/Gemfile
index 1b5d50a3c7f..600d8c1720d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -197,6 +197,10 @@ gem 'hamlit', '~> 2.15.0', feature_category: :shared
gem 'carrierwave', '~> 1.3', feature_category: :shared
gem 'mini_magick', '~> 4.12', feature_category: :shared
+# PDF generation
+gem 'prawn', feature_category: :vulnerability_management
+gem 'prawn-svg', feature_category: :vulnerability_management
+
# for backups
gem 'fog-aws', '~> 3.26', feature_category: :shared
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 75a06978472..11ea1bc8fc4 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -508,6 +508,7 @@
{"name":"parser","version":"3.3.7.1","platform":"ruby","checksum":"7dbe61618025519024ac72402a6677ead02099587a5538e84371b76659e6aca1"},
{"name":"parslet","version":"1.8.2","platform":"ruby","checksum":"08d1ab3721cd3f175bfbee8788b2ddff71f92038f2d69bd65454c22bb9fbd98a"},
{"name":"pastel","version":"0.8.0","platform":"ruby","checksum":"481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75"},
+{"name":"pdf-core","version":"0.10.0","platform":"ruby","checksum":"0a5d101e2063c01e3f941e1ee47cbb97f1adfc1395b58372f4f65f1300f3ce91"},
{"name":"peek","version":"1.1.0","platform":"ruby","checksum":"d6501ead8cde46d8d8ed0d59eb6f0ba713d0a41c11a2c4a81447b2dce37b3ecc"},
{"name":"pg","version":"1.5.9","platform":"ruby","checksum":"761efbdf73b66516f0c26fcbe6515dc7500c3f0aa1a1b853feae245433c64fdc"},
{"name":"pg","version":"1.5.9","platform":"x64-mingw-ucrt","checksum":"9d9d6a4fcc25251312065b61f94eb56c5266007c0e747606704641d47b92c5eb"},
@@ -516,6 +517,8 @@
{"name":"pg_query","version":"6.1.0","platform":"ruby","checksum":"8b005229e209f12c5887c34c60d0eb2a241953b9475b53a9840d24578532481e"},
{"name":"plist","version":"3.7.0","platform":"ruby","checksum":"703ca90a7cb00e8263edd03da2266627f6741d280c910abbbac07c95ffb2f073"},
{"name":"png_quantizator","version":"0.2.1","platform":"ruby","checksum":"6023d4d064125c3a7e02929c95b7320ed6ac0d7341f9e8de0c9ea6576ef3106b"},
+{"name":"prawn","version":"2.5.0","platform":"ruby","checksum":"f4e20e3b4f30bf5b9ae37dad15eb421831594553aa930b2391b0fa0a99c43cb6"},
+{"name":"prawn-svg","version":"0.37.0","platform":"ruby","checksum":"271bdd032c066777b2e76fe971b570e24cb6f4890d5658588106e8aa5b6e2830"},
{"name":"premailer","version":"1.23.0","platform":"ruby","checksum":"f0d7f6ba299559c96ddf982aa5263f85e5617c86437c8d8ffff120813b2d7efb"},
{"name":"premailer-rails","version":"1.12.0","platform":"ruby","checksum":"c13815d161b9bc7f7d3d81396b0bb0a61a90fa9bd89931548bf4e537c7710400"},
{"name":"prime","version":"0.1.3","platform":"ruby","checksum":"baf031c50d6ce923594913befc8ac86a3251bffb9d6a5e8b03687962054e53e3"},
@@ -746,6 +749,7 @@
{"name":"trailblazer-option","version":"0.1.2","platform":"ruby","checksum":"20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3"},
{"name":"train-core","version":"3.10.8","platform":"ruby","checksum":"8493da02015fbe9b11840d22ba879ef18a0aa2633cb0c04eac3f07dd9b87223b"},
{"name":"truncato","version":"0.7.13","platform":"ruby","checksum":"34621943c067eb892389d356d1312822b81b574e8d7dec2b61955fef0e91e380"},
+{"name":"ttfunk","version":"1.8.0","platform":"ruby","checksum":"a7cbc7e489cc46e979dde04d34b5b9e4f5c8f1ee5fc6b1a7be39b829919d20ca"},
{"name":"tty-color","version":"0.6.0","platform":"ruby","checksum":"6f9c37ca3a4e2367fb2e6d09722762647d6f455c111f05b59f35730eeb24332a"},
{"name":"tty-command","version":"0.10.1","platform":"ruby","checksum":"0c6c471fcb932d55518734eb4e2e07e9efdd2918713cc39bb7393ba862471192"},
{"name":"tty-cursor","version":"0.7.1","platform":"ruby","checksum":"79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48"},
diff --git a/Gemfile.lock b/Gemfile.lock
index f0c0a1ee4a7..fc841fd06f5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1448,6 +1448,7 @@ GEM
parslet (1.8.2)
pastel (0.8.0)
tty-color (~> 0.5)
+ pdf-core (0.10.0)
peek (1.1.0)
railties (>= 4.0.0)
pg (1.5.9)
@@ -1455,6 +1456,15 @@ GEM
google-protobuf (>= 3.25.3)
plist (3.7.0)
png_quantizator (0.2.1)
+ prawn (2.5.0)
+ matrix (~> 0.4)
+ pdf-core (~> 0.10.0)
+ ttfunk (~> 1.8)
+ prawn-svg (0.37.0)
+ css_parser (~> 1.6)
+ matrix (~> 0.4.2)
+ prawn (>= 0.11.1, < 3)
+ rexml (>= 3.3.9, < 4)
premailer (1.23.0)
addressable
css_parser (>= 1.12.0)
@@ -1896,6 +1906,8 @@ GEM
truncato (0.7.13)
htmlentities (~> 4.3.1)
nokogiri (>= 1.7.0, <= 2.0)
+ ttfunk (1.8.0)
+ bigdecimal (~> 3.1)
tty-color (0.6.0)
tty-command (0.10.1)
pastel (~> 0.8)
@@ -2277,6 +2289,8 @@ DEPENDENCIES
pg (~> 1.5.6)
pg_query (~> 6.1.0)
png_quantizator (~> 0.2.1)
+ prawn
+ prawn-svg
premailer-rails (~> 1.12.0)
prometheus-client-mmap (~> 1.2.8)
pry-byebug
diff --git a/Gemfile.next.checksum b/Gemfile.next.checksum
index 6b6f3e2aecf..6bbd281ae3d 100644
--- a/Gemfile.next.checksum
+++ b/Gemfile.next.checksum
@@ -511,6 +511,7 @@
{"name":"parser","version":"3.3.7.1","platform":"ruby","checksum":"7dbe61618025519024ac72402a6677ead02099587a5538e84371b76659e6aca1"},
{"name":"parslet","version":"1.8.2","platform":"ruby","checksum":"08d1ab3721cd3f175bfbee8788b2ddff71f92038f2d69bd65454c22bb9fbd98a"},
{"name":"pastel","version":"0.8.0","platform":"ruby","checksum":"481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75"},
+{"name":"pdf-core","version":"0.10.0","platform":"ruby","checksum":"0a5d101e2063c01e3f941e1ee47cbb97f1adfc1395b58372f4f65f1300f3ce91"},
{"name":"peek","version":"1.1.0","platform":"ruby","checksum":"d6501ead8cde46d8d8ed0d59eb6f0ba713d0a41c11a2c4a81447b2dce37b3ecc"},
{"name":"pg","version":"1.5.9","platform":"ruby","checksum":"761efbdf73b66516f0c26fcbe6515dc7500c3f0aa1a1b853feae245433c64fdc"},
{"name":"pg","version":"1.5.9","platform":"x64-mingw-ucrt","checksum":"9d9d6a4fcc25251312065b61f94eb56c5266007c0e747606704641d47b92c5eb"},
@@ -520,6 +521,8 @@
{"name":"plist","version":"3.7.0","platform":"ruby","checksum":"703ca90a7cb00e8263edd03da2266627f6741d280c910abbbac07c95ffb2f073"},
{"name":"png_quantizator","version":"0.2.1","platform":"ruby","checksum":"6023d4d064125c3a7e02929c95b7320ed6ac0d7341f9e8de0c9ea6576ef3106b"},
{"name":"pp","version":"0.6.2","platform":"ruby","checksum":"947ec3120c6f92195f8ee8aa25a7b2c5297bb106d83b41baa02983686577b6ff"},
+{"name":"prawn","version":"2.5.0","platform":"ruby","checksum":"f4e20e3b4f30bf5b9ae37dad15eb421831594553aa930b2391b0fa0a99c43cb6"},
+{"name":"prawn-svg","version":"0.37.0","platform":"ruby","checksum":"271bdd032c066777b2e76fe971b570e24cb6f4890d5658588106e8aa5b6e2830"},
{"name":"premailer","version":"1.23.0","platform":"ruby","checksum":"f0d7f6ba299559c96ddf982aa5263f85e5617c86437c8d8ffff120813b2d7efb"},
{"name":"premailer-rails","version":"1.12.0","platform":"ruby","checksum":"c13815d161b9bc7f7d3d81396b0bb0a61a90fa9bd89931548bf4e537c7710400"},
{"name":"prettyprint","version":"0.2.0","platform":"ruby","checksum":"2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193"},
@@ -725,8 +728,8 @@
{"name":"state_machines-activemodel","version":"0.8.0","platform":"ruby","checksum":"e932dab190d4be044fb5f9cab01a3ea0b092c5f113d4676c6c0a0d49bf738d2c"},
{"name":"state_machines-activerecord","version":"0.8.0","platform":"ruby","checksum":"072fb701b8ab03de0608297f6c55dc34ed096e556fa8f77e556f3c461c71aab6"},
{"name":"state_machines-rspec","version":"0.6.0","platform":"ruby","checksum":"2ba57a45df57d0c97f79146e2e0f65f519b52e65e182805ef79cb73b1fe5c0bd"},
-{"name":"stringio","version":"3.1.6","platform":"java","checksum":"dbdb1ee4e6d75782bbc7e8cc7d84cd05e592df50494f363011cc7cd48153bbf7"},
-{"name":"stringio","version":"3.1.6","platform":"ruby","checksum":"292c495d1657adfcdf0a32eecf12a60e6691317a500c3112ad3b2e31068274f5"},
+{"name":"stringio","version":"3.1.7","platform":"java","checksum":"167bdd3d60a002ee94bc289cc3259638aaadc36a47b3086a44a694b5dc72a499"},
+{"name":"stringio","version":"3.1.7","platform":"ruby","checksum":"5b78b7cb242a315fb4fca61a8255d62ec438f58da2b90be66048546ade4507fa"},
{"name":"strings","version":"0.2.1","platform":"ruby","checksum":"933293b3c95cf85b81eb44b3cf673e3087661ba739bbadfeadf442083158d6fb"},
{"name":"strings-ansi","version":"0.2.0","platform":"ruby","checksum":"90262d760ea4a94cc2ae8d58205277a343409c288cbe7c29416b1826bd511c88"},
{"name":"swd","version":"2.0.3","platform":"ruby","checksum":"4cdbe2a4246c19f093fce22e967ec3ebdd4657d37673672e621bf0c7eb770655"},
@@ -759,6 +762,7 @@
{"name":"trailblazer-option","version":"0.1.2","platform":"ruby","checksum":"20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3"},
{"name":"train-core","version":"3.10.8","platform":"ruby","checksum":"8493da02015fbe9b11840d22ba879ef18a0aa2633cb0c04eac3f07dd9b87223b"},
{"name":"truncato","version":"0.7.13","platform":"ruby","checksum":"34621943c067eb892389d356d1312822b81b574e8d7dec2b61955fef0e91e380"},
+{"name":"ttfunk","version":"1.8.0","platform":"ruby","checksum":"a7cbc7e489cc46e979dde04d34b5b9e4f5c8f1ee5fc6b1a7be39b829919d20ca"},
{"name":"tty-color","version":"0.6.0","platform":"ruby","checksum":"6f9c37ca3a4e2367fb2e6d09722762647d6f455c111f05b59f35730eeb24332a"},
{"name":"tty-command","version":"0.10.1","platform":"ruby","checksum":"0c6c471fcb932d55518734eb4e2e07e9efdd2918713cc39bb7393ba862471192"},
{"name":"tty-cursor","version":"0.7.1","platform":"ruby","checksum":"79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48"},
diff --git a/Gemfile.next.lock b/Gemfile.next.lock
index 0d42621c291..af227e4ad38 100644
--- a/Gemfile.next.lock
+++ b/Gemfile.next.lock
@@ -1465,6 +1465,7 @@ GEM
parslet (1.8.2)
pastel (0.8.0)
tty-color (~> 0.5)
+ pdf-core (0.10.0)
peek (1.1.0)
railties (>= 4.0.0)
pg (1.5.9)
@@ -1474,6 +1475,15 @@ GEM
png_quantizator (0.2.1)
pp (0.6.2)
prettyprint
+ prawn (2.5.0)
+ matrix (~> 0.4)
+ pdf-core (~> 0.10.0)
+ ttfunk (~> 1.8)
+ prawn-svg (0.37.0)
+ css_parser (~> 1.6)
+ matrix (~> 0.4.2)
+ prawn (>= 0.11.1, < 3)
+ rexml (>= 3.3.9, < 4)
premailer (1.23.0)
addressable
css_parser (>= 1.12.0)
@@ -1870,7 +1880,7 @@ GEM
activesupport
rspec (~> 3.3)
state_machines
- stringio (3.1.6)
+ stringio (3.1.7)
strings (0.2.1)
strings-ansi (~> 0.2)
unicode-display_width (>= 1.5, < 3.0)
@@ -1930,6 +1940,8 @@ GEM
truncato (0.7.13)
htmlentities (~> 4.3.1)
nokogiri (>= 1.7.0, <= 2.0)
+ ttfunk (1.8.0)
+ bigdecimal (~> 3.1)
tty-color (0.6.0)
tty-command (0.10.1)
pastel (~> 0.8)
@@ -2311,6 +2323,8 @@ DEPENDENCIES
pg (~> 1.5.6)
pg_query (~> 6.1.0)
png_quantizator (~> 0.2.1)
+ prawn
+ prawn-svg
premailer-rails (~> 1.12.0)
prometheus-client-mmap (~> 1.2.8)
pry-byebug
diff --git a/app/assets/javascripts/blob/components/blob_content_error.vue b/app/assets/javascripts/blob/components/blob_content_error.vue
index 49b49a5546a..138f611251d 100644
--- a/app/assets/javascripts/blob/components/blob_content_error.vue
+++ b/app/assets/javascripts/blob/components/blob_content_error.vue
@@ -70,7 +70,6 @@ export default {
{{ option.text }}
diff --git a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
index 3c62bb388fa..a2e5b2f4b5d 100644
--- a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
+++ b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
@@ -116,7 +116,6 @@ export default {
:selected="isBlameViewer"
category="primary"
variant="default"
- data-test-id="blame-toggle"
@click="switchToViewer($options.BLAME_VIEWER)"
>{{ __('Blame') }}
diff --git a/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue b/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue
index 52f39eece49..63b544aae24 100644
--- a/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue
+++ b/app/assets/javascripts/ci/pipeline_details/header/pipeline_header.vue
@@ -4,9 +4,8 @@ import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL } from '~/ci/constants';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import { setUrlFragment, visitUrl } from '~/lib/utils/url_utility';
import { __, n__, sprintf, formatNumber } from '~/locale';
-import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import PageHeading from '~/vue_shared/components/page_heading.vue';
-import { TYPENAME_CI_PIPELINE } from '~/graphql_shared/constants';
import { reportToSentry } from '~/ci/utils';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
@@ -70,9 +69,6 @@ export default {
pipelineIid: {
default: '',
},
- pipelineId: {
- default: '',
- },
},
apollo: {
pipeline: {
@@ -89,6 +85,40 @@ export default {
update(data) {
return data.project.pipeline;
},
+ result({ data }) {
+ // we use a manual subscribeToMore call due to issues with
+ // the skip hook not working correctly for the subscription
+ if (this.showRealTimePipelineStatus && data?.project?.pipeline?.id) {
+ this.$apollo.queries.pipeline.subscribeToMore({
+ document: pipelineCiStatusUpdatedSubscription,
+ variables: {
+ pipelineId: data.project.pipeline.id,
+ },
+ updateQuery(
+ previousData,
+ {
+ subscriptionData: {
+ data: { ciPipelineStatusUpdated },
+ },
+ },
+ ) {
+ if (ciPipelineStatusUpdated) {
+ return {
+ project: {
+ ...previousData.project,
+ pipeline: {
+ ...previousData.project.pipeline,
+ detailedStatus: ciPipelineStatusUpdated.detailedStatus,
+ },
+ },
+ };
+ }
+
+ return previousData;
+ },
+ });
+ }
+ },
error(error) {
this.reportFailure(LOAD_FAILURE);
reportToSentry(this.$options.name, error);
@@ -102,41 +132,6 @@ export default {
this.isRetrying = false;
}
},
- subscribeToMore: {
- document() {
- return pipelineCiStatusUpdatedSubscription;
- },
- variables() {
- return {
- pipelineId: convertToGraphQLId(TYPENAME_CI_PIPELINE, this.pipelineId),
- };
- },
- skip() {
- return !this.showRealTimePipelineStatus;
- },
- updateQuery(
- previousData,
- {
- subscriptionData: {
- data: { ciPipelineStatusUpdated },
- },
- },
- ) {
- if (ciPipelineStatusUpdated) {
- return {
- project: {
- ...previousData.project,
- pipeline: {
- ...previousData.project.pipeline,
- detailedStatus: ciPipelineStatusUpdated.detailedStatus,
- },
- },
- };
- }
-
- return previousData;
- },
- },
},
},
data() {
diff --git a/app/assets/javascripts/ci/pipeline_details/pipeline_header.js b/app/assets/javascripts/ci/pipeline_details/pipeline_header.js
index bd0e94384b5..f80c19ff68b 100644
--- a/app/assets/javascripts/ci/pipeline_details/pipeline_header.js
+++ b/app/assets/javascripts/ci/pipeline_details/pipeline_header.js
@@ -15,7 +15,6 @@ export const createPipelineHeaderApp = (elSelector, apolloProvider, graphqlResou
const {
fullPath,
pipelineIid,
- pipelineId,
pipelinesPath,
identityVerificationPath,
identityVerificationRequired,
@@ -36,7 +35,6 @@ export const createPipelineHeaderApp = (elSelector, apolloProvider, graphqlResou
pipelinesPath,
},
pipelineIid,
- pipelineId,
identityVerificationPath,
identityVerificationRequired: parseBoolean(identityVerificationRequired),
mergeTrainsAvailable: parseBoolean(mergeTrainsAvailable),
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 5c549bd8b2d..417179a7559 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -3,8 +3,12 @@ import { GlLoadingIcon, GlPagination, GlSprintf, GlAlert } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { debounce, throttle } from 'lodash';
// eslint-disable-next-line no-restricted-imports
-import { mapState, mapGetters, mapActions } from 'vuex';
-import { mapState as mapPiniaState, mapActions as mapPiniaActions } from 'pinia';
+import {
+ mapState as mapVuexState,
+ mapGetters as mapVuexGetters,
+ mapActions as mapVuexActions,
+} from 'vuex';
+import { mapState, mapActions } from 'pinia';
import FindingsDrawer from 'ee_component/diffs/components/shared/findings_drawer.vue';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import {
@@ -23,12 +27,12 @@ import { BV_HIDE_TOOLTIP, DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/c
import { Mousetrap } from '~/lib/mousetrap';
import { updateHistory, getLocationHash } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
-
import notesEventHub from '~/notes/event_hub';
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
import getMRCodequalityAndSecurityReports from 'ee_else_ce/diffs/components/graphql/get_mr_codequality_and_security_reports.query.graphql';
import { useFileBrowser } from '~/diffs/stores/file_browser';
import { useLegacyDiffs } from '~/diffs/stores/legacy_diffs';
+import { useNotes } from '~/notes/store/legacy_notes';
import { sortFindingsByFile } from '../utils/sort_findings_by_file';
import {
ALERT_OVERFLOW_HIDDEN,
@@ -47,7 +51,6 @@ import {
EVT_DISCUSSIONS_ASSIGNED,
FILE_BROWSER_VISIBLE,
} from '../constants';
-
import { isCollapsed } from '../utils/diff_file';
import diffsEventHub from '../event_hub';
import { reviewStatuses } from '../utils/file_reviews';
@@ -219,12 +222,11 @@ export default {
},
},
computed: {
- ...mapPiniaState(useLegacyDiffs, {
+ ...mapState(useLegacyDiffs, {
numTotalFiles: 'realSize',
numVisibleFiles: 'size',
}),
- ...mapState('findingsDrawer', ['activeDrawer']),
- ...mapPiniaState(useLegacyDiffs, [
+ ...mapState(useLegacyDiffs, [
'isLoading',
'diffViewType',
'commit',
@@ -256,9 +258,10 @@ export default {
'flatBlobsList',
'diffFiles',
]),
- ...mapGetters(['isNotesFetched', 'getNoteableData']),
- ...mapGetters('findingsDrawer', ['activeDrawer']),
- ...mapPiniaState(useFileBrowser, ['fileBrowserVisible']),
+ ...mapState(useNotes, ['isNotesFetched', 'getNoteableData']),
+ ...mapState(useFileBrowser, ['fileBrowserVisible']),
+ ...mapVuexState('findingsDrawer', ['activeDrawer']),
+ ...mapVuexGetters('findingsDrawer', ['activeDrawer']),
diffs() {
if (!this.viewDiffsFileByFile) {
return this.diffFiles;
@@ -440,8 +443,8 @@ export default {
diffsEventHub.$off('scrollToIndex', this.scrollVirtualScrollerToIndex);
},
methods: {
- ...mapActions(['startTaskList']),
- ...mapPiniaActions(useLegacyDiffs, [
+ ...mapActions(useNotes, ['startTaskList']),
+ ...mapActions(useLegacyDiffs, [
'moveToNeighboringCommit',
'fetchDiffFilesMeta',
'fetchDiffFilesBatch',
@@ -467,8 +470,8 @@ export default {
'reviewFile',
'setFileCollapsedByUser',
]),
- ...mapActions('findingsDrawer', ['setDrawer']),
- ...mapPiniaActions(useFileBrowser, ['setFileBrowserVisibility']),
+ ...mapActions(useFileBrowser, ['setFileBrowserVisibility']),
+ ...mapVuexActions('findingsDrawer', ['setDrawer']),
closeDrawer() {
this.setDrawer({});
},
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index 395b0b43a21..90f5a5701bf 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -1,7 +1,5 @@
',
- target_branch: '',
- };
+ useNotes().noteableData.source_branch = '';
+ useNotes().noteableData.target_branch = '';
const wrapper = createComponent();
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index a6667bf70d7..b0d56d6ea94 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -34,7 +34,11 @@ describe('Repository last commit component', () => {
const subscriptionHandler = jest.fn().mockResolvedValue(mockPipelineStatusUpdatedResponse);
- const createComponent = (data = {}, pipelineSubscriptionHandler = subscriptionHandler) => {
+ const createComponent = (
+ data = {},
+ pipelineSubscriptionHandler = subscriptionHandler,
+ isRealTime = true,
+ ) => {
const currentPath = 'path';
commitData = createCommitData(data);
@@ -50,7 +54,7 @@ describe('Repository last commit component', () => {
propsData: { currentPath, historyUrl: '/history' },
provide: {
glFeatures: {
- ciPipelineStatusRealtime: true,
+ ciPipelineStatusRealtime: isRealTime,
},
},
});
@@ -182,6 +186,16 @@ describe('Repository last commit component', () => {
pipelineId: 'gid://gitlab/Ci::Pipeline/167',
});
});
+
+ it('does not call the subscription when feature flag is false', async () => {
+ const realTime = false;
+
+ createComponent({}, subscriptionHandler, realTime);
+
+ await waitForPromises();
+
+ expect(subscriptionHandler).not.toHaveBeenCalled();
+ });
});
describe('polling', () => {
diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js
index 49500411c4b..026f391c4b6 100644
--- a/spec/frontend/search/topbar/components/app_spec.js
+++ b/spec/frontend/search/topbar/components/app_spec.js
@@ -195,7 +195,7 @@ describe('GlobalSearchTopbar', () => {
it(`calls applyQuery ${called ? '' : 'NOT '}`, async () => {
await nextTick();
- findGlSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
+ findGlSearchBox().vm.$emit('keydown', new KeyboardEvent('keydown', { key: ENTER_KEY }));
expect(actionSpies.applyQuery).toHaveBeenCalledTimes(called ? 1 : 0);
});
});
diff --git a/spec/frontend/sidebar/components/assignees/assignee_title_spec.js b/spec/frontend/sidebar/components/assignees/assignee_title_spec.js
index b2d15e76e80..9c72a538355 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_title_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_title_spec.js
@@ -1,5 +1,5 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import Component from '~/sidebar/components/assignees/assignee_title.vue';
@@ -7,7 +7,7 @@ describe('AssigneeTitle component', () => {
let wrapper;
const createComponent = (props) => {
- return shallowMount(Component, {
+ return shallowMountExtended(Component, {
propsData: {
numberOfAssignees: 0,
editable: false,
@@ -17,6 +17,8 @@ describe('AssigneeTitle component', () => {
});
};
+ const findEditLink = () => wrapper.findByTestId('edit-link');
+
describe('assignee title', () => {
it('renders assignee', () => {
wrapper = createComponent({
@@ -41,7 +43,7 @@ describe('AssigneeTitle component', () => {
it('renders "Edit"', () => {
wrapper = createComponent({ editable: true });
- expect(wrapper.find('[data-test-id="edit-link"]').text()).toEqual('Edit');
+ expect(findEditLink().text()).toBe('Edit');
});
});
@@ -49,7 +51,7 @@ describe('AssigneeTitle component', () => {
it('renders "Edit"', () => {
wrapper = createComponent({ editable: true, changing: true });
- expect(wrapper.find('[data-test-id="edit-link"]').text()).toEqual('Apply');
+ expect(findEditLink().text()).toBe('Apply');
});
});
diff --git a/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js b/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
index ca77c9d45b1..4945a79caea 100644
--- a/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
+++ b/spec/frontend/vue_shared/components/entity_select/entity_select_spec.js
@@ -133,7 +133,7 @@ describe('EntitySelect', () => {
});
it("renders the error slot's content", () => {
- const selector = 'data-test-id="error-element"';
+ const selector = 'data-testid="error-element"';
createComponent({
slots: {
error: `
`,
diff --git a/spec/helpers/projects/pipeline_helper_spec.rb b/spec/helpers/projects/pipeline_helper_spec.rb
index 2370723c12f..863d6857126 100644
--- a/spec/helpers/projects/pipeline_helper_spec.rb
+++ b/spec/helpers/projects/pipeline_helper_spec.rb
@@ -63,7 +63,6 @@ RSpec.describe Projects::PipelineHelper do
full_path: project.full_path,
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
pipeline_iid: pipeline.iid,
- pipeline_id: pipeline.id,
pipelines_path: project_pipelines_path(project)
})
end
diff --git a/spec/lib/api/entities/packages/conan/recipe_revision_spec.rb b/spec/lib/api/entities/packages/conan/recipe_revision_spec.rb
deleted file mode 100644
index 8baf36604b9..00000000000
--- a/spec/lib/api/entities/packages/conan/recipe_revision_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ::API::Entities::Packages::Conan::RecipeRevision, feature_category: :package_registry do
- let(:recipe_revision) { build_stubbed(:conan_recipe_revision) }
- let(:entity) { described_class.new(recipe_revision) }
-
- subject { entity.as_json }
-
- it 'exposes required attributes' do
- is_expected.to eq(
- revision: recipe_revision.revision,
- time: recipe_revision.created_at
- )
- end
-end
diff --git a/spec/lib/api/entities/packages/conan/revision_spec.rb b/spec/lib/api/entities/packages/conan/revision_spec.rb
new file mode 100644
index 00000000000..fb77fc55f32
--- /dev/null
+++ b/spec/lib/api/entities/packages/conan/revision_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::API::Entities::Packages::Conan::Revision, feature_category: :package_registry do
+ shared_examples 'exposes revision attributes' do |factory|
+ let(:revision) { build_stubbed(factory) }
+ let(:entity) { described_class.new(revision) }
+
+ subject { entity.as_json }
+
+ it 'exposes required attributes' do
+ is_expected.to eq(
+ revision: revision.revision,
+ time: revision.created_at
+ )
+ end
+ end
+
+ it_behaves_like 'exposes revision attributes', :conan_recipe_revision
+ it_behaves_like 'exposes revision attributes', :conan_package_revision
+end
diff --git a/spec/models/container_registry/protection/rule_spec.rb b/spec/models/container_registry/protection/rule_spec.rb
index 7e4ea68d61a..25cdd5b463c 100644
--- a/spec/models/container_registry/protection/rule_spec.rb
+++ b/spec/models/container_registry/protection/rule_spec.rb
@@ -240,100 +240,6 @@ RSpec.describe ContainerRegistry::Protection::Rule, type: :model, feature_catego
end
end
- describe '.for_push_exists?' do
- subject do
- project
- .container_registry_protection_rules
- .for_push_exists?(
- access_level: access_level,
- repository_path: repository_path
- )
- end
-
- context 'when the repository path matches multiple protection rules' do
- # The abbreviation `crpr` stands for container registry protection rule
- let_it_be(:project_with_crpr) { create(:project) }
- let_it_be(:project_without_crpr) { create(:project) }
-
- let_it_be(:protection_rule_for_developer) do
- create(:container_registry_protection_rule,
- project: project_with_crpr,
- repository_path_pattern: "#{project_with_crpr.full_path}/my-container-stage*",
- minimum_access_level_for_push: :maintainer
- )
- end
-
- let_it_be(:protection_rule_for_maintainer) do
- create(:container_registry_protection_rule,
- project: project_with_crpr,
- repository_path_pattern: "#{project_with_crpr.full_path}/my-container-prod*",
- minimum_access_level_for_push: :owner
- )
- end
-
- let_it_be(:protection_rule_for_owner) do
- create(:container_registry_protection_rule,
- project: project_with_crpr,
- repository_path_pattern: "#{project_with_crpr.full_path}/my-container-release*",
- minimum_access_level_for_push: :admin
- )
- end
-
- let_it_be(:protection_rule_overlapping_for_developer) do
- create(:container_registry_protection_rule,
- project: project_with_crpr,
- repository_path_pattern: "#{project_with_crpr.full_path}/my-container-*",
- minimum_access_level_for_push: :maintainer
- )
- end
-
- # rubocop:disable Layout/LineLength -- Avoid formatting to keep one-line table syntax
- where(:project, :access_level, :repository_path, :for_push_exists) do
- ref(:project_with_crpr) | Gitlab::Access::REPORTER | lazy { "#{project_with_crpr.full_path}/my-container-stage-sha-1234" } | true
- ref(:project_with_crpr) | Gitlab::Access::DEVELOPER | lazy { "#{project_with_crpr.full_path}/my-container-stage-sha-1234" } | true
- ref(:project_with_crpr) | Gitlab::Access::MAINTAINER | lazy { "#{project_with_crpr.full_path}/my-container-stage-sha-1234" } | false
- ref(:project_with_crpr) | Gitlab::Access::MAINTAINER | lazy { "#{project_with_crpr.full_path}/my-container-stage-sha-1234" } | false
- ref(:project_with_crpr) | Gitlab::Access::OWNER | lazy { "#{project_with_crpr.full_path}/my-container-stage-sha-1234" } | false
- ref(:project_with_crpr) | Gitlab::Access::ADMIN | lazy { "#{project_with_crpr.full_path}/my-container-stage-sha-1234" } | false
-
- ref(:project_with_crpr) | Gitlab::Access::DEVELOPER | lazy { "#{project_with_crpr.full_path}/my-container-prod-sha-1234" } | true
- ref(:project_with_crpr) | Gitlab::Access::MAINTAINER | lazy { "#{project_with_crpr.full_path}/my-container-prod-sha-1234" } | true
- ref(:project_with_crpr) | Gitlab::Access::OWNER | lazy { "#{project_with_crpr.full_path}/my-container-prod-sha-1234" } | false
- ref(:project_with_crpr) | Gitlab::Access::ADMIN | lazy { "#{project_with_crpr.full_path}/my-container-prod-sha-1234" } | false
-
- ref(:project_with_crpr) | Gitlab::Access::DEVELOPER | lazy { "#{project_with_crpr.full_path}/my-container-release-v1" } | true
- ref(:project_with_crpr) | Gitlab::Access::OWNER | lazy { "#{project_with_crpr.full_path}/my-container-release-v1" } | true
- ref(:project_with_crpr) | Gitlab::Access::ADMIN | lazy { "#{project_with_crpr.full_path}/my-container-release-v1" } | false
-
- ref(:project_with_crpr) | Gitlab::Access::DEVELOPER | lazy { "#{project_with_crpr.full_path}/my-container-any-suffix" } | true
- ref(:project_with_crpr) | Gitlab::Access::MAINTAINER | lazy { "#{project_with_crpr.full_path}/my-container-any-suffix" } | false
- ref(:project_with_crpr) | Gitlab::Access::OWNER | lazy { "#{project_with_crpr.full_path}/my-container-any-suffix" } | false
-
- # For non-matching repository_path
- ref(:project_with_crpr) | Gitlab::Access::DEVELOPER | lazy { "#{project_with_crpr.full_path}/non-matching-container" } | false
-
- # For no access level
- ref(:project_with_crpr) | Gitlab::Access::NO_ACCESS | lazy { "#{project_with_crpr.full_path}/my-container-prod-sha-1234" } | true
-
- # Edge cases
- ref(:project_with_crpr) | 0 | '' | false
- ref(:project_with_crpr) | nil | nil | false
- ref(:project_with_crpr) | Gitlab::Access::DEVELOPER | nil | false
- ref(:project_with_crpr) | nil | lazy { "#{project_with_crpr.full_path}/non-matching-container" } | false
-
- # For projects that have no container registry protection rules
- ref(:project_without_crpr) | Gitlab::Access::DEVELOPER | lazy { "#{project_without_crpr.full_path}/my-container-prod-sha-1234" } | false
- ref(:project_without_crpr) | Gitlab::Access::MAINTAINER | lazy { "#{project_without_crpr.full_path}/my-container-prod-sha-1234" } | false
- ref(:project_without_crpr) | Gitlab::Access::OWNER | lazy { "#{project_without_crpr.full_path}/my-container-prod-sha-1234" } | false
- end
- # rubocop:enable Layout/LineLength
-
- with_them do
- it { is_expected.to eq for_push_exists }
- end
- end
- end
-
describe '.for_action_exists?' do
let_it_be(:project1) { create(:project) }
let_it_be(:project_no_crpr) { create(:project) }
diff --git a/spec/models/packages/conan/package_revision_spec.rb b/spec/models/packages/conan/package_revision_spec.rb
index 56facb94578..31e5b803c64 100644
--- a/spec/models/packages/conan/package_revision_spec.rb
+++ b/spec/models/packages/conan/package_revision_spec.rb
@@ -61,4 +61,41 @@ RSpec.describe Packages::Conan::PackageRevision, type: :model, feature_category:
end
end
end
+
+ describe 'scopes' do
+ describe '.order_by_id_desc' do
+ let_it_be(:revision_1) { create(:conan_package_revision) }
+ let_it_be(:revision_2) { create(:conan_package_revision) }
+
+ subject { described_class.order_by_id_desc }
+
+ it { is_expected.to eq([revision_2, revision_1]) }
+ end
+
+ describe '.by_recipe_revision_and_package_reference' do
+ let_it_be(:package) { create(:conan_package) }
+ let_it_be(:recipe_revision) { package.conan_recipe_revisions.first }
+ let_it_be(:package_reference) { package.conan_package_references.first }
+ let_it_be(:package_revision) { package.conan_package_revisions.first }
+
+ let(:revision_value) { recipe_revision.revision }
+ let(:reference_value) { package_reference.reference }
+
+ subject { described_class.by_recipe_revision_and_package_reference(revision_value, reference_value) }
+
+ it { is_expected.to contain_exactly(package_revision) }
+
+ context 'when recipe revision does not match' do
+ it 'returns empty relation' do
+ expect(described_class.by_recipe_revision_and_package_reference('nonexistent', reference_value)).to be_empty
+ end
+ end
+
+ context 'when package reference does not match' do
+ it 'returns empty relation' do
+ expect(described_class.by_recipe_revision_and_package_reference(revision_value, 'nonexistent')).to be_empty
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/conan/v2/project_packages_spec.rb b/spec/requests/api/conan/v2/project_packages_spec.rb
index 3200431acbd..2b1f952cb18 100644
--- a/spec/requests/api/conan/v2/project_packages_spec.rb
+++ b/spec/requests/api/conan/v2/project_packages_spec.rb
@@ -281,4 +281,52 @@ RSpec.describe API::Conan::V2::ProjectPackages, feature_category: :package_regis
it_behaves_like 'package not found'
it_behaves_like 'project not found by project id'
end
+
+ describe 'GET /api/v4/projects/:id/packages/conan/v2/conans/:package_name/:package_version/:package_username' \
+ '/:package_channel/revisions/:recipe_revision/packages/:conan_package_reference/latest' do
+ let(:recipe_path) { package.conan_recipe_path }
+ let(:recipe_revision) { package.conan_recipe_revisions.first.revision }
+ let(:conan_package_reference) { package.conan_package_references.first.reference }
+ let(:url_suffix) { "#{recipe_path}/revisions/#{recipe_revision}/packages/#{conan_package_reference}/latest" }
+
+ subject(:request) { get api(url), headers: headers }
+
+ it 'returns the latest revision' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ package_revision = package.conan_package_revisions.first
+
+ expect(json_response['revision']).to eq(package_revision.revision)
+ expect(json_response['time']).to eq(package_revision.created_at.iso8601(3))
+ end
+
+ shared_examples 'returns 404 when resource does not exist' do
+ it 'returns 404' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Revision Not Found')
+ end
+ end
+
+ context 'when recipe revision does not exist' do
+ let(:recipe_revision) { OpenSSL::Digest.hexdigest('MD5', 'nonexistent-revision') }
+
+ it_behaves_like 'returns 404 when resource does not exist'
+ end
+
+ context 'when package reference does not exist' do
+ let(:conan_package_reference) { OpenSSL::Digest.hexdigest('SHA1', 'nonexistent-reference') }
+
+ it_behaves_like 'returns 404 when resource does not exist'
+ end
+
+ it_behaves_like 'enforcing read_packages job token policy'
+ it_behaves_like 'accept get request on private project with access to package registry for everyone'
+ it_behaves_like 'conan FIPS mode'
+ it_behaves_like 'package not found'
+ it_behaves_like 'project not found by project id'
+ end
end
diff --git a/spec/services/container_registry/protection/update_tag_rule_service_spec.rb b/spec/services/container_registry/protection/update_tag_rule_service_spec.rb
index 449c8d160a7..bce819b8636 100644
--- a/spec/services/container_registry/protection/update_tag_rule_service_spec.rb
+++ b/spec/services/container_registry/protection/update_tag_rule_service_spec.rb
@@ -173,4 +173,12 @@ RSpec.describe ContainerRegistry::Protection::UpdateTagRuleService, '#execute',
it_behaves_like 'an erroneous service response',
message: 'GitLab container registry API not supported'
end
+
+ context 'when the rule is immutable' do
+ let_it_be(:container_protection_tag_rule) do
+ create(:container_registry_protection_tag_rule, :immutable, project: project, tag_name_pattern: 'a')
+ end
+
+ it_behaves_like 'an erroneous service response', message: 'Operation not allowed'
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 97ceeb848f2..9ee9779e02a 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -278,10 +278,6 @@ RSpec.configure do |config|
::Ci::ApplicationRecord.set_open_transactions_baseline
end
- config.around do |example|
- example.run
- end
-
config.append_after do
ApplicationRecord.reset_open_transactions_baseline
::Ci::ApplicationRecord.reset_open_transactions_baseline
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index 7ee8910e6a1..dcd1450c617 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -1496,6 +1496,34 @@ RSpec.shared_examples 'a container registry auth service' do
it_behaves_like params[:shared_examples_name]
end
end
+
+ context 'for deploy tokens' do
+ let_it_be(:deploy_token) { create(:deploy_token, write_registry: true, projects: [current_project]) }
+
+ let(:current_params) { { scopes: current_params_scopes, deploy_token: deploy_token } }
+
+ before do
+ container_registry_protection_rule.update!(
+ repository_path_pattern: repository_path_pattern,
+ minimum_access_level_for_push: minimum_access_level_for_push
+ )
+ end
+
+ # rubocop:disable Layout/LineLength -- Avoid formatting to keep one-line table layout
+ where(:repository_path_pattern, :minimum_access_level_for_push, :current_params_scopes, :shared_examples_name) do
+ ref(:container_repository_path) | :maintainer | lazy { ["repository:#{container_repository_path}:push"] } | 'a protected container repository'
+ ref(:container_repository_path) | :maintainer | lazy { ["repository:#{container_repository_path}:push,pull"] } | 'a protected container repository'
+ ref(:container_repository_path) | :owner | lazy { ["repository:#{container_repository_path}:push"] } | 'a protected container repository'
+ ref(:container_repository_path) | :admin | lazy { ["repository:#{container_repository_path}:push"] } | 'a protected container repository'
+ ref(:container_repository_path_pattern_no_match) | :maintainer | lazy { ["repository:#{container_repository_path}:push"] } | 'a pushable'
+ ref(:container_repository_path_pattern_no_match) | :admin | lazy { ["repository:#{container_repository_path}:push"] } | 'a pushable'
+ end
+ # rubocop:enable Layout/LineLength
+
+ with_them do
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
end
context 'with protected tags' do