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