Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-08-28 15:10:21 +00:00
parent 9f8061811b
commit c41b66bd05
80 changed files with 432 additions and 528 deletions

View file

@ -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)

View file

@ -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>

View file

@ -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);
},
});
};

View file

@ -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;

View file

@ -0,0 +1,3 @@
export default (initialState = {}) => ({
clusterConnectHelpPath: initialState.clusterConnectHelpPath,
});

View file

@ -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.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();
}
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();

View file

@ -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;

View file

@ -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);
}

View file

@ -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,

View file

@ -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);
}

View file

@ -12,6 +12,7 @@ export default () => ({
lastFetchedAt: null,
currentDiscussionId: null,
batchSuggestionsInfo: [],
currentlyFetchingDiscussions: false,
/**
* selectedCommentPosition & selectedCommentPosition structures are the same as `position.line_range`:
* {

View file

@ -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';

View file

@ -379,4 +379,7 @@ export default {
[types.UPDATE_ASSIGNEES](state, assignees) {
state.noteableData.assignees = assignees;
},
[types.SET_FETCHING_DISCUSSIONS](state, value) {
state.currentlyFetchingDiscussions = value;
},
};

View file

@ -0,0 +1,5 @@
import initNewCluster from '~/clusters/new_cluster';
document.addEventListener('DOMContentLoaded', () => {
initNewCluster();
});

View file

@ -0,0 +1,5 @@
import initNewCluster from '~/clusters/new_cluster';
document.addEventListener('DOMContentLoaded', () => {
initNewCluster();
});

View file

@ -0,0 +1,5 @@
import initNewCluster from '~/clusters/new_cluster';
document.addEventListener('DOMContentLoaded', () => {
initNewCluster();
});

View file

@ -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';
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -92,7 +92,7 @@ class Projects::TagsController < Projects::ApplicationController
end
format.js do
render status: :unprocessable_entity
render status: :ok
end
end
end

View file

@ -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) ||

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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 }

View file

@ -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

View file

@ -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 }

View file

@ -0,0 +1,5 @@
---
title: Automatically create self monitoring project on new GitLab installations
merge_request: 40404
author:
type: changed

View file

@ -1,5 +0,0 @@
---
title: Create IssueLink for Vulnerabilities that do not have them
merge_request: 39986
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix reading some merge request diffs
merge_request: 40598
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Paginate profile group notifications
merge_request: 40326
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Moved Cluster Connect Form to Vue
merge_request: 40295
author:
type: changed

View file

@ -1,5 +0,0 @@
---
title: Avoid creating diff position when line-code is nil
merge_request: 40089
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix snowplow tracking event error for new user invite page
merge_request: 40628
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add rate limit on webhooks testing feature
merge_request:
author:
type: security

View file

@ -0,0 +1,5 @@
---
title: Upgrade jquery to v3.5
merge_request:
author:
type: security

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
e8fc0809b5bd3248dc625602deeaaef16e2db6b33d8eaf51fdcc1c67dee49e17

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 ""

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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) }

View file

@ -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

View file

@ -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) }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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 }

View file

@ -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)

View file

@ -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')

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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>"
`;

View file

@ -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');
});
});

View file

@ -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();

View file

@ -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;

View file

@ -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();
});
});

View file

@ -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 } };

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"