Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9f8061811b
commit
c41b66bd05
80 changed files with 432 additions and 528 deletions
|
@ -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)
|
||||
|
|
34
app/assets/javascripts/clusters/components/new_cluster.vue
Normal file
34
app/assets/javascripts/clusters/components/new_cluster.vue
Normal 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>
|
19
app/assets/javascripts/clusters/new_cluster.js
Normal file
19
app/assets/javascripts/clusters/new_cluster.js
Normal 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);
|
||||
},
|
||||
});
|
||||
};
|
12
app/assets/javascripts/clusters/stores/new_cluster/index.js
Normal file
12
app/assets/javascripts/clusters/stores/new_cluster/index.js
Normal 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;
|
|
@ -0,0 +1,3 @@
|
|||
export default (initialState = {}) => ({
|
||||
clusterConnectHelpPath: initialState.clusterConnectHelpPath,
|
||||
});
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ export default () => ({
|
|||
lastFetchedAt: null,
|
||||
currentDiscussionId: null,
|
||||
batchSuggestionsInfo: [],
|
||||
currentlyFetchingDiscussions: false,
|
||||
/**
|
||||
* selectedCommentPosition & selectedCommentPosition structures are the same as `position.line_range`:
|
||||
* {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -379,4 +379,7 @@ export default {
|
|||
[types.UPDATE_ASSIGNEES](state, assignees) {
|
||||
state.noteableData.assignees = assignees;
|
||||
},
|
||||
[types.SET_FETCHING_DISCUSSIONS](state, value) {
|
||||
state.currentlyFetchingDiscussions = value;
|
||||
},
|
||||
};
|
||||
|
|
5
app/assets/javascripts/pages/admin/clusters/new/index.js
Normal file
5
app/assets/javascripts/pages/admin/clusters/new/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import initNewCluster from '~/clusters/new_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNewCluster();
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import initNewCluster from '~/clusters/new_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNewCluster();
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import initNewCluster from '~/clusters/new_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNewCluster();
|
||||
});
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -92,7 +92,7 @@ class Projects::TagsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
format.js do
|
||||
render status: :unprocessable_entity
|
||||
render status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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) ||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 }
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Automatically create self monitoring project on new GitLab installations
|
||||
merge_request: 40404
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Create IssueLink for Vulnerabilities that do not have them
|
||||
merge_request: 39986
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix reading some merge request diffs
|
||||
merge_request: 40598
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Paginate profile group notifications
|
||||
merge_request: 40326
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/emilyring-cluster-new-vue.yml
Normal file
5
changelogs/unreleased/emilyring-cluster-new-vue.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Moved Cluster Connect Form to Vue
|
||||
merge_request: 40295
|
||||
author:
|
||||
type: changed
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Avoid creating diff position when line-code is nil
|
||||
merge_request: 40089
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix snowplow tracking event error for new user invite page
|
||||
merge_request: 40628
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add rate limit on webhooks testing feature
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
5
changelogs/unreleased/security-upgrade-jquery-3-5.yml
Normal file
5
changelogs/unreleased/security-upgrade-jquery-3-5.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Upgrade jquery to v3.5
|
||||
merge_request:
|
||||
author:
|
||||
type: security
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
e8fc0809b5bd3248dc625602deeaaef16e2db6b33d8eaf51fdcc1c67dee49e17
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>"
|
||||
`;
|
41
spec/frontend/clusters/components/new_cluster_spec.js
Normal file
41
spec/frontend/clusters/components/new_cluster_spec.js
Normal 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');
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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 } };
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue