diff options
80 files changed, 432 insertions, 528 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index ef36b27f167..60e1d7820da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,14 @@ entry. - Scope incident issue counts by given project or group. !40700 +## 13.3.1 (2020-08-25) + +### Fixed (2 changes) + +- Fix bug when promoting an Issue with attachments to an Epic. !39654 +- Avoid creating diff position when line-code is nil. !40089 + + ## 13.3.0 (2020-08-22) ### Security (2 changes) diff --git a/app/assets/javascripts/clusters/components/new_cluster.vue b/app/assets/javascripts/clusters/components/new_cluster.vue new file mode 100644 index 00000000000..2e74ad073c5 --- /dev/null +++ b/app/assets/javascripts/clusters/components/new_cluster.vue @@ -0,0 +1,34 @@ +<script> +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { mapState } from 'vuex'; +import { s__ } from '~/locale'; + +export default { + i18n: { + title: s__('ClusterIntegration|Enter the details for your Kubernetes cluster'), + information: s__( + 'ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{linkStart}documentation%{linkEnd} on Kubernetes', + ), + }, + components: { + GlLink, + GlSprintf, + }, + computed: { + ...mapState(['clusterConnectHelpPath']), + }, +}; +</script> + +<template> + <div> + <h4>{{ $options.i18n.title }}</h4> + <p> + <gl-sprintf :message="$options.i18n.information"> + <template #link="{ content }"> + <gl-link :href="clusterConnectHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> + </div> +</template> diff --git a/app/assets/javascripts/clusters/new_cluster.js b/app/assets/javascripts/clusters/new_cluster.js new file mode 100644 index 00000000000..71f585fd307 --- /dev/null +++ b/app/assets/javascripts/clusters/new_cluster.js @@ -0,0 +1,19 @@ +import Vue from 'vue'; +import NewCluster from './components/new_cluster.vue'; +import { createStore } from './stores/new_cluster'; + +export default () => { + const entryPoint = document.querySelector('#js-cluster-new'); + + if (!entryPoint) { + return null; + } + + return new Vue({ + el: '#js-cluster-new', + store: createStore(entryPoint.dataset), + render(createElement) { + return createElement(NewCluster); + }, + }); +}; diff --git a/app/assets/javascripts/clusters/stores/new_cluster/index.js b/app/assets/javascripts/clusters/stores/new_cluster/index.js new file mode 100644 index 00000000000..ae082c07f26 --- /dev/null +++ b/app/assets/javascripts/clusters/stores/new_cluster/index.js @@ -0,0 +1,12 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import state from './state'; + +Vue.use(Vuex); + +export const createStore = initialState => + new Vuex.Store({ + state: state(initialState), + }); + +export default createStore; diff --git a/app/assets/javascripts/clusters/stores/new_cluster/state.js b/app/assets/javascripts/clusters/stores/new_cluster/state.js new file mode 100644 index 00000000000..1ca1ac8de18 --- /dev/null +++ b/app/assets/javascripts/clusters/stores/new_cluster/state.js @@ -0,0 +1,3 @@ +export default (initialState = {}) => ({ + clusterConnectHelpPath: initialState.clusterConnectHelpPath, +}); diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index e6a411d20d5..66d7c2e3530 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -278,7 +278,6 @@ export default { ...mapActions('diffs', [ 'moveToNeighboringCommit', 'setBaseConfig', - 'fetchDiffFiles', 'fetchDiffFilesMeta', 'fetchDiffFilesBatch', 'fetchCoverageFiles', @@ -311,50 +310,29 @@ export default { return !this.diffFiles.length; }, fetchData(toggleTree = true) { - if (this.glFeatures.diffsBatchLoad) { - this.fetchDiffFilesMeta() - .then(({ real_size }) => { - this.diffFilesLength = parseInt(real_size, 10); - if (toggleTree) this.hideTreeListIfJustOneFile(); + this.fetchDiffFilesMeta() + .then(({ real_size }) => { + this.diffFilesLength = parseInt(real_size, 10); + if (toggleTree) this.hideTreeListIfJustOneFile(); - this.startDiffRendering(); - }) - .catch(() => { - createFlash(__('Something went wrong on our end. Please try again!')); - }); - - this.fetchDiffFilesBatch() - .then(() => { - // Guarantee the discussions are assigned after the batch finishes. - // Just watching the length of the discussions or the diff files - // isn't enough, because with split diff loading, neither will - // change when loading the other half of the diff files. - this.setDiscussions(); - }) - .then(() => this.startDiffRendering()) - .catch(() => { - createFlash(__('Something went wrong on our end. Please try again!')); - }); - } else { - this.fetchDiffFiles() - .then(({ real_size }) => { - this.diffFilesLength = parseInt(real_size, 10); - if (toggleTree) { - this.hideTreeListIfJustOneFile(); - } + this.startDiffRendering(); + }) + .catch(() => { + createFlash(__('Something went wrong on our end. Please try again!')); + }); - requestIdleCallback( - () => { - this.setDiscussions(); - this.startRenderDiffsQueue(); - }, - { timeout: 1000 }, - ); - }) - .catch(() => { - createFlash(__('Something went wrong on our end. Please try again!')); - }); - } + this.fetchDiffFilesBatch() + .then(() => { + // Guarantee the discussions are assigned after the batch finishes. + // Just watching the length of the discussions or the diff files + // isn't enough, because with split diff loading, neither will + // change when loading the other half of the diff files. + this.setDiscussions(); + }) + .then(() => this.startDiffRendering()) + .catch(() => { + createFlash(__('Something went wrong on our end. Please try again!')); + }); if (this.endpointCoverage) { this.fetchCoverageFiles(); diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index fc381adf218..e0470a7d93f 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -64,42 +64,6 @@ export const setBaseConfig = ({ commit }, options) => { }); }; -export const fetchDiffFiles = ({ state, commit }) => { - const worker = new TreeWorker(); - const urlParams = { - w: state.showWhitespace ? '0' : '1', - view: window.gon?.features?.unifiedDiffLines ? 'inline' : state.diffViewType, - }; - let returnData; - - commit(types.SET_LOADING, true); - - worker.addEventListener('message', ({ data }) => { - commit(types.SET_TREE_DATA, data); - - worker.terminate(); - }); - - return axios - .get(mergeUrlParams(urlParams, state.endpoint)) - .then(res => { - commit(types.SET_LOADING, false); - - commit(types.SET_MERGE_REQUEST_DIFFS, res.data.merge_request_diffs || []); - commit(types.SET_DIFF_DATA, res.data); - - worker.postMessage(state.diffFiles); - - returnData = res.data; - return Vue.nextTick(); - }) - .then(() => { - handleLocationHash(); - return returnData; - }) - .catch(() => worker.terminate()); -}; - export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => { const id = window?.location?.hash; const isNoteLink = id.indexOf('#note') === 0; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 3be73d78c42..c39532599cb 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -56,10 +56,7 @@ export default { [types.SET_DIFF_DATA](state, data) { let files = state.diffFiles; - if ( - !(gon?.features?.diffsBatchLoad && window.location.search.indexOf('diff_id') === -1) && - data.diff_files - ) { + if (window.location.search.indexOf('diff_id') !== -1 && data.diff_files) { files = prepareDiffData(data, files); } diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index c1449237f8a..b81aae7c257 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -22,6 +22,8 @@ export const TIME_DIFFERENCE_VALUE = 10; export const ASC = 'asc'; export const DESC = 'desc'; +export const DISCUSSION_FETCH_TIMEOUT = 750; + export const NOTEABLE_TYPE_MAPPING = { Issue: ISSUE_NOTEABLE_TYPE, MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE, diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index f6069b509e8..d7acb7d5e05 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -87,6 +87,7 @@ export const fetchDiscussions = ({ commit, dispatch }, { path, filter, persistFi return axios.get(path, config).then(({ data }) => { commit(types.SET_INITIAL_DISCUSSIONS, data); + commit(types.SET_FETCHING_DISCUSSIONS, false); dispatch('updateResolvableDiscussionsCounts'); }); @@ -136,6 +137,23 @@ export const updateNote = ({ commit, dispatch }, { endpoint, note }) => export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => { const { notesById } = getters; + const debouncedFetchDiscussions = isFetching => { + if (!isFetching) { + commit(types.SET_FETCHING_DISCUSSIONS, true); + dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); + } else { + if (isFetching !== true) { + clearTimeout(state.currentlyFetchingDiscussions); + } + + commit( + types.SET_FETCHING_DISCUSSIONS, + setTimeout(() => { + dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); + }, constants.DISCUSSION_FETCH_TIMEOUT), + ); + } + }; notes.forEach(note => { if (notesById[note.id]) { @@ -146,7 +164,7 @@ export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) if (discussion) { commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); } else if (note.type === constants.DIFF_NOTE) { - dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); + debouncedFetchDiscussions(state.currentlyFetchingDiscussions); } else { commit(types.ADD_NEW_NOTE, note); } diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 1649e63c61f..161c9b8b1b5 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -12,6 +12,7 @@ export default () => ({ lastFetchedAt: null, currentDiscussionId: null, batchSuggestionsInfo: [], + currentlyFetchingDiscussions: false, /** * selectedCommentPosition & selectedCommentPosition structures are the same as `position.line_range`: * { diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index eb3447291bc..23515cdd9e3 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -36,6 +36,7 @@ export const SET_CURRENT_DISCUSSION_ID = 'SET_CURRENT_DISCUSSION_ID'; export const SET_DISCUSSIONS_SORT = 'SET_DISCUSSIONS_SORT'; export const SET_SELECTED_COMMENT_POSITION = 'SET_SELECTED_COMMENT_POSITION'; export const SET_SELECTED_COMMENT_POSITION_HOVER = 'SET_SELECTED_COMMENT_POSITION_HOVER'; +export const SET_FETCHING_DISCUSSIONS = 'SET_FETCHING_DISCUSSIONS'; // Issue export const CLOSE_ISSUE = 'CLOSE_ISSUE'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index aa078f00569..a8bd94cc763 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -379,4 +379,7 @@ export default { [types.UPDATE_ASSIGNEES](state, assignees) { state.noteableData.assignees = assignees; }, + [types.SET_FETCHING_DISCUSSIONS](state, value) { + state.currentlyFetchingDiscussions = value; + }, }; diff --git a/app/assets/javascripts/pages/admin/clusters/new/index.js b/app/assets/javascripts/pages/admin/clusters/new/index.js new file mode 100644 index 00000000000..876bab0b339 --- /dev/null +++ b/app/assets/javascripts/pages/admin/clusters/new/index.js @@ -0,0 +1,5 @@ +import initNewCluster from '~/clusters/new_cluster'; + +document.addEventListener('DOMContentLoaded', () => { + initNewCluster(); +}); diff --git a/app/assets/javascripts/pages/groups/clusters/new/index.js b/app/assets/javascripts/pages/groups/clusters/new/index.js new file mode 100644 index 00000000000..876bab0b339 --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/new/index.js @@ -0,0 +1,5 @@ +import initNewCluster from '~/clusters/new_cluster'; + +document.addEventListener('DOMContentLoaded', () => { + initNewCluster(); +}); diff --git a/app/assets/javascripts/pages/projects/clusters/new/index.js b/app/assets/javascripts/pages/projects/clusters/new/index.js new file mode 100644 index 00000000000..876bab0b339 --- /dev/null +++ b/app/assets/javascripts/pages/projects/clusters/new/index.js @@ -0,0 +1,5 @@ +import initNewCluster from '~/clusters/new_cluster'; + +document.addEventListener('DOMContentLoaded', () => { + initNewCluster(); +}); diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss index 964dc33658b..943179100a3 100644 --- a/app/assets/stylesheets/fontawesome_custom.scss +++ b/app/assets/stylesheets/fontawesome_custom.scss @@ -166,10 +166,6 @@ content: '\f0c6'; } -.fa-tag::before { - content: '\f02b'; -} - .fa-arrow-up::before { content: '\f062'; } @@ -202,10 +198,6 @@ content: '\f016'; } -.fa-tags::before { - content: '\f02c'; -} - .fa-lightbulb-o::before { content: '\f0eb'; } diff --git a/app/controllers/admin/instance_statistics_controller.rb b/app/controllers/admin/instance_statistics_controller.rb index ad2fea5983d..30c49eb3e5f 100644 --- a/app/controllers/admin/instance_statistics_controller.rb +++ b/app/controllers/admin/instance_statistics_controller.rb @@ -7,6 +7,6 @@ class Admin::InstanceStatisticsController < Admin::ApplicationController end def check_feature_flag - render_404 unless Feature.enabled?(:instance_analytics) + render_404 unless Feature.enabled?(:instance_statistics) end end diff --git a/app/controllers/concerns/hooks_execution.rb b/app/controllers/concerns/hooks_execution.rb index e8add1f4055..ad1f8341109 100644 --- a/app/controllers/concerns/hooks_execution.rb +++ b/app/controllers/concerns/hooks_execution.rb @@ -17,4 +17,16 @@ module HooksExecution flash[:alert] = "Hook execution failed: #{message}" end end + + def create_rate_limit(key, scope) + if rate_limiter.throttled?(key, scope: [scope, current_user]) + rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) + + render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests + end + end + + def rate_limiter + ::Gitlab::ApplicationRateLimiter + end end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 1126d72bfef..aa02bc132f9 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -108,7 +108,7 @@ class InvitesController < ApplicationController Gitlab::Experimentation::EXPERIMENTS[:invite_email][:tracking_category], action, property: property, - value: Digest::MD5.hexdigest(member.to_global_id.to_s) + label: Digest::MD5.hexdigest(member.to_global_id.to_s) ) end end diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 064b2a2cc12..6ba32fd45b3 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -1,15 +1,14 @@ # frozen_string_literal: true class Profiles::NotificationsController < Profiles::ApplicationController + NOTIFICATIONS_PER_PAGE = 10 + # rubocop: disable CodeReuse/ActiveRecord def show @user = current_user - @group_notifications = current_user.notification_settings.preload_source_route.for_groups.order(:id) - @group_notifications += GroupsFinder.new( - current_user, - all_available: false, - exclude_group_ids: @group_notifications.select(:source_id) - ).execute.map { |group| current_user.notification_settings_for(group, inherit: true) } + @user_groups = user_groups + @group_notifications = user_groups.map { |group| current_user.notification_settings_for(group, inherit: true) } + @project_notifications = current_user.notification_settings.for_projects.order(:id) .preload_source_route .select { |notification| current_user.can?(:read_project, notification.source) } @@ -32,4 +31,10 @@ class Profiles::NotificationsController < Profiles::ApplicationController def user_params params.require(:user).permit(:notification_email, :notified_of_own_activity) end + + private + + def user_groups + GroupsFinder.new(current_user).execute.order_name_asc.page(params[:page]).per(NOTIFICATIONS_PER_PAGE) + end end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 097a357889f..2f4dc1ddc3a 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -6,6 +6,7 @@ class Projects::HooksController < Projects::ApplicationController # Authorize before_action :authorize_admin_project! before_action :hook_logs, only: :edit + before_action -> { create_rate_limit(:project_testing_hook, @project) }, only: :test respond_to :html diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index bceccc7063b..b8f14a82d96 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -21,15 +21,15 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic end def diffs_batch - return render_404 unless Feature.enabled?(:diffs_batch_load, @merge_request.project, default_enabled: true) - diffs = @compare.diffs_in_batch(params[:page], params[:per_page], diff_options: diff_options) positions = @merge_request.note_positions_for_paths(diffs.diff_file_paths, current_user) + environment = @merge_request.environments_for(current_user, latest: true).last diffs.unfold_diff_files(positions.unfoldable) diffs.write_cache options = { + environment: environment, merge_request: @merge_request, diff_view: diff_view, pagination_data: diffs.pagination_data diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 11d15692f7b..db7f17a78b4 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -25,7 +25,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] before_action only: [:show] do - push_frontend_feature_flag(:diffs_batch_load, @project, default_enabled: true) push_frontend_feature_flag(:deploy_from_footer, @project, default_enabled: true) push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline) push_frontend_feature_flag(:code_navigation, @project, default_enabled: true) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index df20daa8f7e..475ca8894e4 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -92,7 +92,7 @@ class Projects::TagsController < Projects::ApplicationController end format.js do - render status: :unprocessable_entity + render status: :ok end end end diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index b97e847c397..caad215e996 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -36,6 +36,12 @@ module ClustersHelper } end + def js_cluster_new + { + cluster_connect_help_path: help_page_path('user/project/clusters/add_remove_clusters', anchor: 'add-existing-cluster') + } + end + # This method is depreciated and will be removed when associated HAML files are moved to JavaScript def provider_icon(provider = nil) img_data = js_clusters_list_data.dig(:img_tags, provider&.to_sym) || diff --git a/app/models/concerns/enums/user_callout.rb b/app/models/concerns/enums/user_callout.rb index f91cc19d3ff..80df1ada9d9 100644 --- a/app/models/concerns/enums/user_callout.rb +++ b/app/models/concerns/enums/user_callout.rb @@ -23,7 +23,8 @@ module Enums web_ide_alert_dismissed: 16, personal_access_token_expiry: 21, # EE-only suggest_pipeline: 22, - customize_homepage: 23 + customize_homepage: 23, + feature_flags_new_version: 24 } end end diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb index 23319445a38..55ff4250c2d 100644 --- a/app/models/merge_request_diff_file.rb +++ b/app/models/merge_request_diff_file.rb @@ -25,6 +25,16 @@ class MergeRequestDiffFile < ApplicationRecord super end - binary? ? content.unpack1('m0') : content + return content unless binary? + + # If the data isn't valid base64, return it as-is, since it's almost certain + # to be a valid diff. Parsing it as a diff will fail if it's something else. + # + # https://gitlab.com/gitlab-org/gitlab/-/issues/240921 + begin + content.unpack1('m0') + rescue ArgumentError + content + end end end diff --git a/app/views/clusters/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml index 0a51d4b2e93..ff33fb46db8 100644 --- a/app/views/clusters/clusters/new.html.haml +++ b/app/views/clusters/clusters/new.html.haml @@ -16,8 +16,8 @@ %span = create_new_cluster_label(provider: params[:provider]) %li.nav-item{ role: 'presentation' } - %a.nav-link{ href: '#add-cluster-pane', id: 'add-cluster-tab', class: active_when(active_tab == 'add'), data: { toggle: 'tab' }, role: 'tab' } - %span Add existing cluster + %a.nav-link{ href: '#add-cluster-pane', id: 'add-cluster-tab', class: active_when(active_tab == 'add'), data: { toggle: 'tab', qa_selector: 'add_existing_cluster_tab' }, role: 'tab' } + %span= s_('ClusterIntegration|Connect existing cluster') .tab-content.gitlab-tab-content .tab-pane.p-0{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' } @@ -28,5 +28,5 @@ = render "clusters/clusters/#{provider}/new" .tab-pane{ id: 'add-cluster-pane', class: active_when(active_tab == 'add'), role: 'tabpanel' } - = render 'clusters/clusters/user/header' + #js-cluster-new{ data: js_cluster_new } = render 'clusters/clusters/user/form' diff --git a/app/views/clusters/clusters/user/_header.html.haml b/app/views/clusters/clusters/user/_header.html.haml deleted file mode 100644 index b0a24ee464f..00000000000 --- a/app/views/clusters/clusters/user/_header.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%h4 - = s_('ClusterIntegration|Enter the details for your Kubernetes cluster') -%p - - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/add_remove_clusters', anchor: 'add-existing-cluster'), target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes').html_safe % { link_to_help_page: link_to_help_page } diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index ab04d977a4d..da684c29372 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -39,10 +39,11 @@ %hr %h5 - = _('Groups (%{count})') % { count: @group_notifications.size } + = _('Groups (%{count})') % { count: @user_groups.total_count } %div - @group_notifications.each do |setting| = render 'group_settings', setting: setting, group: setting.source + = paginate @user_groups, theme: 'gitlab' %h5 = _('Projects (%{count})') % { count: @project_notifications.size } %p.account-well diff --git a/app/views/projects/commit/_limit_exceeded_message.html.haml b/app/views/projects/commit/_limit_exceeded_message.html.haml index ace1be787fb..236418ecd0e 100644 --- a/app/views/projects/commit/_limit_exceeded_message.html.haml +++ b/app/views/projects/commit/_limit_exceeded_message.html.haml @@ -3,6 +3,6 @@ - if objects == :branch = sprite_icon('fork', size: 12) - else - = icon('tag') + = sprite_icon('tag') .limit-message %span= _('%{label_for_message} unavailable') % { label_for_message: label_for_message.capitalize } diff --git a/changelogs/unreleased/208454-create-self-monitor-project-on-new-install.yml b/changelogs/unreleased/208454-create-self-monitor-project-on-new-install.yml new file mode 100644 index 00000000000..c996e3e9373 --- /dev/null +++ b/changelogs/unreleased/208454-create-self-monitor-project-on-new-install.yml @@ -0,0 +1,5 @@ +--- +title: Automatically create self monitoring project on new GitLab installations +merge_request: 40404 +author: +type: changed diff --git a/changelogs/unreleased/234066-create-issuelink-for-vulnerabilities-that-do-not-have-them.yml b/changelogs/unreleased/234066-create-issuelink-for-vulnerabilities-that-do-not-have-them.yml deleted file mode 100644 index 5cbc6a09587..00000000000 --- a/changelogs/unreleased/234066-create-issuelink-for-vulnerabilities-that-do-not-have-them.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Create IssueLink for Vulnerabilities that do not have them -merge_request: 39986 -author: -type: fixed diff --git a/changelogs/unreleased/240921-fix-merge-request-diff-file-invalid-base64.yml b/changelogs/unreleased/240921-fix-merge-request-diff-file-invalid-base64.yml new file mode 100644 index 00000000000..5975dafdd9b --- /dev/null +++ b/changelogs/unreleased/240921-fix-merge-request-diff-file-invalid-base64.yml @@ -0,0 +1,5 @@ +--- +title: Fix reading some merge request diffs +merge_request: 40598 +author: +type: fixed diff --git a/changelogs/unreleased/28459-paginate_profile_settings_group_notifications.yml b/changelogs/unreleased/28459-paginate_profile_settings_group_notifications.yml new file mode 100644 index 00000000000..1d177115242 --- /dev/null +++ b/changelogs/unreleased/28459-paginate_profile_settings_group_notifications.yml @@ -0,0 +1,5 @@ +--- +title: Paginate profile group notifications +merge_request: 40326 +author: +type: added diff --git a/changelogs/unreleased/emilyring-cluster-new-vue.yml b/changelogs/unreleased/emilyring-cluster-new-vue.yml new file mode 100644 index 00000000000..662e6eeaf62 --- /dev/null +++ b/changelogs/unreleased/emilyring-cluster-new-vue.yml @@ -0,0 +1,5 @@ +--- +title: Moved Cluster Connect Form to Vue +merge_request: 40295 +author: +type: changed diff --git a/changelogs/unreleased/id-fix-nil-line-codes-for-diff-positions.yml b/changelogs/unreleased/id-fix-nil-line-codes-for-diff-positions.yml deleted file mode 100644 index 1d7b495e8f0..00000000000 --- a/changelogs/unreleased/id-fix-nil-line-codes-for-diff-positions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid creating diff position when line-code is nil -merge_request: 40089 -author: -type: fixed diff --git a/changelogs/unreleased/nicolasdular-fix-invite-mail-tracking.yml b/changelogs/unreleased/nicolasdular-fix-invite-mail-tracking.yml new file mode 100644 index 00000000000..919a3e02bdc --- /dev/null +++ b/changelogs/unreleased/nicolasdular-fix-invite-mail-tracking.yml @@ -0,0 +1,5 @@ +--- +title: Fix snowplow tracking event error for new user invite page +merge_request: 40628 +author: +type: fixed diff --git a/changelogs/unreleased/security-223-webhook-dos-attack.yml b/changelogs/unreleased/security-223-webhook-dos-attack.yml new file mode 100644 index 00000000000..ef1ab2c2415 --- /dev/null +++ b/changelogs/unreleased/security-223-webhook-dos-attack.yml @@ -0,0 +1,5 @@ +--- +title: Add rate limit on webhooks testing feature +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-upgrade-jquery-3-5.yml b/changelogs/unreleased/security-upgrade-jquery-3-5.yml new file mode 100644 index 00000000000..d2a9a8fed6c --- /dev/null +++ b/changelogs/unreleased/security-upgrade-jquery-3-5.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade jquery to v3.5 +merge_request: +author: +type: security diff --git a/db/fixtures/development/98_gitlab_instance_administration_project.rb b/db/fixtures/development/98_gitlab_instance_administration_project.rb new file mode 100644 index 00000000000..8be707ffb08 --- /dev/null +++ b/db/fixtures/development/98_gitlab_instance_administration_project.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +response = ::Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute + +if response[:status] == :success + puts "Successfully created self monitoring project." +else + puts "Could not create self monitoring project due to error: '#{response[:message]}'" + puts "Check logs for more details." +end diff --git a/db/fixtures/production/998_gitlab_instance_administration_project.rb b/db/fixtures/production/998_gitlab_instance_administration_project.rb new file mode 100644 index 00000000000..7148c01121a --- /dev/null +++ b/db/fixtures/production/998_gitlab_instance_administration_project.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +::Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute + +if response[:status] == :success + puts "Successfully created self monitoring project." +else + puts "Could not create self monitoring project due to error: '#{response[:message]}'" + puts "Check logs for more details." +end diff --git a/db/post_migrate/20200811130433_create_missing_vulnerabilities_issue_links.rb b/db/post_migrate/20200811130433_create_missing_vulnerabilities_issue_links.rb deleted file mode 100644 index 9b1d706a38e..00000000000 --- a/db/post_migrate/20200811130433_create_missing_vulnerabilities_issue_links.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -class CreateMissingVulnerabilitiesIssueLinks < ActiveRecord::Migration[6.0] - class VulnerabilitiesFeedback < ActiveRecord::Base - include EachBatch - self.table_name = 'vulnerability_feedback' - end - - class VulnerabilitiesIssueLink < ActiveRecord::Base - self.table_name = 'vulnerability_issue_links' - LINK_TYPE_CREATED = 2 - end - - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - def up - VulnerabilitiesFeedback.where('issue_id IS NOT NULL').each_batch do |relation| - timestamp = Time.now - issue_links = relation - .joins("JOIN vulnerability_occurrences vo ON vo.project_id = vulnerability_feedback.project_id AND vo.report_type = vulnerability_feedback.category AND encode(vo.project_fingerprint, 'hex') = vulnerability_feedback.project_fingerprint") - .where('vo.vulnerability_id IS NOT NULL') - .pluck(:vulnerability_id, :issue_id) - .map do |v_id, i_id| - { - vulnerability_id: v_id, - issue_id: i_id, - link_type: VulnerabilitiesIssueLink::LINK_TYPE_CREATED, - created_at: timestamp, - updated_at: timestamp - } - end - - next if issue_links.empty? - - VulnerabilitiesIssueLink.insert_all( - issue_links, - returning: false, - unique_by: %i[vulnerability_id issue_id] - ) - end - end - - def down - end -end diff --git a/db/schema_migrations/20200811130433 b/db/schema_migrations/20200811130433 deleted file mode 100644 index 303468e8949..00000000000 --- a/db/schema_migrations/20200811130433 +++ /dev/null @@ -1 +0,0 @@ -e8fc0809b5bd3248dc625602deeaaef16e2db6b33d8eaf51fdcc1c67dee49e17
\ No newline at end of file diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index fdf895c42f2..35cc2c6fb5e 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -147,17 +147,18 @@ always take the latest SAST artifact available. ### Configure SAST in the UI -> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3659) in GitLab Ultimate 13.3. +> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3659) in GitLab Ultimate 13.3. +> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/232862) in GitLab Ultimate 13.4. -For a project that does not have a `.gitlab-ci.yml` file, you can enable SAST with a basic -configuration using the **SAST Configuration** page: +You can enable and configure SAST with a basic configuration using the **SAST Configuration** +page: 1. From the project's home page, go to **Security & Compliance** > **Configuration** in the left sidebar. -1. Click **Enable via Merge Request** on the Static Application Security Testing (SAST) row. -1. Enter the appropriate SAST details into the fields on the page. See [Available variables](#available-variables) - for a description of these variables. -1. Click **Create Merge Request**. +1. If the project does not have a `gitlab-ci.yml` file, click **Enable** in the Static Application Security Testing (SAST) row, otherwise click **Configure**. +1. Enter the custom SAST values, then click **Create Merge Request**. + + Custom values are stored in the `.gitlab-ci.yml` file. For variables not in the SAST Configuration page, their values are left unchanged. Default values are inherited from the GitLab SAST template. 1. Review and merge the merge request. ### Customizing the SAST settings diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb index ed963476524..4eeec534fc0 100644 --- a/lib/gitlab/application_rate_limiter.rb +++ b/lib/gitlab/application_rate_limiter.rb @@ -25,11 +25,13 @@ module Gitlab project_repositories_archive: { threshold: 5, interval: 1.minute }, project_generate_new_export: { threshold: -> { application_settings.project_export_limit }, interval: 1.minute }, project_import: { threshold: -> { application_settings.project_import_limit }, interval: 1.minute }, + project_testing_hook: { threshold: 5, interval: 1.minute }, play_pipeline_schedule: { threshold: 1, interval: 1.minute }, show_raw_controller: { threshold: -> { application_settings.raw_blob_request_limit }, interval: 1.minute }, group_export: { threshold: -> { application_settings.group_export_limit }, interval: 1.minute }, group_download_export: { threshold: -> { application_settings.group_download_export_limit }, interval: 1.minute }, - group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute } + group_import: { threshold: -> { application_settings.group_import_limit }, interval: 1.minute }, + group_testing_hook: { threshold: 5, interval: 1.minute } }.freeze end diff --git a/lib/gitlab/repository_set_cache.rb b/lib/gitlab/repository_set_cache.rb index 1e2d86b7ad2..69c1688767c 100644 --- a/lib/gitlab/repository_set_cache.rb +++ b/lib/gitlab/repository_set_cache.rb @@ -22,7 +22,7 @@ module Gitlab with do |redis| redis.multi do - redis.del(full_key) + redis.unlink(full_key) # Splitting into groups of 1000 prevents us from creating a too-long # Redis command diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ee9bc0fa54e..d3b30973caa 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5315,6 +5315,9 @@ msgstr "" msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters. %{linkStart}More information%{linkEnd}" msgstr "" +msgid "ClusterIntegration|Connect existing cluster" +msgstr "" + msgid "ClusterIntegration|Copy API URL" msgstr "" @@ -5693,7 +5696,7 @@ msgstr "" msgid "ClusterIntegration|Number of nodes must be a numerical value." msgstr "" -msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{linkStart}documentation%{linkEnd} on Kubernetes" msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" @@ -6056,9 +6059,6 @@ msgstr "" msgid "ClusterIntegration|can be used instead of a custom domain. " msgstr "" -msgid "ClusterIntegration|documentation" -msgstr "" - msgid "ClusterIntegration|installed via %{linkStart}Cloud Run%{linkEnd}" msgstr "" diff --git a/package.json b/package.json index 74d353cceef..55bd4db72f0 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "ipaddr.js": "^1.9.1", "jed": "^1.1.1", "jest-transform-graphql": "^2.1.0", - "jquery": "^3.4.1", + "jquery": "^3.5.0", "jquery-ujs": "1.2.2", "jquery.caret": "^0.3.1", "jquery.waitforimages": "^2.2.0", diff --git a/qa/qa/page/project/operations/kubernetes/add.rb b/qa/qa/page/project/operations/kubernetes/add.rb index 785791652ba..9a6ea99ac18 100644 --- a/qa/qa/page/project/operations/kubernetes/add.rb +++ b/qa/qa/page/project/operations/kubernetes/add.rb @@ -7,11 +7,11 @@ module QA module Kubernetes class Add < Page::Base view 'app/views/clusters/clusters/new.html.haml' do - element :add_existing_cluster_button, "Add existing cluster" # rubocop:disable QA/ElementWithPattern + element :add_existing_cluster_tab end def add_existing_cluster - click_on 'Add existing cluster' + click_element(:add_existing_cluster_tab) end end end diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index fa9ec7413f8..a069a5dc050 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -10,6 +10,7 @@ RSpec.describe InvitesController do let(:md5_member_global_id) { Digest::MD5.hexdigest(member.to_global_id.to_s) } before do + stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: 'localhost') controller.instance_variable_set(:@member, member) sign_in(user) end @@ -51,17 +52,17 @@ RSpec.describe InvitesController do let(:params) { { id: token, new_user_invite: 'experiment' } } it 'tracks the user as experiment group' do - expect(Gitlab::Tracking).to receive(:event).with( + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( 'Growth::Acquisition::Experiment::InviteEmail', 'opened', property: 'experiment_group', - value: md5_member_global_id + label: md5_member_global_id ) - expect(Gitlab::Tracking).to receive(:event).with( + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( 'Growth::Acquisition::Experiment::InviteEmail', 'accepted', property: 'experiment_group', - value: md5_member_global_id + label: md5_member_global_id ) request @@ -72,17 +73,17 @@ RSpec.describe InvitesController do let(:params) { { id: token, new_user_invite: 'control' } } it 'tracks the user as control group' do - expect(Gitlab::Tracking).to receive(:event).with( + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( 'Growth::Acquisition::Experiment::InviteEmail', 'opened', property: 'control_group', - value: md5_member_global_id + label: md5_member_global_id ) - expect(Gitlab::Tracking).to receive(:event).with( + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( 'Growth::Acquisition::Experiment::InviteEmail', 'accepted', property: 'control_group', - value: md5_member_global_id + label: md5_member_global_id ) request @@ -107,11 +108,11 @@ RSpec.describe InvitesController do let(:params) { { id: token, new_user_invite: 'experiment' } } it 'tracks the user as experiment group' do - expect(Gitlab::Tracking).to receive(:event).with( + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( 'Growth::Acquisition::Experiment::InviteEmail', 'accepted', property: 'experiment_group', - value: md5_member_global_id + label: md5_member_global_id ) request @@ -122,11 +123,11 @@ RSpec.describe InvitesController do let(:params) { { id: token, new_user_invite: 'control' } } it 'tracks the user as control group' do - expect(Gitlab::Tracking).to receive(:event).with( + expect(Gitlab::Tracking).to receive(:event).and_call_original.with( 'Growth::Acquisition::Experiment::InviteEmail', 'accepted', property: 'control_group', - value: md5_member_global_id + label: md5_member_global_id ) request diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb index 40b4c8f0371..b320c909cc5 100644 --- a/spec/controllers/profiles/notifications_controller_spec.rb +++ b/spec/controllers/profiles/notifications_controller_spec.rb @@ -53,6 +53,22 @@ RSpec.describe Profiles::NotificationsController do end end + context 'with group notifications' do + let_it_be(:group) { create(:group) } + let_it_be(:subgroups) { create_list(:group, 10, parent: group) } + + before do + group.add_developer(user) + sign_in(user) + stub_const('Profiles::NotificationsController::NOTIFICATIONS_PER_PAGE', 5) + get :show + end + + it 'paginates the groups' do + expect(assigns(:group_notifications).count).to eq(5) + end + end + context 'with project notifications' do let!(:notification_setting) { create(:notification_setting, source: project, user: user, level: :watch) } diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index 85d036486ee..bd543cebeec 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -47,4 +47,26 @@ RSpec.describe Projects::HooksController do expect(ProjectHook.first).to have_attributes(hook_params) end end + + describe '#test' do + let(:hook) { create(:project_hook, project: project) } + + context 'when the endpoint receives requests above the limit' do + before do + allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits) + .and_return(project_testing_hook: { threshold: 1, interval: 1.minute }) + end + + it 'prevents making test requests' do + expect_next_instance_of(TestHooks::ProjectService) do |service| + expect(service).to receive(:execute).and_return(http_status: 200) + end + + 2.times { post :test, params: { namespace_id: project.namespace, project_id: project, id: hook } } + + expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.')) + expect(response).to have_gitlab_http_status(:too_many_requests) + end + end + end end diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb index 217447c2ad6..09e5196cb52 100644 --- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb @@ -414,6 +414,7 @@ RSpec.describe Projects::MergeRequests::DiffsController do def collection_arguments(pagination_data = {}) { + environment: nil, merge_request: merge_request, diff_view: :inline, pagination_data: { @@ -439,18 +440,6 @@ RSpec.describe Projects::MergeRequests::DiffsController do it_behaves_like '404 for unexistent diffable' - context 'when feature is disabled' do - before do - stub_feature_flags(diffs_batch_load: false) - end - - it 'returns 404' do - go - - expect(response).to have_gitlab_http_status(:not_found) - end - end - context 'when not authorized' do let(:other_user) { create(:user) } diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb index c6e5da92160..90253451d6b 100644 --- a/spec/features/groups/clusters/user_spec.rb +++ b/spec/features/groups/clusters/user_spec.rb @@ -26,7 +26,7 @@ RSpec.describe 'User Cluster', :js do visit group_clusters_path(group) click_link 'Add Kubernetes cluster' - click_link 'Add existing cluster' + click_link 'Connect existing cluster' end context 'when user filled form with valid parameters' do diff --git a/spec/features/merge_request/batch_comments_spec.rb b/spec/features/merge_request/batch_comments_spec.rb index 60671213d75..40f6482c948 100644 --- a/spec/features/merge_request/batch_comments_spec.rb +++ b/spec/features/merge_request/batch_comments_spec.rb @@ -20,8 +20,6 @@ RSpec.describe 'Merge request > Batch comments', :js do context 'Feature is enabled' do before do - stub_feature_flags(diffs_batch_load: false) - visit_diffs end diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb index d3867a91846..fc17ef011c2 100644 --- a/spec/features/merge_request/user_expands_diff_spec.rb +++ b/spec/features/merge_request/user_expands_diff_spec.rb @@ -7,8 +7,6 @@ RSpec.describe 'User expands diff', :js do let(:merge_request) { create(:merge_request, source_branch: 'expand-collapse-files', source_project: project, target_project: project) } before do - stub_feature_flags(diffs_batch_load: false) - allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes) allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes) @@ -18,7 +16,7 @@ RSpec.describe 'User expands diff', :js do end it 'allows user to expand diff' do - page.within find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9"]') do + page.within find('[id="19763941ab80e8c09871c0a425f0560d9053bcb3"]') do click_link 'Click to expand it.' wait_for_requests diff --git a/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb b/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb index 9a79a80abe9..782a7e3bfb6 100644 --- a/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb +++ b/spec/features/merge_request/user_interacts_with_batched_mr_diffs_spec.rb @@ -10,8 +10,6 @@ RSpec.describe 'Batch diffs', :js do let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'empty-branch') } before do - stub_feature_flags(diffs_batch_load: true) - sign_in(project.owner) visit diffs_project_merge_request_path(merge_request.project, merge_request) diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb index f2adfd21e49..cd06886169d 100644 --- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb +++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb @@ -15,10 +15,6 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do diff_refs: merge_request.diff_refs) end - before do - stub_feature_flags(diffs_batch_load: false) - end - context 'no threads' do before do project.add_maintainer(user) diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb index 7fad805866b..d15d5b3bc73 100644 --- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb +++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb @@ -20,7 +20,6 @@ RSpec.describe 'Merge request > User sees avatars on diff notes', :js do let!(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position) } before do - stub_feature_flags(diffs_batch_load: false) project.add_maintainer(user) sign_in user diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 93960c6ad3e..e38f9e3b142 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -9,10 +9,6 @@ RSpec.describe 'Merge request > User sees diff', :js do let(:project) { create(:project, :public, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } - before do - stub_feature_flags(diffs_batch_load: false) - end - context 'when linking to note' do describe 'with unresolved note' do let(:note) { create :diff_note_on_merge_request, project: project, noteable: merge_request } diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index 60e054ddbee..fb616ceae9d 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -17,8 +17,6 @@ RSpec.describe 'Merge request > User sees versions', :js do let!(:params) { {} } before do - stub_feature_flags(diffs_batch_load: false) - project.add_maintainer(user) sign_in(user) visit diffs_project_merge_request_path(project, merge_request, params) diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb index 537c0473fa4..928755bf5de 100644 --- a/spec/features/merge_request/user_views_diffs_spec.rb +++ b/spec/features/merge_request/user_views_diffs_spec.rb @@ -11,7 +11,6 @@ RSpec.describe 'User views diffs', :js do let(:view) { 'inline' } before do - stub_feature_flags(diffs_batch_load: false) visit(diffs_project_merge_request_path(project, merge_request, view: view)) wait_for_requests @@ -62,7 +61,7 @@ RSpec.describe 'User views diffs', :js do end it 'expands all diffs' do - first('#a5cc2925ca8258af241be7e5b0381edf30266302 .js-file-title').click + first('.js-file-title').click expect(page).to have_button('Expand all') diff --git a/spec/features/merge_requests/user_views_diffs_commit_spec.rb b/spec/features/merge_requests/user_views_diffs_commit_spec.rb index fcaabf9b0e7..cf92603972e 100644 --- a/spec/features/merge_requests/user_views_diffs_commit_spec.rb +++ b/spec/features/merge_requests/user_views_diffs_commit_spec.rb @@ -10,7 +10,6 @@ RSpec.describe 'User views diff by commit', :js do let(:project) { create(:project, :public, :repository) } before do - stub_feature_flags(diffs_batch_load: false) visit(diffs_project_merge_request_path(project, merge_request, commit_id: merge_request.diff_head_sha)) end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 63e5546b43c..04339d20d77 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -144,7 +144,7 @@ RSpec.describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do visit project_clusters_path(project) click_link 'Add Kubernetes cluster' - click_link 'Add existing cluster' + click_link 'Connect existing cluster' end it 'user sees the "Environment scope" field' do diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 450eaa7f004..9d0dc65093e 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -26,7 +26,7 @@ RSpec.describe 'User Cluster', :js do visit project_clusters_path(project) click_link 'Add Kubernetes cluster' - click_link 'Add existing cluster' + click_link 'Connect existing cluster' end context 'when user filled form with valid parameters' do diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index c56a1ed1711..d674fbc457e 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'Clusters', :js do context 'when user filled form with environment scope' do before do click_link 'Add Kubernetes cluster' - click_link 'Add existing cluster' + click_link 'Connect existing cluster' fill_in 'cluster_name', with: 'staging-cluster' fill_in 'cluster_environment_scope', with: 'staging/*' click_button 'Add Kubernetes cluster' @@ -72,7 +72,7 @@ RSpec.describe 'Clusters', :js do context 'when user updates duplicated environment scope' do before do click_link 'Add Kubernetes cluster' - click_link 'Add existing cluster' + click_link 'Connect existing cluster' fill_in 'cluster_name', with: 'staging-cluster' fill_in 'cluster_environment_scope', with: '*' fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'https://0.0.0.0' diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb index 6f78f888c12..d220db01c24 100644 --- a/spec/features/projects/view_on_env_spec.rb +++ b/spec/features/projects/view_on_env_spec.rb @@ -9,8 +9,6 @@ RSpec.describe 'View on environment', :js do let(:user) { project.creator } before do - stub_feature_flags(diffs_batch_load: false) - project.add_maintainer(user) end diff --git a/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap new file mode 100644 index 00000000000..5577176bcc5 --- /dev/null +++ b/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NewCluster renders the cluster component correctly 1`] = ` +"<div> + <h4>Enter the details for your Kubernetes cluster</h4> + <p>Please enter access information for your Kubernetes cluster. If you need help, you can read our <b-link-stub href=\\"/some/help/path\\" target=\\"_blank\\" event=\\"click\\" routertag=\\"a\\" class=\\"gl-link\\">documentation</b-link-stub> on Kubernetes</p> +</div>" +`; diff --git a/spec/frontend/clusters/components/new_cluster_spec.js b/spec/frontend/clusters/components/new_cluster_spec.js new file mode 100644 index 00000000000..bb4898f98ba --- /dev/null +++ b/spec/frontend/clusters/components/new_cluster_spec.js @@ -0,0 +1,41 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLink, GlSprintf } from '@gitlab/ui'; +import NewCluster from '~/clusters/components/new_cluster.vue'; +import createClusterStore from '~/clusters/stores/new_cluster'; + +describe('NewCluster', () => { + let store; + let wrapper; + + const createWrapper = () => { + store = createClusterStore({ clusterConnectHelpPath: '/some/help/path' }); + wrapper = shallowMount(NewCluster, { store, stubs: { GlLink, GlSprintf } }); + return wrapper.vm.$nextTick(); + }; + + const findDescription = () => wrapper.find(GlSprintf); + + const findLink = () => wrapper.find(GlLink); + + beforeEach(() => { + return createWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders the cluster component correctly', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('renders the correct information text', () => { + expect(findDescription().text()).toContain( + 'Please enter access information for your Kubernetes cluster.', + ); + }); + + it('renders a valid help link set by the backend', () => { + expect(findLink().attributes('href')).toBe('/some/help/path'); + }); +}); diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index dea2108b393..5c2b684e165 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -108,7 +108,6 @@ describe('diffs/components/app', () => { }; jest.spyOn(window, 'requestIdleCallback').mockImplementation(fn => fn()); createComponent(); - jest.spyOn(wrapper.vm, 'fetchDiffFiles').mockImplementation(fetchResolver); jest.spyOn(wrapper.vm, 'fetchDiffFilesMeta').mockImplementation(fetchResolver); jest.spyOn(wrapper.vm, 'fetchDiffFilesBatch').mockImplementation(fetchResolver); jest.spyOn(wrapper.vm, 'fetchCoverageFiles').mockImplementation(fetchResolver); @@ -139,22 +138,10 @@ describe('diffs/components/app', () => { parallel_diff_lines: ['line'], }; - function expectFetchToOccur({ - vueInstance, - done = () => {}, - batch = false, - existingFiles = 1, - } = {}) { + function expectFetchToOccur({ vueInstance, done = () => {}, existingFiles = 1 } = {}) { vueInstance.$nextTick(() => { expect(vueInstance.diffFiles.length).toEqual(existingFiles); - - if (!batch) { - expect(vueInstance.fetchDiffFiles).toHaveBeenCalled(); - expect(vueInstance.fetchDiffFilesBatch).not.toHaveBeenCalled(); - } else { - expect(vueInstance.fetchDiffFiles).not.toHaveBeenCalled(); - expect(vueInstance.fetchDiffFilesBatch).toHaveBeenCalled(); - } + expect(vueInstance.fetchDiffFilesBatch).toHaveBeenCalled(); done(); }); @@ -165,7 +152,7 @@ describe('diffs/components/app', () => { store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: false, existingFiles: 0, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, existingFiles: 0, done }); }); it('fetches diffs if it has both view styles, but no lines in either', done => { @@ -196,89 +183,46 @@ describe('diffs/components/app', () => { }); it('fetches batch diffs if it has none', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, existingFiles: 0, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, existingFiles: 0, done }); }); it('fetches batch diffs if it has both view styles, but no lines in either', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(noLinesDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, done }); }); it('fetches batch diffs if it only has inline view style', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(inlineLinesDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done }); + expectFetchToOccur({ vueInstance: wrapper.vm, done }); }); it('fetches batch diffs if it only has parallel view style', done => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(parallelLinesDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done }); - }); - - it('does not fetch diffs if it has already fetched both styles of diff', () => { - wrapper.vm.glFeatures.diffsBatchLoad = false; - - store.state.diffs.diffFiles.push(fullDiff); - store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); - - expect(wrapper.vm.diffFiles.length).toEqual(1); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled(); + expectFetchToOccur({ vueInstance: wrapper.vm, done }); }); it('does not fetch batch diffs if it has already fetched both styles of diff', () => { - wrapper.vm.glFeatures.diffsBatchLoad = true; - store.state.diffs.diffFiles.push(fullDiff); store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType); expect(wrapper.vm.diffFiles.length).toEqual(1); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled(); - }); - }); - - it('calls fetchDiffFiles if diffsBatchLoad is not enabled', done => { - expect(wrapper.vm.diffFilesLength).toEqual(0); - wrapper.vm.glFeatures.diffsBatchLoad = false; - wrapper.vm.fetchData(false); - - expect(wrapper.vm.fetchDiffFiles).toHaveBeenCalled(); - setImmediate(() => { - expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesMeta).not.toHaveBeenCalled(); expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled(); - expect(wrapper.vm.fetchCoverageFiles).toHaveBeenCalled(); - expect(wrapper.vm.unwatchDiscussions).toHaveBeenCalled(); - expect(wrapper.vm.diffFilesLength).toEqual(100); - expect(wrapper.vm.unwatchRetrievingBatches).toHaveBeenCalled(); - - done(); }); }); it('calls batch methods if diffsBatchLoad is enabled, and not latest version', done => { expect(wrapper.vm.diffFilesLength).toEqual(0); - wrapper.vm.glFeatures.diffsBatchLoad = true; wrapper.vm.isLatestVersion = () => false; wrapper.vm.fetchData(false); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); setImmediate(() => { expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled(); expect(wrapper.vm.fetchDiffFilesMeta).toHaveBeenCalled(); @@ -293,10 +237,8 @@ describe('diffs/components/app', () => { it('calls batch methods if diffsBatchLoad is enabled, and latest version', done => { expect(wrapper.vm.diffFilesLength).toEqual(0); - wrapper.vm.glFeatures.diffsBatchLoad = true; wrapper.vm.fetchData(false); - expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); setImmediate(() => { expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled(); expect(wrapper.vm.fetchDiffFilesMeta).toHaveBeenCalled(); diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index 2a83305f29a..858f362d3df 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -13,7 +13,6 @@ import { } from '~/diffs/constants'; import { setBaseConfig, - fetchDiffFiles, fetchDiffFilesBatch, fetchDiffFilesMeta, fetchCoverageFiles, @@ -142,32 +141,6 @@ describe('DiffsStoreActions', () => { }); }); - describe('fetchDiffFiles', () => { - it('should fetch diff files', done => { - const endpoint = '/fetch/diff/files?view=inline&w=1'; - const mock = new MockAdapter(axios); - const res = { diff_files: 1, merge_request_diffs: [] }; - mock.onGet(endpoint).reply(200, res); - - testAction( - fetchDiffFiles, - {}, - { endpoint, diffFiles: [], showWhitespace: false, diffViewType: 'inline' }, - [ - { type: types.SET_LOADING, payload: true }, - { type: types.SET_LOADING, payload: false }, - { type: types.SET_MERGE_REQUEST_DIFFS, payload: res.merge_request_diffs }, - { type: types.SET_DIFF_DATA, payload: res }, - ], - [], - () => { - mock.restore(); - done(); - }, - ); - }); - }); - describe('fetchDiffFilesBatch', () => { let mock; diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js index 705005b809c..ce98bc58af7 100644 --- a/spec/frontend/diffs/store/mutations_spec.js +++ b/spec/frontend/diffs/store/mutations_spec.js @@ -68,12 +68,13 @@ describe('DiffsStoreMutations', () => { }); describe('SET_DIFF_DATA', () => { - it('should set diff data type properly', () => { + it('should not modify the existing state', () => { const state = { diffFiles: [ { - ...diffFileMockData, - parallel_diff_lines: [], + content_sha: diffFileMockData.content_sha, + file_hash: diffFileMockData.file_hash, + highlighted_diff_lines: [], }, ], }; @@ -83,43 +84,7 @@ describe('DiffsStoreMutations', () => { mutations[types.SET_DIFF_DATA](state, diffMock); - const firstLine = state.diffFiles[0].parallel_diff_lines[0]; - - expect(firstLine.right.text).toBeUndefined(); - expect(state.diffFiles.length).toEqual(1); - expect(state.diffFiles[0].renderIt).toEqual(true); - expect(state.diffFiles[0].collapsed).toEqual(false); - }); - - describe('given diffsBatchLoad feature flag is enabled', () => { - beforeEach(() => { - gon.features = { diffsBatchLoad: true }; - }); - - afterEach(() => { - delete gon.features; - }); - - it('should not modify the existing state', () => { - const state = { - diffFiles: [ - { - content_sha: diffFileMockData.content_sha, - file_hash: diffFileMockData.file_hash, - highlighted_diff_lines: [], - }, - ], - }; - const diffMock = { - diff_files: [diffFileMockData], - }; - - mutations[types.SET_DIFF_DATA](state, diffMock); - - // If the batch load is enabled, there shouldn't be any processing - // done on the existing state object, so we shouldn't have this. - expect(state.diffFiles[0].parallel_diff_lines).toBeUndefined(); - }); + expect(state.diffFiles[0].parallel_diff_lines).toBeUndefined(); }); }); diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js index 6b8d0790669..52eea99ce8c 100644 --- a/spec/frontend/notes/stores/actions_spec.js +++ b/spec/frontend/notes/stores/actions_spec.js @@ -3,6 +3,7 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import Api from '~/api'; import { deprecatedCreateFlash as Flash } from '~/flash'; import * as actions from '~/notes/stores/actions'; +import mutations from '~/notes/stores/mutations'; import * as mutationTypes from '~/notes/stores/mutation_types'; import * as notesConstants from '~/notes/constants'; import createStore from '~/notes/stores'; @@ -651,6 +652,26 @@ describe('Actions Notes Store', () => { }); describe('updateOrCreateNotes', () => { + it('Prevents `fetchDiscussions` being called multiple times within time limit', () => { + jest.useFakeTimers(); + const note = { id: 1234, type: notesConstants.DIFF_NOTE }; + const getters = { notesById: {} }; + state = { discussions: [note], notesData: { discussionsPath: '' } }; + commit.mockImplementation((type, value) => { + if (type === mutationTypes.SET_FETCHING_DISCUSSIONS) { + mutations[type](state, value); + } + }); + + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]); + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]); + + jest.runAllTimers(); + actions.updateOrCreateNotes({ commit, state, getters, dispatch }, [note]); + + expect(dispatch).toHaveBeenCalledTimes(2); + }); + it('Updates existing note', () => { const note = { id: 1234 }; const getters = { notesById: { 1234: note } }; diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb index dff83005c89..6164f3b5e8d 100644 --- a/spec/helpers/clusters_helper_spec.rb +++ b/spec/helpers/clusters_helper_spec.rb @@ -77,7 +77,15 @@ RSpec.describe ClustersHelper do end it 'displays and ancestor_help_path' do - expect(subject[:ancestor_help_path]).to eq('/help/user/group/clusters/index#cluster-precedence') + expect(subject[:ancestor_help_path]).to eq(help_page_path('user/group/clusters/index', anchor: 'cluster-precedence')) + end + end + + describe '#js_cluster_new' do + subject { helper.js_cluster_new } + + it 'displays a cluster_connect_help_path' do + expect(subject[:cluster_connect_help_path]).to eq(help_page_path('user/project/clusters/add_remove_clusters', anchor: 'add-existing-cluster')) end end diff --git a/spec/migrations/20200811130433_create_missing_vulnerabilities_issue_links_spec.rb b/spec/migrations/20200811130433_create_missing_vulnerabilities_issue_links_spec.rb deleted file mode 100644 index c7fb1d93bbc..00000000000 --- a/spec/migrations/20200811130433_create_missing_vulnerabilities_issue_links_spec.rb +++ /dev/null @@ -1,145 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20200811130433_create_missing_vulnerabilities_issue_links.rb') - -RSpec.describe CreateMissingVulnerabilitiesIssueLinks, :migration do - let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } - let(:users) { table(:users) } - let(:user) { create_user! } - let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) } - let(:scanners) { table(:vulnerability_scanners) } - let(:scanner) { scanners.create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') } - let(:different_scanner) { scanners.create!(project_id: project.id, external_id: 'test 2', name: 'test scanner 2') } - let(:issues) { table(:issues) } - let(:issue1) { issues.create!(id: 123, project_id: project.id) } - let(:issue2) { issues.create!(id: 124, project_id: project.id) } - let(:vulnerabilities) { table(:vulnerabilities) } - let(:vulnerabilities_findings) { table(:vulnerability_occurrences) } - let(:vulnerability_feedback) { table(:vulnerability_feedback) } - let(:vulnerability_issue_links) { table(:vulnerability_issue_links) } - let(:vulnerability_identifiers) { table(:vulnerability_identifiers) } - let(:vulnerability_identifier) { vulnerability_identifiers.create!(project_id: project.id, external_type: 'test 1', external_id: 'test 1', fingerprint: 'test 1', name: 'test 1') } - let(:different_vulnerability_identifier) { vulnerability_identifiers.create!(project_id: project.id, external_type: 'test 2', external_id: 'test 2', fingerprint: 'test 2', name: 'test 2') } - - let!(:vulnerability) do - create_vulnerability!( - project_id: project.id, - author_id: user.id - ) - end - - before do - create_finding!( - vulnerability_id: vulnerability.id, - project_id: project.id, - scanner_id: scanner.id, - primary_identifier_id: vulnerability_identifier.id - ) - create_feedback!( - issue_id: issue1.id, - project_id: project.id, - author_id: user.id - ) - - # Create a finding with no vulnerability_id - # https://gitlab.com/gitlab-com/gl-infra/production/-/issues/2539 - create_finding!( - vulnerability_id: nil, - project_id: project.id, - scanner_id: different_scanner.id, - primary_identifier_id: different_vulnerability_identifier.id, - location_fingerprint: 'somewhereinspace', - uuid: 'test2' - ) - create_feedback!( - category: 2, - issue_id: issue2.id, - project_id: project.id, - author_id: user.id - ) - end - - context 'with no Vulnerabilities::IssueLinks present' do - it 'creates missing Vulnerabilities::IssueLinks' do - expect(vulnerability_issue_links.count).to eq(0) - - migrate! - - expect(vulnerability_issue_links.count).to eq(1) - end - end - - context 'when an Vulnerabilities::IssueLink already exists' do - before do - vulnerability_issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue1.id) - end - - it 'creates no duplicates' do - expect(vulnerability_issue_links.count).to eq(1) - - migrate! - - expect(vulnerability_issue_links.count).to eq(1) - end - end - - private - - def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0) - vulnerabilities.create!( - project_id: project_id, - author_id: author_id, - title: title, - severity: severity, - confidence: confidence, - report_type: report_type - ) - end - - # rubocop:disable Metrics/ParameterLists - def create_finding!( - vulnerability_id:, project_id:, scanner_id:, primary_identifier_id:, - name: "test", severity: 7, confidence: 7, report_type: 0, - project_fingerprint: '123qweasdzxc', location_fingerprint: 'test', - metadata_version: 'test', raw_metadata: 'test', uuid: 'test') - vulnerabilities_findings.create!( - vulnerability_id: vulnerability_id, - project_id: project_id, - name: name, - severity: severity, - confidence: confidence, - report_type: report_type, - project_fingerprint: project_fingerprint, - scanner_id: scanner.id, - primary_identifier_id: vulnerability_identifier.id, - location_fingerprint: location_fingerprint, - metadata_version: metadata_version, - raw_metadata: raw_metadata, - uuid: uuid - ) - end - # rubocop:enable Metrics/ParameterLists - - # project_fingerprint on Vulnerabilities::Finding is a bytea and we need to match this - def create_feedback!(issue_id:, project_id:, author_id:, feedback_type: 1, category: 0, project_fingerprint: '3132337177656173647a7863') - vulnerability_feedback.create!( - feedback_type: feedback_type, - issue_id: issue_id, - category: category, - project_fingerprint: project_fingerprint, - project_id: project_id, - author_id: author_id - ) - end - - def create_user!(name: "Example User", email: "user@example.com", user_type: nil, created_at: Time.now, confirmed_at: Time.now) - users.create!( - name: name, - email: email, - username: name, - projects_limit: 0, - user_type: user_type, - confirmed_at: confirmed_at - ) - end -end diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb index 25971f63338..d22a22d4518 100644 --- a/spec/models/merge_request_diff_file_spec.rb +++ b/spec/models/merge_request_diff_file_spec.rb @@ -25,6 +25,14 @@ RSpec.describe MergeRequestDiffFile do it 'unpacks from base 64' do expect(subject.diff).to eq(unpacked) end + + context 'invalid base64' do + let(:packed) { '---/dev/null' } + + it 'returns the raw diff' do + expect(subject.diff).to eq(packed) + end + end end context 'when the diff is not marked as binary' do diff --git a/spec/requests/profiles/notifications_controller_spec.rb b/spec/requests/profiles/notifications_controller_spec.rb index d60cee00aef..633375de6aa 100644 --- a/spec/requests/profiles/notifications_controller_spec.rb +++ b/spec/requests/profiles/notifications_controller_spec.rb @@ -25,7 +25,8 @@ RSpec.describe 'view user notifications' do end describe 'GET /profile/notifications' do - it 'avoid N+1 due to an additional groups (with no parent group)' do + # To be fixed in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40457 + it 'has an N+1 due to an additional groups (with no parent group) - but should not' do get_profile_notifications control = ActiveRecord::QueryRecorder.new do @@ -36,7 +37,7 @@ RSpec.describe 'view user notifications' do expect do get_profile_notifications - end.not_to exceed_query_limit(control) + end.to exceed_query_limit(control) end end end diff --git a/yarn.lock b/yarn.lock index 50dbab0db5b..69a37601beb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7078,10 +7078,10 @@ jquery.waitforimages@^2.2.0: resolved "https://registry.yarnpkg.com/jquery.waitforimages/-/jquery.waitforimages-2.2.0.tgz#63f23131055a1b060dc913e6d874bcc9b9e6b16b" integrity sha1-Y/IxMQVaGwYNyRPm2HS8ybnmsWs= -"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" - integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== +"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^3.5.0: + version "3.5.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" + integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== js-base64@^2.1.8: version "2.5.1" |