summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-19 12:10:46 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-19 12:10:46 +0000
commit442a79b7336b763ad390285ef75f4e4ff48a9cd0 (patch)
tree75ad88357d71e33c1fc92618cd618cfaa2bfeb69
parentfcef382cb994b8ecdbff75490bab0425a35f2641 (diff)
downloadgitlab-ce-442a79b7336b763ad390285ef75f4e4ff48a9cd0.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/api.js4
-rw-r--r--app/assets/javascripts/diffs/components/app.vue6
-rw-r--r--app/assets/javascripts/diffs/index.js2
-rw-r--r--app/assets/javascripts/diffs/store/actions.js2
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js2
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue14
-rw-r--r--app/assets/javascripts/notes/stores/actions.js4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue2
-rw-r--r--app/assets/javascripts/pipelines/graphql/fragments/linked_pipelines.fragment.graphql17
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue21
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/index.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue117
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue34
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue26
-rw-r--r--app/assets/stylesheets/components/batch_comments/review_bar.scss2
-rw-r--r--app/assets/stylesheets/framework/awards.scss2
-rw-r--r--app/assets/stylesheets/framework/contextual_sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss8
-rw-r--r--app/assets/stylesheets/utilities.scss2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/graphql/queries/pipelines/get_pipeline_details.query.graphql (renamed from app/assets/javascripts/pipelines/graphql/queries/get_pipeline_details.query.graphql)38
-rw-r--r--app/helpers/merge_requests_helper.rb4
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/concerns/cacheable_attributes.rb2
-rw-r--r--app/views/abuse_reports/new.html.haml2
-rw-r--r--app/views/admin/groups/_group.html.haml2
-rw-r--r--app/views/admin/projects/_projects.html.haml4
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/projects/edit.html.haml8
-rw-r--r--app/views/projects/merge_requests/show.html.haml3
-rw-r--r--app/views/projects/milestones/_form.html.haml4
-rw-r--r--app/views/projects/pipelines/show.html.haml3
-rw-r--r--app/views/projects/services/prometheus/_top.html.haml2
-rw-r--r--app/views/projects/settings/_archive.html.haml4
-rw-r--r--app/views/projects/settings/_general.html.haml2
-rw-r--r--app/views/projects/settings/integrations/show.html.haml2
-rw-r--r--app/views/shared/_file_picker_button.html.haml2
-rw-r--r--app/views/shared/_label.html.haml4
-rw-r--r--app/views/shared/empty_states/_profile_tabs.html.haml4
-rw-r--r--app/views/shared/issuable/_board_create_list_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml4
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml4
-rw-r--r--changelogs/unreleased/add-api-command-to-delete-pending-invitation.yml5
-rw-r--r--changelogs/unreleased/yo-move-to-gl-button.yml5
-rw-r--r--config/feature_flags/development/restrict_access_to_build_debug_mode.yml8
-rw-r--r--config/feature_flags/development/suggestions_custom_commit.yml8
-rw-r--r--config/feature_flags/development/upload_middleware_jwt_params_handler.yml8
-rw-r--r--config/gitlab.yml.example3
-rw-r--r--doc/administration/uploads.md29
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql365
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json1279
-rw-r--r--doc/api/graphql/reference/index.md150
-rw-r--r--doc/api/invitations.md24
-rw-r--r--doc/ci/variables/README.md27
-rw-r--r--lib/api/invitations.rb18
-rw-r--r--lib/gitlab/middleware/multipart.rb124
-rw-r--r--lib/uploaded_file.rb32
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb34
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb3
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb57
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/mock_data.js74
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js384
-rw-r--r--spec/frontend/vue_shared/components/deprecated_modal_2_spec.js258
-rw-r--r--spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap1
-rw-r--r--spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js1
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestions_spec.js1
-rw-r--r--spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb53
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb (renamed from spec/lib/gitlab/middleware/multipart_with_handler_for_jwt_params_spec.rb)20
-rw-r--r--spec/lib/gitlab/middleware/multipart_with_handler_spec.rb196
-rw-r--r--spec/lib/uploaded_file_spec.rb194
-rw-r--r--spec/models/ci/build_spec.rb16
-rw-r--r--spec/requests/api/invitations_spec.rb84
-rw-r--r--spec/requests/api/jobs_spec.rb26
-rw-r--r--spec/support/helpers/multipart_helpers.rb20
-rw-r--r--spec/support/shared_examples/features/file_uploads_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb24
-rw-r--r--tmp/.gitignore7
96 files changed, 2453 insertions, 1564 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 7fb9791df63..334d4e7869b 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-64625df11e8add7e64cce44a47984512e5f42d72
+c89fdf6bb2dc9f652f5c724caf13d3bde76e9d90
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index e0b9643f509..0a3db8ad3a6 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -442,10 +442,10 @@ const Api = {
});
},
- applySuggestion(id) {
+ applySuggestion(id, message) {
const url = Api.buildUrl(Api.applySuggestionPath).replace(':id', encodeURIComponent(id));
- return axios.put(url);
+ return axios.put(url, { commit_message: message });
},
applySuggestionBatch(ids) {
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 5c777fc6cb6..32822fe1fe8 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -124,6 +124,11 @@ export default {
required: false,
default: false,
},
+ defaultSuggestionCommitMessage: {
+ type: String,
+ required: false,
+ default: '',
+ },
mrReviews: {
type: Object,
required: false,
@@ -268,6 +273,7 @@ export default {
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
viewDiffsFileByFile: fileByFile(this.fileByFileUserPreference),
+ defaultSuggestionCommitMessage: this.defaultSuggestionCommitMessage,
mrReviews: this.mrReviews || {},
});
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index 35ba937c1e2..4e0720c645a 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -83,6 +83,7 @@ export default function initDiffsApp(store) {
showSuggestPopover: parseBoolean(dataset.showSuggestPopover),
showWhitespaceDefault: parseBoolean(dataset.showWhitespaceDefault),
viewDiffsFileByFile: parseBoolean(dataset.fileByFileDefault),
+ defaultSuggestionCommitMessage: dataset.defaultSuggestionCommitMessage,
};
},
computed: {
@@ -123,6 +124,7 @@ export default function initDiffsApp(store) {
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
fileByFileUserPreference: this.viewDiffsFileByFile,
+ defaultSuggestionCommitMessage: this.defaultSuggestionCommitMessage,
mrReviews: getReviewsForMergeRequest(mrPath),
},
});
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index ac34684bb16..e95e9ac3ee4 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -62,6 +62,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ defaultSuggestionCommitMessage,
viewDiffsFileByFile,
mrReviews,
} = options;
@@ -73,6 +74,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ defaultSuggestionCommitMessage,
viewDiffsFileByFile,
mrReviews,
});
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 2aa971374ec..aa89c74cef0 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -45,5 +45,6 @@ export default () => ({
fileFinderVisible: false,
dismissEndpoint: '',
showSuggestPopover: true,
+ defaultSuggestionCommitMessage: '',
mrReviews: {},
});
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 4e772d17c29..06f0f2c3dfb 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -36,6 +36,7 @@ export default {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ defaultSuggestionCommitMessage,
viewDiffsFileByFile,
mrReviews,
} = options;
@@ -47,6 +48,7 @@ export default {
projectPath,
dismissEndpoint,
showSuggestPopover,
+ defaultSuggestionCommitMessage,
viewDiffsFileByFile,
mrReviews,
});
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index 094ebe5316a..8855ceac3d5 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -54,6 +54,7 @@ export default {
...mapState({
batchSuggestionsInfo: (state) => state.notes.batchSuggestionsInfo,
}),
+ ...mapState('diffs', ['defaultSuggestionCommitMessage']),
noteBody() {
return this.note.note;
},
@@ -98,12 +99,16 @@ export default {
formCancelHandler(shouldConfirm, isDirty) {
this.$emit('cancelForm', shouldConfirm, isDirty);
},
- applySuggestion({ suggestionId, flashContainer, callback = () => {} }) {
+ applySuggestion({ suggestionId, flashContainer, callback = () => {}, message }) {
const { discussion_id: discussionId, id: noteId } = this.note;
- return this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer }).then(
- callback,
- );
+ return this.submitSuggestion({
+ discussionId,
+ noteId,
+ suggestionId,
+ flashContainer,
+ message,
+ }).then(callback);
},
applySuggestionBatch({ flashContainer }) {
return this.submitSuggestionBatch({ flashContainer });
@@ -130,6 +135,7 @@ export default {
:note-html="note.note_html"
:line-type="lineType"
:help-page-path="helpPagePath"
+ :default-commit-message="defaultSuggestionCommitMessage"
@apply="applySuggestion"
@applyBatch="applySuggestionBatch"
@addToBatch="addSuggestionToBatch"
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 0ab781c13d3..c6684efed4d 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -559,7 +559,7 @@ export const updateResolvableDiscussionsCounts = ({ commit }) =>
export const submitSuggestion = (
{ commit, dispatch },
- { discussionId, suggestionId, flashContainer },
+ { discussionId, suggestionId, flashContainer, message },
) => {
const dispatchResolveDiscussion = () =>
dispatch('resolveDiscussion', { discussionId }).catch(() => {});
@@ -567,7 +567,7 @@ export const submitSuggestion = (
commit(types.SET_RESOLVING_DISCUSSION, true);
dispatch('stopPolling');
- return Api.applySuggestion(suggestionId)
+ return Api.applySuggestion(suggestionId, message)
.then(dispatchResolveDiscussion)
.catch((err) => {
const defaultMessage = __(
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
index f440903d8f2..f596333237d 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -1,8 +1,8 @@
<script>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
+import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import { __ } from '~/locale';
import { DEFAULT, LOAD_FAILURE } from '../../constants';
-import getPipelineDetails from '../../graphql/queries/get_pipeline_details.query.graphql';
import PipelineGraph from './graph_component.vue';
import { unwrapPipelineData, toggleQueryPollingByVisibility, reportToSentry } from './utils';
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index 7b8551c32e5..40e6a01b88c 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -1,5 +1,5 @@
<script>
-import getPipelineDetails from '../../graphql/queries/get_pipeline_details.query.graphql';
+import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import LinkedPipeline from './linked_pipeline.vue';
import { LOAD_FAILURE } from '../../constants';
import { UPSTREAM } from './constants';
diff --git a/app/assets/javascripts/pipelines/graphql/fragments/linked_pipelines.fragment.graphql b/app/assets/javascripts/pipelines/graphql/fragments/linked_pipelines.fragment.graphql
deleted file mode 100644
index 3bf6d8dc9d8..00000000000
--- a/app/assets/javascripts/pipelines/graphql/fragments/linked_pipelines.fragment.graphql
+++ /dev/null
@@ -1,17 +0,0 @@
-fragment LinkedPipelineData on Pipeline {
- id
- iid
- path
- status: detailedStatus {
- group
- label
- icon
- }
- sourceJob {
- name
- }
- project {
- name
- fullPath
- }
-}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
index 5be1ce66d60..560a68031ef 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_alert_message.vue
@@ -36,7 +36,7 @@ export default {
</script>
<template>
- <div class="m-3 ml-7" :class="messageClass">
+ <div class="gl-m-3 gl-ml-7" :class="messageClass">
<slot></slot>
<gl-link v-if="helpPath" :href="helpPath" target="_blank">
<gl-icon :size="16" name="question-o" class="align-middle" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
index 1727383ea2c..3cd003461b3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
@@ -30,7 +30,7 @@ export default {
};
</script>
<template>
- <section class="mr-widget-help font-italic">
+ <section class="gl-py-3 gl-pr-3 gl-pl-5 gl-ml-7 mr-widget-help gl-font-style-italic">
<template v-if="missingBranch">
{{ missingBranchInfo }}
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
index 56a50b55f9d..43317130b08 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
@@ -30,7 +30,7 @@ export default {
};
</script>
<template>
- <section class="mr-info-list mr-links">
+ <section class="mr-info-list gl-ml-7 gl-pb-5">
<p v-if="relatedLinks.closing">{{ closesText }} <span v-html="relatedLinks.closing"></span></p>
<p v-if="relatedLinks.mentioned">
{{ s__('mrWidget|Mentions') }} <span v-html="relatedLinks.mentioned"></span>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
index c917b69953f..d50d97e3570 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -18,7 +18,7 @@ export default {
</script>
<template>
- <p v-once class="mr-info-list mr-links gl-mb-0">
+ <p v-once class="mr-info-list gl-ml-7 gl-pb-5 gl-mb-0">
<span class="status-text">
<gl-sprintf :message="$options.i18n.removesBranchText">
<template #strong="{ content }">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index bdcea9871ea..d331f1690f5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -83,6 +83,7 @@ export default {
:aria-label="ariaLabel"
category="tertiary"
class="commit-edit-toggle gl-mr-3"
+ size="small"
:icon="collapseIcon"
@click.stop="toggle()"
/>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index a63094206a8..bf86e0d8b07 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -159,13 +159,13 @@ export default {
<div class="rebase-state-find-class-convention media media-body space-children">
<span
v-if="rebaseInProgress || isMakingRequest"
- class="gl-font-weight-bold"
+ class="gl-font-weight-bold gl-ml-0!"
data-testid="rebase-message"
>{{ __('Rebase in progress') }}</span
>
<span
v-if="!rebaseInProgress && !canPushToSourceBranch"
- class="gl-font-weight-bold"
+ class="gl-font-weight-bold gl-ml-0!"
data-testid="rebase-message"
v-html="fastForwardMergeText"
></span>
@@ -181,12 +181,17 @@ export default {
>
{{ __('Rebase') }}
</gl-button>
- <span v-if="!rebasingError" class="gl-font-weight-bold" data-testid="rebase-message">{{
- __(
- 'Fast-forward merge is not possible. Rebase the source branch onto the target branch.',
- )
- }}</span>
- <span v-else class="gl-font-weight-bold danger" data-testid="rebase-message">{{
+ <span
+ v-if="!rebasingError"
+ class="gl-font-weight-bold gl-ml-0!"
+ data-testid="rebase-message"
+ >{{
+ __(
+ 'Fast-forward merge is not possible. Rebase the source branch onto the target branch.',
+ )
+ }}</span
+ >
+ <span v-else class="gl-font-weight-bold danger gl-ml-0!" data-testid="rebase-message">{{
rebasingError
}}</span>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js
index 8f2cca3309a..d512877a20d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/index.js
@@ -26,7 +26,11 @@ export default () => {
registerExtension(issueExtension);
- const vm = new Vue({ ...MrWidgetOptions, apolloProvider });
+ const vm = new Vue({
+ el: '#js-vue-mr-widget',
+ ...MrWidgetOptions,
+ apolloProvider,
+ });
window.gl.mrWidget = {
checkStatus: vm.checkStatus,
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 2653e1351ed..519576d9fe6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -48,7 +48,6 @@ import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grou
import getStateQuery from './queries/get_state.query.graphql';
export default {
- el: '#js-vue-mr-widget',
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'MRWidget',
diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue
deleted file mode 100644
index b856bbcdedd..00000000000
--- a/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue
+++ /dev/null
@@ -1,117 +0,0 @@
-<script>
-import $ from 'jquery';
-import { GlButton } from '@gitlab/ui';
-
-const buttonVariants = ['danger', 'primary', 'success', 'warning'];
-const sizeVariants = ['sm', 'md', 'lg', 'xl'];
-
-export default {
- name: 'DeprecatedModal2', // use GlModal instead
-
- components: {
- GlButton,
- },
- props: {
- id: {
- type: String,
- required: false,
- default: null,
- },
- modalSize: {
- type: String,
- required: false,
- default: 'md',
- validator: (value) => sizeVariants.includes(value),
- },
- headerTitleText: {
- type: String,
- required: false,
- default: '',
- },
- footerPrimaryButtonVariant: {
- type: String,
- required: false,
- default: 'primary',
- validator: (value) => buttonVariants.includes(value),
- },
- footerPrimaryButtonText: {
- type: String,
- required: false,
- default: '',
- },
- },
- computed: {
- modalSizeClass() {
- return this.modalSize === 'md' ? '' : `modal-${this.modalSize}`;
- },
- },
- mounted() {
- $(this.$el).on('shown.bs.modal', this.opened).on('hidden.bs.modal', this.closed);
- },
- beforeDestroy() {
- $(this.$el).off('shown.bs.modal', this.opened).off('hidden.bs.modal', this.closed);
- },
- methods: {
- emitCancel(event) {
- this.$emit('cancel', event);
- },
- emitSubmit(event) {
- this.$emit('submit', event);
- },
- opened() {
- this.$emit('open');
- },
- closed() {
- this.$emit('closed');
- },
- },
-};
-</script>
-
-<template>
- <div :id="id" class="modal fade" tabindex="-1" role="dialog">
- <div :class="modalSizeClass" class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header gl-pr-4">
- <slot name="header">
- <h4 class="modal-title">
- <slot name="title"> {{ headerTitleText }} </slot>
- </h4>
- <gl-button
- :aria-label="s__('Modal|Close')"
- variant="default"
- category="tertiary"
- size="small"
- icon="close"
- class="js-modal-close-action"
- data-dismiss="modal"
- @click="emitCancel($event)"
- />
- </slot>
- </div>
-
- <div class="modal-body"><slot></slot></div>
-
- <div class="modal-footer">
- <slot name="footer">
- <gl-button
- class="js-modal-cancel-action qa-modal-cancel-button"
- data-dismiss="modal"
- @click="emitCancel($event)"
- >
- {{ s__('Modal|Cancel') }}
- </gl-button>
- <gl-button
- :class="`btn-${footerPrimaryButtonVariant}`"
- class="js-modal-primary-action qa-modal-primary-button"
- data-dismiss="modal"
- @click="emitSubmit($event)"
- >
- {{ footerPrimaryButtonText }}
- </gl-button>
- </slot>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
index b9729a3dc5c..10887aee689 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
@@ -1,6 +1,5 @@
<script>
import { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton } from '@gitlab/ui';
-import { __, sprintf } from '~/locale';
export default {
components: { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton },
@@ -10,7 +9,7 @@ export default {
required: false,
default: false,
},
- fileName: {
+ defaultCommitMessage: {
type: String,
required: true,
},
@@ -18,18 +17,11 @@ export default {
data() {
return {
message: null,
- buttonText: __('Apply suggestion'),
- headerText: __('Apply suggestion commit message'),
};
},
- computed: {
- placeholderText() {
- return sprintf(__('Apply suggestion on %{fileName}'), { fileName: this.fileName });
- },
- },
methods: {
onApply() {
- this.$emit('apply', this.message || this.placeholderText);
+ this.$emit('apply', this.message);
},
},
};
@@ -37,18 +29,26 @@ export default {
<template>
<gl-dropdown
- :text="buttonText"
- :header-text="headerText"
+ :text="__('Apply suggestion')"
:disabled="disabled"
boundary="window"
right
- menu-class="gl-w-full! gl-pb-0!"
+ menu-class="gl-w-full!"
+ @shown="$refs.commitMessage.$el.focus()"
>
- <gl-dropdown-form class="gl-m-3!">
- <gl-form-textarea v-model="message" :placeholder="placeholderText" />
+ <gl-dropdown-form class="gl-px-4! gl-m-0!">
+ <label for="commit-message">{{ __('Commit message') }}</label>
+ <gl-form-textarea
+ id="commit-message"
+ ref="commitMessage"
+ v-model="message"
+ :placeholder="defaultCommitMessage"
+ submit-on-enter
+ @submit="onApply"
+ />
<gl-button
- class="gl-w-quarter! gl-mt-3 gl-text-center! float-right"
- category="secondary"
+ class="gl-w-auto! gl-mt-3 gl-text-center! gl-hover-text-white! gl-transition-medium! float-right"
+ category="primary"
variant="success"
@click="onApply"
>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
index 13ec7a6ada9..93a270b8a97 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -27,6 +27,10 @@ export default {
type: String,
required: true,
},
+ defaultCommitMessage: {
+ type: String,
+ required: true,
+ },
suggestionsCount: {
type: Number,
required: false,
@@ -47,8 +51,8 @@ export default {
},
},
methods: {
- applySuggestion(callback) {
- this.$emit('apply', { suggestionId: this.suggestion.id, callback });
+ applySuggestion(callback, message) {
+ this.$emit('apply', { suggestionId: this.suggestion.id, callback, message });
},
applySuggestionBatch() {
this.$emit('applyBatch');
@@ -74,6 +78,7 @@ export default {
:is-applying-batch="suggestion.is_applying_batch"
:batch-suggestions-count="batchSuggestionsCount"
:help-page-path="helpPagePath"
+ :default-commit-message="defaultCommitMessage"
:inapplicable-reason="suggestion.inapplicable_reason"
@apply="applySuggestion"
@applyBatch="applySuggestionBatch"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index fb51840b689..63341b433e0 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -2,9 +2,10 @@
import { GlButton, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import ApplySuggestion from './apply_suggestion.vue';
export default {
- components: { GlIcon, GlButton, GlLoadingIcon },
+ components: { GlIcon, GlButton, GlLoadingIcon, ApplySuggestion },
directives: { 'gl-tooltip': GlTooltipDirective },
mixins: [glFeatureFlagsMixin()],
props: {
@@ -37,6 +38,10 @@ export default {
type: String,
required: true,
},
+ defaultCommitMessage: {
+ type: String,
+ required: true,
+ },
inapplicableReason: {
type: String,
required: false,
@@ -57,6 +62,9 @@ export default {
canBeBatched() {
return Boolean(this.glFeatures.batchSuggestions);
},
+ canAddCustomCommitMessage() {
+ return this.glFeatures.suggestionsCustomCommit;
+ },
isApplying() {
return this.isApplyingSingle || this.isApplyingBatch;
},
@@ -77,10 +85,10 @@ export default {
},
},
methods: {
- applySuggestion() {
+ applySuggestion(message) {
if (!this.canApply) return;
this.isApplyingSingle = true;
- this.$emit('apply', this.applySuggestionCallback);
+ this.$emit('apply', this.applySuggestionCallback, message);
},
applySuggestionCallback() {
this.isApplyingSingle = false;
@@ -142,7 +150,14 @@ export default {
>
{{ __('Add suggestion to batch') }}
</gl-button>
- <span v-gl-tooltip.viewport="tooltipMessage" tabindex="0">
+ <apply-suggestion
+ v-if="canAddCustomCommitMessage"
+ :disabled="isDisableButton"
+ :default-commit-message="defaultCommitMessage"
+ class="gl-ml-3"
+ @apply="applySuggestion"
+ />
+ <span v-else v-gl-tooltip.viewport="tooltipMessage" tabindex="0">
<gl-button
v-if="isLoggedIn"
class="btn-inverted js-apply-btn btn-grouped"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index a1b3b9c2eba..5ee51764555 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -38,6 +38,10 @@ export default {
type: String,
required: true,
},
+ defaultCommitMessage: {
+ type: String,
+ required: true,
+ },
suggestionsCount: {
type: Number,
required: false,
@@ -82,16 +86,30 @@ export default {
this.isRendered = true;
},
generateDiff(suggestionIndex) {
- const { suggestions, disabled, batchSuggestionsInfo, helpPagePath, suggestionsCount } = this;
+ const {
+ suggestions,
+ disabled,
+ batchSuggestionsInfo,
+ helpPagePath,
+ defaultCommitMessage,
+ suggestionsCount,
+ } = this;
const suggestion =
suggestions && suggestions[suggestionIndex] ? suggestions[suggestionIndex] : {};
const SuggestionDiffComponent = Vue.extend(SuggestionDiff);
const suggestionDiff = new SuggestionDiffComponent({
- propsData: { disabled, suggestion, batchSuggestionsInfo, helpPagePath, suggestionsCount },
+ propsData: {
+ disabled,
+ suggestion,
+ batchSuggestionsInfo,
+ helpPagePath,
+ defaultCommitMessage,
+ suggestionsCount,
+ },
});
- suggestionDiff.$on('apply', ({ suggestionId, callback }) => {
- this.$emit('apply', { suggestionId, callback, flashContainer: this.$el });
+ suggestionDiff.$on('apply', ({ suggestionId, callback, message }) => {
+ this.$emit('apply', { suggestionId, callback, flashContainer: this.$el, message });
});
suggestionDiff.$on('applyBatch', () => {
diff --git a/app/assets/stylesheets/components/batch_comments/review_bar.scss b/app/assets/stylesheets/components/batch_comments/review_bar.scss
index 76bf7ac81e8..d769ea73101 100644
--- a/app/assets/stylesheets/components/batch_comments/review_bar.scss
+++ b/app/assets/stylesheets/components/batch_comments/review_bar.scss
@@ -4,7 +4,7 @@
left: 0;
width: 100%;
background: $white;
- z-index: 300;
+ z-index: $zindex-dropdown-menu;
padding: 7px 0 6px; // to keep aligned with "collapse sidebar" button on the left sidebar
border-top: 1px solid $border-color;
padding-left: $contextual-sidebar-width;
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index d9ad4992458..a7623b65539 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -14,7 +14,7 @@
top: 0;
margin-top: 3px;
padding: $gl-padding;
- z-index: 300;
+ z-index: $zindex-dropdown-menu;
width: $award-emoji-width;
font-size: 14px;
background-color: $white;
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index 745d469e3e8..c5467c304ec 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -471,7 +471,7 @@
background-color: $black-transparent;
height: 100%;
width: 100%;
- z-index: 300;
+ z-index: $zindex-dropdown-menu;
}
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 41fc4d3dd4e..194c2f89e7d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -216,7 +216,7 @@
position: absolute;
width: auto;
top: 100%;
- z-index: 300;
+ z-index: $zindex-dropdown-menu;
min-width: 240px;
max-width: 500px;
margin-top: $dropdown-vertical-offset;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index bef33bd2ef0..241aaad015e 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -75,7 +75,7 @@
.right-sidebar-expanded {
padding-right: 0;
- z-index: 300;
+ z-index: $zindex-dropdown-menu;
@include media-breakpoint-only(sm) {
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 674ba1a307b..ee0d76b0301 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -433,6 +433,7 @@ $browser-scrollbar-size: 10px;
*/
$header-height: 40px;
$header-zindex: 1000;
+$zindex-dropdown-menu: 300;
$suggestion-header-height: 46px;
$ide-statusbar-height: 25px;
$fixed-layout-width: 1280px;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 7854da0989d..1a223ec0f73 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -124,7 +124,7 @@ $mr-widget-min-height: 69px;
padding: $gl-padding;
@include media-breakpoint-up(md) {
- padding-left: $gl-padding-8 * 7;
+ margin-left: $gl-spacing-scale-7;
}
}
}
@@ -221,7 +221,7 @@ $mr-widget-min-height: 69px;
.mr-widget-pipeline-graph {
.dropdown-menu {
- z-index: 300;
+ z-index: $zindex-dropdown-menu;
}
}
@@ -396,10 +396,6 @@ $mr-widget-min-height: 69px;
}
}
- .mr-widget-help {
- padding: 10px 16px 10px ($gl-padding-8 * 7);
- }
-
.ci-coverage {
float: right;
}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index ab330ed69c6..7178e7f0c78 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -110,7 +110,7 @@
// This utility is used to force the z-index to match that of dropdown menu's
.gl-z-dropdown-menu\! {
- z-index: 300 !important;
+ z-index: $zindex-dropdown-menu !important;
}
.gl-flex-basis-quarter {
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2ecbf8db576..d452a5e02e2 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -42,6 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true)
push_frontend_feature_flag(:diffs_gradual_load, @project, default_enabled: true)
push_frontend_feature_flag(:codequality_mr_diff, @project)
+ push_frontend_feature_flag(:suggestions_custom_commit, @project)
record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b)
diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_details.query.graphql b/app/graphql/queries/pipelines/get_pipeline_details.query.graphql
index 25aede49631..92323923266 100644
--- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_details.query.graphql
+++ b/app/graphql/queries/pipelines/get_pipeline_details.query.graphql
@@ -1,11 +1,34 @@
-#import "../fragments/linked_pipelines.fragment.graphql"
+fragment LinkedPipelineData on Pipeline {
+ __typename
+ id
+ iid
+ path
+ status: detailedStatus {
+ __typename
+ group
+ label
+ icon
+ }
+ sourceJob {
+ __typename
+ name
+ }
+ project {
+ __typename
+ name
+ fullPath
+ }
+}
query getPipelineDetails($projectPath: ID!, $iid: ID!) {
project(fullPath: $projectPath) {
+ __typename
pipeline(iid: $iid) {
+ __typename
id
iid
downstream {
+ __typename
nodes {
...LinkedPipelineData
}
@@ -14,18 +37,25 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
...LinkedPipelineData
}
stages {
+ __typename
nodes {
+ __typename
name
status: detailedStatus {
+ __typename
action {
+ __typename
icon
path
title
}
}
groups {
+ __typename
nodes {
+ __typename
status: detailedStatus {
+ __typename
label
group
icon
@@ -33,21 +63,27 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
name
size
jobs {
+ __typename
nodes {
+ __typename
name
scheduledAt
needs {
+ __typename
nodes {
+ __typename
name
}
}
status: detailedStatus {
+ __typename
icon
tooltip
hasDetails
detailsPath
group
action {
+ __typename
buttonTitle
icon
path
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index bbfd06ca06d..6ab5f499b9a 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -185,6 +185,10 @@ module MergeRequestsHelper
current_user.review_requested_open_merge_requests_count
end
+
+ def default_suggestion_commit_message
+ @project.suggestion_commit_message.presence || Gitlab::Suggestions::CommitMessage::DEFAULT_SUGGESTION_COMMIT_MESSAGE
+ end
end
MergeRequestsHelper.prepend_if_ee('EE::MergeRequestsHelper')
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index c130eb3d984..5e3f42d7c2c 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -1020,8 +1020,6 @@ module Ci
end
def debug_mode?
- return false unless Feature.enabled?(:restrict_access_to_build_debug_mode, default_enabled: true)
-
# TODO: Have `debug_mode?` check against data on sent back from runner
# to capture all the ways that variables can be set.
# See (https://gitlab.com/gitlab-org/gitlab/-/issues/290955)
diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb
index de176ffde5c..ee56322cce7 100644
--- a/app/models/concerns/cacheable_attributes.rb
+++ b/app/models/concerns/cacheable_attributes.rb
@@ -83,6 +83,6 @@ module CacheableAttributes
end
def cache!
- self.class.cache_backend.write(self.class.cache_key, self, expires_in: 1.minute)
+ self.class.cache_backend.write(self.class.cache_key, self, expires_in: Gitlab.config.gitlab['application_settings_cache_seconds'] || 60)
end
end
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index c6781e91cfd..09b16c54700 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -25,4 +25,4 @@
= _("Explain the problem. If appropriate, provide a link to the relevant issue or comment.")
.form-actions
- = f.submit _("Send report"), class: "btn btn-success"
+ = f.submit _("Send report"), class: "gl-button btn btn-success"
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index a667fc7ca04..a8c1be8a569 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -33,5 +33,5 @@
= visibility_level_icon(group.visibility_level)
.controls.gl-flex-shrink-0.gl-ml-5
- = link_to _('Edit'), admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
+ = link_to _('Edit'), admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'gl-button btn'
= link_to _('Delete'), [:admin, group], data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name } }, method: :delete, class: 'gl-button btn btn-danger'
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index 44317eb7f6e..4131c8b7edd 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -4,8 +4,8 @@
- @projects.each_with_index do |project|
%li.project-row{ class: ('no-description' if project.description.blank?) }
.controls
- = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
- %button.delete-project-button.btn.btn-danger{ data: { delete_project_url: admin_project_path(project), project_name: project.name } }
+ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "gl-button btn"
+ %button.delete-project-button.gl-button.btn.btn-danger{ data: { delete_project_url: admin_project_path(project), project_name: project.name } }
= s_('AdminProjects|Delete')
.stats
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 41699d6f01f..ae22df1ff19 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -32,7 +32,7 @@
= image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160'
%h5.gl-mt-0= s_("Profiles|Upload new avatar")
.gl-mt-2.gl-mb-3
- %button.btn.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...")
+ %button.gl-button.btn.js-choose-user-avatar-button{ type: 'button' }= s_("Profiles|Choose file...")
%span.avatar-file-name.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen")
= f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*'
.form-text.text-muted= s_("Profiles|The maximum file size allowed is 200KB.")
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index cde8a5f69dd..cc2319ab90a 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -25,7 +25,7 @@
.js-project-permissions-form
- if show_visibility_confirm_modal?(@project)
= render "visibility_modal"
- = f.submit _('Save changes'), class: "btn btn-success #{('js-confirm-danger' if show_visibility_confirm_modal?(@project))}", data: { qa_selector: 'visibility_features_permissions_save_button', check_field_name: ("project[visibility_level]" if show_visibility_confirm_modal?(@project)), check_compare_value: @project.visibility_level }
+ = f.submit _('Save changes'), class: "gl-button gl-button btn btn-success #{('js-confirm-danger' if show_visibility_confirm_modal?(@project))}", data: { qa_selector: 'visibility_features_permissions_save_button', check_field_name: ("project[visibility_level]" if show_visibility_confirm_modal?(@project)), check_compare_value: @project.visibility_level }
%section.qa-merge-request-settings.rspec-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
.settings-header
@@ -39,7 +39,7 @@
= form_for @project, remote: true, html: { multipart: true, class: "merge-request-settings-form js-mr-settings-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
= render 'projects/merge_request_settings', form: f
- = f.submit _('Save changes'), class: "btn btn-succes qa-save-merge-request-changes rspec-save-merge-request-changes"
+ = f.submit _('Save changes'), class: "gl-button btn btn-succes qa-save-merge-request-changes rspec-save-merge-request-changes"
= render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
@@ -70,7 +70,7 @@
%h4= _('Housekeeping')
%p= _('Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.')
= link_to _('Run housekeeping'), housekeeping_project_path(@project),
- method: :post, class: "btn btn-default"
+ method: :post, class: "gl-button btn btn-default"
= render 'export', project: @project
@@ -92,7 +92,7 @@
%li= _('You will need to update your local repositories to point to the new location.')
- if @project.deployment_platform.present?
%li= _('Your deployment services will be broken, you will need to manually fix the services after renaming.')
- = f.submit _('Change path'), class: "btn btn-warning qa-change-path-button"
+ = f.submit _('Change path'), class: "gl-button btn btn-warning qa-change-path-button"
= render 'transfer', project: @project
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 164393ea79a..849cfac825f 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -90,7 +90,8 @@
dismiss_endpoint: user_callouts_path,
show_suggest_popover: show_suggest_popover?.to_s,
show_whitespace_default: @show_whitespace_default.to_s,
- file_by_file_default: @file_by_file_default.to_s }
+ file_by_file_default: @file_by_file_default.to_s,
+ default_suggestion_commit_message: default_suggestion_commit_message }
.mr-loading-status
.loading.hide
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index a21f519da0e..f6e4442d4fb 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -21,8 +21,8 @@
.form-actions
- if @milestone.new_record?
- = f.submit _('Create milestone'), class: 'btn-success btn', data: { qa_selector: 'create_milestone_button' }
+ = f.submit _('Create milestone'), class: 'gl-button btn-success btn', data: { qa_selector: 'create_milestone_button' }
= link_to _('Cancel'), project_milestones_path(@project), class: 'gl-button btn btn-cancel'
- else
- = f.submit _('Save changes'), class: 'btn-success btn'
+ = f.submit _('Save changes'), class: 'gl-button btn-success btn'
= link_to _('Cancel'), project_milestone_path(@project, @milestone), class: 'gl-button btn btn-cancel'
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 847b96cbd0e..b3ad210aa47 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -6,6 +6,9 @@
- add_page_specific_style 'page_bundles/reports'
- add_page_specific_style 'page_bundles/ci_status'
+- if Feature.enabled?(:graphql_pipeline_details, @project)
+ - add_page_startup_graphql_call('pipelines/get_pipeline_details', { projectPath: @project.full_path, iid: @pipeline.iid })
+
.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } }
#js-pipeline-header-vue.pipeline-header-container{ data: { full_path: @project.full_path, pipeline_iid: @pipeline.iid, pipeline_id: @pipeline.id, pipelines_path: project_pipelines_path(@project) } }
- if @pipeline.commit.present?
diff --git a/app/views/projects/services/prometheus/_top.html.haml b/app/views/projects/services/prometheus/_top.html.haml
index 0238a45b75f..db02ea85865 100644
--- a/app/views/projects/services/prometheus/_top.html.haml
+++ b/app/views/projects/services/prometheus/_top.html.haml
@@ -7,4 +7,4 @@
.gl-alert-body
= s_('AlertSettings|You can now set up alert endpoints for manually configured Prometheus instances in the Alerts section on the Operations settings page. Alert endpoint fields on this page have been deprecated.')
.gl-alert-actions
- = link_to _('Visit settings page'), project_settings_operations_path(@project, anchor: 'js-alert-management-settings'), class: 'btn gl-alert-action btn-info gl-button'
+ = link_to _('Visit settings page'), project_settings_operations_path(@project, anchor: 'js-alert-management-settings'), class: 'gl-button btn gl-alert-action btn-info'
diff --git a/app/views/projects/settings/_archive.html.haml b/app/views/projects/settings/_archive.html.haml
index 4133129fde2..9fc643341f4 100644
--- a/app/views/projects/settings/_archive.html.haml
+++ b/app/views/projects/settings/_archive.html.haml
@@ -10,9 +10,9 @@
%p= _("Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments, and other entities can be created. %{strong_start}Once active, this project shows up in the search and on the dashboard.%{strong_end}").html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
= link_to _('Unarchive project'), unarchive_project_path(@project),
data: { confirm: _("Are you sure that you want to unarchive this project?"), qa_selector: 'unarchive_project_link' },
- method: :post, class: "btn btn-success"
+ method: :post, class: "gl-button btn btn-success"
- else
%p= _("Archiving the project will make it entirely read only. It is hidden from the dashboard and doesn't show up in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end}").html_safe % { strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
= link_to _('Archive project'), archive_project_path(@project),
data: { confirm: _("Are you sure that you want to archive this project?"), qa_selector: 'archive_project_link' },
- method: :post, class: "btn btn-warning"
+ method: :post, class: "gl-button btn btn-warning"
diff --git a/app/views/projects/settings/_general.html.haml b/app/views/projects/settings/_general.html.haml
index 5d5f1d54439..92069849b08 100644
--- a/app/views/projects/settings/_general.html.haml
+++ b/app/views/projects/settings/_general.html.haml
@@ -40,4 +40,4 @@
%hr
= link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
- = f.submit _('Save changes'), class: "btn btn-success mt-4 qa-save-naming-topics-avatar-button"
+ = f.submit _('Save changes'), class: "gl-button btn btn-success gl-mt-6 qa-save-naming-topics-avatar-button"
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index 18c6cb31874..3f5fd765b11 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -10,7 +10,7 @@
.gl-alert-body
= _('Webhooks have moved. They can now be found under the Settings menu.')
.gl-alert-actions
- = link_to _('Go to Webhooks'), project_hooks_path(@project), class: 'btn gl-alert-action btn-info new-gl-button'
+ = link_to _('Go to Webhooks'), project_hooks_path(@project), class: 'gl-button btn gl-alert-action btn-info'
%h4= _('Integrations')
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('user/project/integrations/overview') }
diff --git a/app/views/shared/_file_picker_button.html.haml b/app/views/shared/_file_picker_button.html.haml
index 8c10e4958b9..9e6a7626d89 100644
--- a/app/views/shared/_file_picker_button.html.haml
+++ b/app/views/shared/_file_picker_button.html.haml
@@ -1,7 +1,7 @@
- classes = local_assigns.fetch(:classes, '')
%span.js-filepicker
- %button.btn.js-filepicker-button{ type: 'button', class: classes }= _("Choose file…")
+ %button.gl-button.btn.js-filepicker-button{ type: 'button', class: classes }= _("Choose file…")
%span.file_name.js-filepicker-filename= _("No file chosen")
= f.file_field field, class: "js-filepicker-input hidden"
- if help_text.present?
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 4b09e8de896..3647abc6403 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -46,7 +46,7 @@
%button.js-unsubscribe-button.label-subscribe-button.btn.btn-default{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span= _('Unsubscribe')
.dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) }
- %button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } }
+ %button.gl-button.label-subscribe-button.btn.btn-default{ data: { toggle: 'dropdown' } }
%span
= _('Subscribe')
= sprite_icon('chevron-down')
@@ -59,7 +59,7 @@
%button.js-subscribe-button.js-group-level.label-subscribe-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } }
%span= _('Subscribe at group level')
- else
- %button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
+ %button.gl-button.js-subscribe-button.label-subscribe-button.btn.btn-default{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span= label_subscription_toggle_button_text(label, @project)
= render 'shared/delete_label_modal', label: label
diff --git a/app/views/shared/empty_states/_profile_tabs.html.haml b/app/views/shared/empty_states/_profile_tabs.html.haml
index 38c9fe7179c..7780a144a26 100644
--- a/app/views/shared/empty_states/_profile_tabs.html.haml
+++ b/app/views/shared/empty_states/_profile_tabs.html.haml
@@ -12,9 +12,9 @@
%p= current_user_empty_message_description
- if secondary_button_link.present?
- = link_to secondary_button_label, secondary_button_link, class: 'btn btn-success btn-inverted'
+ = link_to secondary_button_label, secondary_button_link, class: 'gl-button btn btn-success btn-inverted'
- if primary_button_link.present?
- = link_to primary_button_label, primary_button_link, class: 'btn btn-success'
+ = link_to primary_button_label, primary_button_link, class: 'gl-button btn btn-success'
- else
%h5= visitor_empty_message
diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
index b6cf23faff8..132a951fd34 100644
--- a/app/views/shared/issuable/_board_create_list_dropdown.html.haml
+++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
@@ -1,5 +1,5 @@
.dropdown.gl-ml-3#js-add-list
- %button.btn.btn-success.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
+ %button.gl-button.btn.btn-success.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
Add list
.dropdown-menu.dropdown-extended-height.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index 2f30958c877..fd27fac7fee 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -7,8 +7,8 @@
= form_tag [:bulk_update, @project, type], method: :post, class: "bulk-update" do
.block.issuable-sidebar-header
.filter-item.inline.update-issues-btn.float-left
- = button_tag _('Update all'), class: "btn update-selected-issues btn-info", disabled: true
- = button_tag _('Cancel'), class: "btn btn-default js-bulk-update-menu-hide float-right"
+ = button_tag _('Update all'), class: "gl-button btn update-selected-issues btn-info", disabled: true
+ = button_tag _('Cancel'), class: "gl-button btn btn-default js-bulk-update-menu-hide float-right"
- if params[:state] != 'merged'
.block
.title
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
index a0d3bc64f1f..005b76180fd 100644
--- a/app/views/shared/issuable/_label_page_create.html.haml
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -19,7 +19,7 @@
%input.js-add-list{ type: "checkbox", name: "add_list", checked: add_list }
%span= _('Add list')
.clearfix
- %button.btn.btn-primary.float-left.js-new-label-btn{ type: "button" }
+ %button.gl-button.btn.btn-success.float-left.js-new-label-btn{ type: "button" }
= _('Create')
- %button.btn.btn-default.float-right.js-cancel-label-btn{ type: "button" }
+ %button.gl-button.btn.btn-default.float-right.js-cancel-label-btn{ type: "button" }
= _('Cancel')
diff --git a/changelogs/unreleased/add-api-command-to-delete-pending-invitation.yml b/changelogs/unreleased/add-api-command-to-delete-pending-invitation.yml
new file mode 100644
index 00000000000..c11d89900e1
--- /dev/null
+++ b/changelogs/unreleased/add-api-command-to-delete-pending-invitation.yml
@@ -0,0 +1,5 @@
+---
+title: Add API command to remove pending member invitation
+merge_request: 51134
+author:
+type: added
diff --git a/changelogs/unreleased/yo-move-to-gl-button.yml b/changelogs/unreleased/yo-move-to-gl-button.yml
new file mode 100644
index 00000000000..68120ce9286
--- /dev/null
+++ b/changelogs/unreleased/yo-move-to-gl-button.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate old button classes to our Pajamas style GitLab button in multiple areas
+merge_request: 51826
+author: Yogi (@yo)
+type: other
diff --git a/config/feature_flags/development/restrict_access_to_build_debug_mode.yml b/config/feature_flags/development/restrict_access_to_build_debug_mode.yml
deleted file mode 100644
index 6d195e03842..00000000000
--- a/config/feature_flags/development/restrict_access_to_build_debug_mode.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: restrict_access_to_build_debug_mode
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48932
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292661
-milestone: '13.7'
-type: development
-group: group::continuous integration
-default_enabled: true
diff --git a/config/feature_flags/development/suggestions_custom_commit.yml b/config/feature_flags/development/suggestions_custom_commit.yml
new file mode 100644
index 00000000000..e51e219ee1d
--- /dev/null
+++ b/config/feature_flags/development/suggestions_custom_commit.yml
@@ -0,0 +1,8 @@
+---
+name: suggestions_custom_commit
+introduced_by_url:
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/297404
+milestone: '13.9'
+type: development
+group: group::code review
+default_enabled: false
diff --git a/config/feature_flags/development/upload_middleware_jwt_params_handler.yml b/config/feature_flags/development/upload_middleware_jwt_params_handler.yml
deleted file mode 100644
index 60a68005235..00000000000
--- a/config/feature_flags/development/upload_middleware_jwt_params_handler.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: upload_middleware_jwt_params_handler
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33277
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/233895
-milestone: '13.4'
-type: development
-group: group::package
-default_enabled: true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 92e7501d49d..a4609b26e0c 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -165,6 +165,9 @@ production: &base
## Disable jQuery and CSS animations
# disable_animations: true
+ ## Application settings cache expiry in seconds (default: 60)
+ # application_settings_cache_seconds: 60
+
## Reply by email
# Allow users to comment on issues and merge requests by replying to notification emails.
# For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index f4e86c6b65d..49af8358e29 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -8,35 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Uploads represent all user data that may be sent to GitLab as a single file. As an example, avatars and notes' attachments are uploads. Uploads are integral to GitLab functionality, and therefore cannot be disabled.
-## Upload parameters
-
-> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/214785) in GitLab 13.5.
-> - It's [deployed behind a feature flag](../user/feature_flags.md), enabled by default.
-> - It's enabled on GitLab.com.
-> - It's recommended for production use.
-> - For GitLab self-managed instances, GitLab administrators can opt to disable it. **(CORE ONLY)**
-
-In 13.5 and later, upload parameters are passed [between Workhorse and GitLab Rails](../development/architecture.md#simplified-component-overview) differently than they
-were before.
-
-This change is deployed behind a feature flag that is **enabled by default**.
-
-If you experience any issues with upload,
-[GitLab administrators with access to the GitLab Rails console](feature_flags.md)
-can opt to disable it.
-
-To enable it:
-
-```ruby
-Feature.enable(:upload_middleware_jwt_params_handler)
-```
-
-To disable it:
-
-```ruby
-Feature.disable(:upload_middleware_jwt_params_handler)
-```
-
## Using local storage
This is the default configuration. To change the location where the uploads are
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 94792a49933..146e9ad0cf7 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -26513,6 +26513,11 @@ type Vulnerability implements Noteable {
description: String
"""
+ Details of the vulnerability
+ """
+ details: [VulnerabilityDetail!]!
+
+ """
Timestamp of when the vulnerability was first detected
"""
detectedAt: Time!
@@ -26781,6 +26786,366 @@ type VulnerabilityConnection {
}
"""
+Represents a vulnerability detail field. The fields with data will depend on the vulnerability detail type
+"""
+union VulnerabilityDetail = VulnerabilityDetailBase | VulnerabilityDetailBoolean | VulnerabilityDetailCode | VulnerabilityDetailCommit | VulnerabilityDetailDiff | VulnerabilityDetailFileLocation | VulnerabilityDetailInt | VulnerabilityDetailList | VulnerabilityDetailMarkdown | VulnerabilityDetailModuleLocation | VulnerabilityDetailTable | VulnerabilityDetailText | VulnerabilityDetailUrl
+
+"""
+Represents the vulnerability details base
+"""
+type VulnerabilityDetailBase {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+}
+
+"""
+Represents the vulnerability details boolean value
+"""
+type VulnerabilityDetailBoolean {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Value of the field.
+ """
+ value: Boolean!
+}
+
+"""
+Represents the vulnerability details code field
+"""
+type VulnerabilityDetailCode {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Language of the code.
+ """
+ lang: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Source code.
+ """
+ value: String!
+}
+
+"""
+Represents the vulnerability details commit field
+"""
+type VulnerabilityDetailCommit {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ The commit SHA value.
+ """
+ value: String!
+}
+
+"""
+Represents the vulnerability details diff field
+"""
+type VulnerabilityDetailDiff {
+ """
+ Value of the field after the change.
+ """
+ after: String!
+
+ """
+ Value of the field before the change.
+ """
+ before: String!
+
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+}
+
+"""
+Represents the vulnerability details location within a file in the project
+"""
+type VulnerabilityDetailFileLocation {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ File name.
+ """
+ fileName: String!
+
+ """
+ End line number of the file location.
+ """
+ lineEnd: Int!
+
+ """
+ Start line number of the file location.
+ """
+ lineStart: Int!
+
+ """
+ Name of the field.
+ """
+ name: String!
+}
+
+"""
+Represents the vulnerability details integer value
+"""
+type VulnerabilityDetailInt {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Value of the field.
+ """
+ value: Int!
+}
+
+"""
+Represents the vulnerability details list value
+"""
+type VulnerabilityDetailList {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ List of details.
+ """
+ items: [VulnerabilityDetail!]!
+
+ """
+ Name of the field.
+ """
+ name: String!
+}
+
+"""
+Represents the vulnerability details Markdown field
+"""
+type VulnerabilityDetailMarkdown {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Value of the Markdown field.
+ """
+ value: String!
+}
+
+"""
+Represents the vulnerability details location within a file in the project
+"""
+type VulnerabilityDetailModuleLocation {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Module name.
+ """
+ moduleName: String!
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Offset of the module location.
+ """
+ offset: Int!
+}
+
+"""
+Represents the vulnerability details table value
+"""
+type VulnerabilityDetailTable {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Table headers.
+ """
+ headers: [VulnerabilityDetail!]!
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Table rows.
+ """
+ rows: [VulnerabilityDetail!]!
+}
+
+"""
+Represents the vulnerability details text field
+"""
+type VulnerabilityDetailText {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Value of the text field.
+ """
+ value: String!
+}
+
+"""
+Represents the vulnerability details URL field
+"""
+type VulnerabilityDetailUrl {
+ """
+ Description of the field.
+ """
+ description: String!
+
+ """
+ Name of the field.
+ """
+ fieldName: String
+
+ """
+ Href of the URL.
+ """
+ href: String!
+
+ """
+ Name of the field.
+ """
+ name: String!
+
+ """
+ Text of the URL.
+ """
+ text: String
+}
+
+"""
Autogenerated input type of VulnerabilityDismiss
"""
input VulnerabilityDismissInput {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 138530abb17..f3fd31f85eb 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -76771,6 +76771,32 @@
"deprecationReason": null
},
{
+ "name": "details",
+ "description": "Details of the vulnerability",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "UNION",
+ "name": "VulnerabilityDetail",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "detectedAt",
"description": "Timestamp of when the vulnerability was first detected",
"args": [
@@ -77516,6 +77542,1259 @@
"possibleTypes": null
},
{
+ "kind": "UNION",
+ "name": "VulnerabilityDetail",
+ "description": "Represents a vulnerability detail field. The fields with data will depend on the vulnerability detail type",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": [
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailBase",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailBoolean",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailCode",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailCommit",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailDiff",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailFileLocation",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailInt",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailList",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailMarkdown",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailModuleLocation",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailTable",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailText",
+ "ofType": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailUrl",
+ "ofType": null
+ }
+ ]
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailBase",
+ "description": "Represents the vulnerability details base",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailBoolean",
+ "description": "Represents the vulnerability details boolean value",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "Value of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailCode",
+ "description": "Represents the vulnerability details code field",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lang",
+ "description": "Language of the code.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "Source code.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailCommit",
+ "description": "Represents the vulnerability details commit field",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "The commit SHA value.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailDiff",
+ "description": "Represents the vulnerability details diff field",
+ "fields": [
+ {
+ "name": "after",
+ "description": "Value of the field after the change.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "before",
+ "description": "Value of the field before the change.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailFileLocation",
+ "description": "Represents the vulnerability details location within a file in the project",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fileName",
+ "description": "File name.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lineEnd",
+ "description": "End line number of the file location.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lineStart",
+ "description": "Start line number of the file location.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailInt",
+ "description": "Represents the vulnerability details integer value",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "Value of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailList",
+ "description": "Represents the vulnerability details list value",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "items",
+ "description": "List of details.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "UNION",
+ "name": "VulnerabilityDetail",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailMarkdown",
+ "description": "Represents the vulnerability details Markdown field",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "Value of the Markdown field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailModuleLocation",
+ "description": "Represents the vulnerability details location within a file in the project",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "moduleName",
+ "description": "Module name.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "offset",
+ "description": "Offset of the module location.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailTable",
+ "description": "Represents the vulnerability details table value",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "headers",
+ "description": "Table headers.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "UNION",
+ "name": "VulnerabilityDetail",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "rows",
+ "description": "Table rows.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "UNION",
+ "name": "VulnerabilityDetail",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailText",
+ "description": "Represents the vulnerability details text field",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "value",
+ "description": "Value of the text field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilityDetailUrl",
+ "description": "Represents the vulnerability details URL field",
+ "fields": [
+ {
+ "name": "description",
+ "description": "Description of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fieldName",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "href",
+ "description": "Href of the URL.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "Name of the field.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "text",
+ "description": "Text of the URL.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "INPUT_OBJECT",
"name": "VulnerabilityDismissInput",
"description": "Autogenerated input type of VulnerabilityDismiss",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c098de16ef6..2cdbbcc89a0 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -3937,6 +3937,7 @@ Represents a vulnerability.
| `confirmedAt` | Time | Timestamp of when the vulnerability state was changed to confirmed |
| `confirmedBy` | User | The user that confirmed the vulnerability. |
| `description` | String | Description of the vulnerability |
+| `details` | VulnerabilityDetail! => Array | Details of the vulnerability |
| `detectedAt` | Time! | Timestamp of when the vulnerability was first detected |
| `discussions` | DiscussionConnection! | All discussions on this noteable |
| `dismissedAt` | Time | Timestamp of when the vulnerability state was changed to dismissed |
@@ -3973,6 +3974,155 @@ Autogenerated return type of VulnerabilityConfirm.
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `vulnerability` | Vulnerability | The vulnerability after state change. |
+### VulnerabilityDetailBase
+
+Represents the vulnerability details base.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `name` | String! | Name of the field. |
+
+### VulnerabilityDetailBoolean
+
+Represents the vulnerability details boolean value.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `name` | String! | Name of the field. |
+| `value` | Boolean! | Value of the field. |
+
+### VulnerabilityDetailCode
+
+Represents the vulnerability details code field.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `lang` | String | Language of the code. |
+| `name` | String! | Name of the field. |
+| `value` | String! | Source code. |
+
+### VulnerabilityDetailCommit
+
+Represents the vulnerability details commit field.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `name` | String! | Name of the field. |
+| `value` | String! | The commit SHA value. |
+
+### VulnerabilityDetailDiff
+
+Represents the vulnerability details diff field.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `after` | String! | Value of the field after the change. |
+| `before` | String! | Value of the field before the change. |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `name` | String! | Name of the field. |
+
+### VulnerabilityDetailFileLocation
+
+Represents the vulnerability details location within a file in the project.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `fileName` | String! | File name. |
+| `lineEnd` | Int! | End line number of the file location. |
+| `lineStart` | Int! | Start line number of the file location. |
+| `name` | String! | Name of the field. |
+
+### VulnerabilityDetailInt
+
+Represents the vulnerability details integer value.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `name` | String! | Name of the field. |
+| `value` | Int! | Value of the field. |
+
+### VulnerabilityDetailList
+
+Represents the vulnerability details list value.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `items` | VulnerabilityDetail! => Array | List of details. |
+| `name` | String! | Name of the field. |
+
+### VulnerabilityDetailMarkdown
+
+Represents the vulnerability details Markdown field.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `name` | String! | Name of the field. |
+| `value` | String! | Value of the Markdown field. |
+
+### VulnerabilityDetailModuleLocation
+
+Represents the vulnerability details location within a file in the project.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `moduleName` | String! | Module name. |
+| `name` | String! | Name of the field. |
+| `offset` | Int! | Offset of the module location. |
+
+### VulnerabilityDetailTable
+
+Represents the vulnerability details table value.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `headers` | VulnerabilityDetail! => Array | Table headers. |
+| `name` | String! | Name of the field. |
+| `rows` | VulnerabilityDetail! => Array | Table rows. |
+
+### VulnerabilityDetailText
+
+Represents the vulnerability details text field.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `name` | String! | Name of the field. |
+| `value` | String! | Value of the text field. |
+
+### VulnerabilityDetailUrl
+
+Represents the vulnerability details URL field.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `description` | String! | Description of the field. |
+| `fieldName` | String | Name of the field. |
+| `href` | String! | Href of the URL. |
+| `name` | String! | Name of the field. |
+| `text` | String | Text of the URL. |
+
### VulnerabilityDismissPayload
Autogenerated return type of VulnerabilityDismiss.
diff --git a/doc/api/invitations.md b/doc/api/invitations.md
index 0891a343cce..7259dddec8c 100644
--- a/doc/api/invitations.md
+++ b/doc/api/invitations.md
@@ -106,3 +106,27 @@ Example response:
},
]
```
+
+## Delete an invitation to a group or project
+
+Deletes a pending invitation by email address.
+
+```plaintext
+DELETE /groups/:id/invitations/:email
+DELETE /projects/:id/invitations/:email
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project or group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `email` | string | yes | The email address to which the invitation was previously sent |
+
+```shell
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/55/invitations/email@example.org"
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/55/invitations/email@example.org"
+```
+
+- Returns `204` and no content on success.
+- Returns `403` forbidden if unauthorized to delete the invitation.
+- Returns `404` not found if authorized and no invitation is found for that email address.
+- Returns `409` if the request was valid but the invitation could not be deleted.
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 3fd388c899d..5fca8e8c2b7 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -877,13 +877,7 @@ before making them visible again.
### Restricted access to debug logging
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213159) in GitLab 13.7.
-> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default.
-> - It's enabled on GitLab.com.
-> - It's recommended for production use.
-> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-restricted-access-to-debug-logging). **(CORE ONLY)**
-
-WARNING:
-This feature might not be available to you. Check the **version history** note above for details.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/292661) in GitLab 13.8.
With restricted access to debug logging, only users with
[developer or higher permissions](../../user/permissions.md#project-members-permissions)
@@ -897,25 +891,6 @@ If you add `CI_DEBUG_TRACE` as a local variable to your runners, debug logs are
to all users with access to job logs. The permission levels are not checked by Runner,
so you should make use of the variable in GitLab only.
-#### Enable or disable Restricted access to debug logging **(CORE ONLY)**
-
-Restricted Access to Debug logging is under development but ready for production use.
-It is deployed behind a feature flag that is **enabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
-can opt to disable it.
-
-To enable it:
-
-```ruby
-Feature.enable(:restrict_access_to_build_debug_mode)
-```
-
-To disable it:
-
-```ruby
-Feature.disable(:restrict_access_to_build_debug_mode)
-```
-
### Enable Debug logging
To enable debug logs (traces), set the `CI_DEBUG_TRACE` variable to `true`:
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index be8147908e9..2ab1f97afe6 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -48,6 +48,24 @@ module API
present_member_invitations invitations
end
+
+ desc 'Removes an invitation from a group or project.'
+ params do
+ requires :email, type: String, desc: 'The email address of the invitation'
+ end
+ delete ":id/invitations/:email", requirements: { email: /[^\/]+/ } do
+ source = find_source(source_type, params[:id])
+ invite_email = params[:email]
+ authorize_admin_source!(source_type, source)
+
+ invite = retrieve_member_invitations(source, invite_email).first
+ not_found! unless invite
+
+ destroy_conditionally!(invite) do
+ ::Members::DestroyService.new(current_user, params).execute(invite)
+ unprocessable_entity! unless invite.destroyed?
+ end
+ end
end
end
end
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index a6d8a778e05..79f1abe820f 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -41,7 +41,7 @@ module Gitlab
end
def with_open_files
- @rewritten_fields.each do |field, tmp_path|
+ @rewritten_fields.keys.each do |field|
raise "invalid field: #{field.inspect}" unless valid_field_name?(field)
parsed_field = Rack::Utils.parse_nested_query(field)
@@ -51,10 +51,10 @@ module Gitlab
if value.nil? # we have a top level param, eg. field = 'foo' and not 'foo[bar]'
raise "invalid field: #{field.inspect}" if field != key
- value = open_file(@request.params, key, tmp_path.presence)
+ value = open_file(extract_upload_params_from(@request.params, with_prefix: key))
@open_files << value
else
- value = decorate_params_value(value, @request.params[key], tmp_path.presence)
+ value = decorate_params_value(value, @request.params[key])
end
update_param(key, value)
@@ -67,12 +67,12 @@ module Gitlab
end
# This function calls itself recursively
- def decorate_params_value(path_hash, value_hash, path_override = nil)
- unless path_hash.is_a?(Hash) && path_hash.count == 1
- raise "invalid path: #{path_hash.inspect}"
+ def decorate_params_value(hash_path, value_hash)
+ unless hash_path.is_a?(Hash) && hash_path.count == 1
+ raise "invalid path: #{hash_path.inspect}"
end
- path_key, path_value = path_hash.first
+ path_key, path_value = hash_path.first
unless value_hash.is_a?(Hash) && value_hash[path_key]
raise "invalid value hash: #{value_hash.inspect}"
@@ -80,19 +80,19 @@ module Gitlab
case path_value
when nil
- value_hash[path_key] = open_file(value_hash.dig(path_key), '', path_override)
+ value_hash[path_key] = open_file(extract_upload_params_from(value_hash[path_key]))
@open_files << value_hash[path_key]
value_hash
when Hash
- decorate_params_value(path_value, value_hash[path_key], path_override)
+ decorate_params_value(path_value, value_hash[path_key])
value_hash
else
raise "unexpected path value: #{path_value.inspect}"
end
end
- def open_file(params, key, path_override = nil)
- ::UploadedFile.from_params(params, key, allowed_paths, path_override)
+ def open_file(params)
+ ::UploadedFile.from_params(params, allowed_paths)
end
# update_params ensures that both rails controllers and rack middleware can find
@@ -111,6 +111,20 @@ module Gitlab
private
+ def extract_upload_params_from(params, with_prefix: '')
+ param_key = "#{with_prefix}#{JWT_PARAM_SUFFIX}"
+ jwt_token = params[param_key]
+ raise "Empty JWT param: #{param_key}" if jwt_token.blank?
+
+ payload = Gitlab::Workhorse.decode_jwt(jwt_token).first
+ raise "Invalid JWT payload: not a Hash" unless payload.is_a?(Hash)
+
+ upload_params = payload.fetch(JWT_PARAM_FIXED_KEY, {})
+ raise "Empty params for: #{param_key}" if upload_params.empty?
+
+ upload_params
+ end
+
def valid_field_name?(name)
# length validation
return false if name.size >= REWRITTEN_FIELD_NAME_MAX_LENGTH
@@ -149,82 +163,6 @@ module Gitlab
end
end
- # TODO this class is meant to replace Handler when the feature flag
- # upload_middleware_jwt_params_handler is removed
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/233895#roll-out-steps
- class HandlerForJWTParams < Handler
- def with_open_files
- @rewritten_fields.keys.each do |field|
- raise "invalid field: #{field.inspect}" unless valid_field_name?(field)
-
- parsed_field = Rack::Utils.parse_nested_query(field)
- raise "unexpected field: #{field.inspect}" unless parsed_field.count == 1
-
- key, value = parsed_field.first
- if value.nil? # we have a top level param, eg. field = 'foo' and not 'foo[bar]'
- raise "invalid field: #{field.inspect}" if field != key
-
- value = open_file(extract_upload_params_from(@request.params, with_prefix: key))
- @open_files << value
- else
- value = decorate_params_value(value, @request.params[key])
- end
-
- update_param(key, value)
- end
-
- yield
- ensure
- @open_files.compact
- .each(&:close)
- end
-
- # This function calls itself recursively
- def decorate_params_value(hash_path, value_hash)
- unless hash_path.is_a?(Hash) && hash_path.count == 1
- raise "invalid path: #{hash_path.inspect}"
- end
-
- path_key, path_value = hash_path.first
-
- unless value_hash.is_a?(Hash) && value_hash[path_key]
- raise "invalid value hash: #{value_hash.inspect}"
- end
-
- case path_value
- when nil
- value_hash[path_key] = open_file(extract_upload_params_from(value_hash[path_key]))
- @open_files << value_hash[path_key]
- value_hash
- when Hash
- decorate_params_value(path_value, value_hash[path_key])
- value_hash
- else
- raise "unexpected path value: #{path_value.inspect}"
- end
- end
-
- def open_file(params)
- ::UploadedFile.from_params_without_field(params, allowed_paths)
- end
-
- private
-
- def extract_upload_params_from(params, with_prefix: '')
- param_key = "#{with_prefix}#{JWT_PARAM_SUFFIX}"
- jwt_token = params[param_key]
- raise "Empty JWT param: #{param_key}" if jwt_token.blank?
-
- payload = Gitlab::Workhorse.decode_jwt(jwt_token).first
- raise "Invalid JWT payload: not a Hash" unless payload.is_a?(Hash)
-
- upload_params = payload.fetch(JWT_PARAM_FIXED_KEY, {})
- raise "Empty params for: #{param_key}" if upload_params.empty?
-
- upload_params
- end
- end
-
def initialize(app)
@app = app
end
@@ -235,22 +173,12 @@ module Gitlab
message = ::Gitlab::Workhorse.decode_jwt(encoded_message)[0]
- handler_class.new(env, message).with_open_files do
+ ::Gitlab::Middleware::Multipart::Handler.new(env, message).with_open_files do
@app.call(env)
end
rescue UploadedFile::InvalidPathError => e
[400, { 'Content-Type' => 'text/plain' }, e.message]
end
-
- private
-
- def handler_class
- if Feature.enabled?(:upload_middleware_jwt_params_handler, default_enabled: true)
- ::Gitlab::Middleware::Multipart::HandlerForJWTParams
- else
- ::Gitlab::Middleware::Multipart::Handler
- end
- end
end
end
end
diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb
index 9b034d1c6c2..79920968603 100644
--- a/lib/uploaded_file.rb
+++ b/lib/uploaded_file.rb
@@ -42,10 +42,7 @@ class UploadedFile
@remote_id = remote_id
end
- # TODO this function is meant to replace .from_params when the feature flag
- # upload_middleware_jwt_params_handler is removed
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/233895#roll-out-steps
- def self.from_params_without_field(params, upload_paths)
+ def self.from_params(params, upload_paths)
path = params['path']
remote_id = params['remote_id']
return if path.blank? && remote_id.blank?
@@ -71,33 +68,6 @@ class UploadedFile
)
end
- # Deprecated. Don't use it.
- # .from_params_without_field will replace this one
- # See .from_params_without_field and
- # https://gitlab.com/gitlab-org/gitlab/-/issues/233895#roll-out-steps
- def self.from_params(params, field, upload_paths, path_override = nil)
- path = path_override || params["#{field}.path"]
- remote_id = params["#{field}.remote_id"]
- return if path.blank? && remote_id.blank?
-
- if remote_id.present? # don't use file_path if remote_id is set
- file_path = nil
- elsif path.present?
- file_path = File.realpath(path)
-
- unless self.allowed_path?(file_path, Array(upload_paths).compact)
- raise InvalidPathError, "insecure path used '#{file_path}'"
- end
- end
-
- UploadedFile.new(file_path,
- filename: params["#{field}.name"],
- content_type: params["#{field}.type"] || 'application/octet-stream',
- sha256: params["#{field}.sha256"],
- remote_id: remote_id,
- size: params["#{field}.size"])
- end
-
def self.allowed_path?(file_path, paths)
paths.any? do |path|
File.exist?(path) && file_path.start_with?(File.realpath(path))
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f971187fb4b..e47bbec804f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3543,12 +3543,6 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
-msgid "Apply suggestion commit message"
-msgstr ""
-
-msgid "Apply suggestion on %{fileName}"
-msgstr ""
-
msgid "Apply suggestions"
msgstr ""
@@ -18469,9 +18463,6 @@ msgstr ""
msgid "ModalButton|Add projects"
msgstr ""
-msgid "Modal|Cancel"
-msgstr ""
-
msgid "Modal|Close"
msgstr ""
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index eb5e62f3d44..430808e1c63 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -675,16 +675,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:forbidden)
end
-
- context 'with restrict_access_to_build_debug_mode feature disabled' do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- end
-
- it 'returns response forbidden' do
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
end
end
end
@@ -1139,18 +1129,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:ok)
end
-
- context 'with restrict_access_to_build_debug_mode feature disabled' do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- end
-
- it 'returns response ok' do
- response = subject
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
end
context 'without proper permissions for debug logging on a project' do
@@ -1164,18 +1142,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:forbidden)
end
-
- context 'with restrict_access_to_build_debug_mode feature disabled' do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- end
-
- it 'returns response ok' do
- response = subject
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
end
end
end
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
index 1e1888cd826..a2ec34335ec 100644
--- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -87,6 +87,7 @@ RSpec.describe 'User comments on a diff', :js do
expect(page).not_to have_content('Applied')
click_button('Apply suggestion')
+ click_button('Apply')
wait_for_requests
expect(page).to have_content('Applied')
@@ -338,6 +339,7 @@ RSpec.describe 'User comments on a diff', :js do
expect(page).not_to have_content('Applied')
click_button('Apply suggestion')
+ click_button('Apply')
wait_for_requests
expect(page).to have_content('Applied')
@@ -349,6 +351,7 @@ RSpec.describe 'User comments on a diff', :js do
expect(page).not_to have_content('Unresolve thread')
click_button('Apply suggestion')
+ click_button('Apply')
wait_for_requests
expect(page).to have_content('Unresolve thread')
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index b1e8127c54c..e87880d74b1 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -179,34 +179,6 @@ RSpec.describe 'Project Jobs Permissions' do
expect(status_code).to eq(expected_status_code)
end
end
-
- context 'when restrict_access_to_build_debug_mode feature not enabled' do
- where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code) do
- true | 'developer' | true | 200
- true | 'guest' | true | 200
- true | 'developer' | false | 200
- true | 'guest' | false | 200
- false | 'developer' | true | 200
- false | 'guest' | true | 403
- false | 'developer' | false | 200
- false | 'guest' | false | 403
- end
-
- with_them do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- ci_instance_variable.update!(value: ci_debug_trace)
- project.update!(public_builds: public_builds)
- project.add_role(user, user_project_role)
- end
-
- it 'renders trace to authorized users' do
- visit trace_project_job_path(project, job)
-
- expect(status_code).to eq(expected_status_code)
- end
- end
- end
end
describe 'raw page' do
@@ -237,35 +209,6 @@ RSpec.describe 'Project Jobs Permissions' do
expect(page).to have_content(expected_msg)
end
end
-
- context 'when restrict_access_to_build_debug_mode feature not enabled' do
- where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code, :expected_msg) do
- true | 'developer' | true | 200 | nil
- true | 'guest' | true | 200 | nil
- true | 'developer' | false | 200 | nil
- true | 'guest' | false | 200 | nil
- false | 'developer' | true | 200 | nil
- false | 'guest' | true | 403 | 'The current user is not authorized to access the job log'
- false | 'developer' | false | 200 | nil
- false | 'guest' | false | 403 | 'The current user is not authorized to access the job log'
- end
-
- with_them do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- ci_instance_variable.update!(value: ci_debug_trace)
- project.update!(public_builds: public_builds)
- project.add_role(user, user_project_role)
- end
-
- it 'renders raw trace to authorized users' do
- visit raw_project_job_path(project, job)
-
- expect(status_code).to eq(expected_status_code)
- expect(page).to have_content(expected_msg)
- end
- end
- end
end
end
end
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index eab29855af5..54593c527cb 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -3,9 +3,9 @@ import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
+import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
-import getPipelineDetails from '~/pipelines/graphql/queries/get_pipeline_details.query.graphql';
import { mockPipelineResponse } from './mock_data';
const defaultProvide = {
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index c6baced73d7..6db152f2607 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -1,10 +1,10 @@
import VueApollo from 'vue-apollo';
import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
+import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
-import getPipelineDetails from '~/pipelines/graphql/queries/get_pipeline_details.query.graphql';
import { DOWNSTREAM, GRAPHQL } from '~/pipelines/components/graph/constants';
import { LOAD_FAILURE } from '~/pipelines/constants';
import {
diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js
index 16773767d4c..7650cbd2d5c 100644
--- a/spec/frontend/pipelines/graph/mock_data.js
+++ b/spec/frontend/pipelines/graph/mock_data.js
@@ -56,7 +56,7 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [],
},
},
@@ -96,7 +96,7 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [],
},
},
@@ -136,7 +136,7 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [],
},
},
@@ -176,7 +176,7 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [],
},
},
@@ -200,7 +200,7 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [],
},
},
@@ -224,7 +224,7 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [],
},
},
@@ -277,18 +277,18 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_c',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_b',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
@@ -331,26 +331,26 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_d 3/3',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_d 2/3',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_d 1/3',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_b',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
@@ -377,26 +377,26 @@ export const mockPipelineResponse = {
},
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_d 3/3',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_d 2/3',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_d 1/3',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_b',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
@@ -433,18 +433,18 @@ export const mockPipelineResponse = {
action: null,
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_c',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_b',
},
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name:
'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
},
@@ -481,10 +481,10 @@ export const mockPipelineResponse = {
action: null,
},
needs: {
- __typename: 'CiJobConnection',
+ __typename: 'CiBuildNeedConnection',
nodes: [
{
- __typename: 'CiJob',
+ __typename: 'CiBuildNeed',
name: 'build_b',
},
],
@@ -578,41 +578,54 @@ export const upstream = {
export const wrappedPipelineReturn = {
data: {
project: {
+ __typename: 'Project',
pipeline: {
+ __typename: 'Pipeline',
id: 'gid://gitlab/Ci::Pipeline/175',
iid: '38',
downstream: {
+ __typename: 'PipelineConnection',
nodes: [],
},
upstream: {
id: 'gid://gitlab/Ci::Pipeline/174',
iid: '37',
path: '/root/elemenohpee/-/pipelines/174',
+ __typename: 'Pipeline',
status: {
+ __typename: 'DetailedStatus',
group: 'success',
label: 'passed',
icon: 'status_success',
},
sourceJob: {
name: 'test_c',
+ __typename: 'CiJob',
},
project: {
id: 'gid://gitlab/Project/25',
name: 'elemenohpee',
fullPath: 'root/elemenohpee',
+ __typename: 'Project',
},
},
stages: {
+ __typename: 'CiStageConnection',
nodes: [
{
name: 'build',
+ __typename: 'CiStage',
status: {
action: null,
+ __typename: 'DetailedStatus',
},
groups: {
+ __typename: 'CiGroupConnection',
nodes: [
{
+ __typename: 'CiGroup',
status: {
+ __typename: 'DetailedStatus',
label: 'passed',
group: 'success',
icon: 'status_success',
@@ -620,20 +633,25 @@ export const wrappedPipelineReturn = {
name: 'build_n',
size: 1,
jobs: {
+ __typename: 'CiJobConnection',
nodes: [
{
+ __typename: 'CiJob',
name: 'build_n',
scheduledAt: null,
needs: {
+ __typename: 'CiBuildNeedConnection',
nodes: [],
},
status: {
+ __typename: 'DetailedStatus',
icon: 'status_success',
tooltip: 'passed',
hasDetails: true,
detailsPath: '/root/elemenohpee/-/jobs/1662',
group: 'success',
action: {
+ __typename: 'StatusAction',
buttonTitle: 'Retry this job',
icon: 'retry',
path: '/root/elemenohpee/-/jobs/1662/retry',
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index 0344ac0ab68..1ea7fe1fbfe 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -1,9 +1,9 @@
-import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
-import mountComponent from 'helpers/vue_mount_component_helper';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
-import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
+import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import notify from '~/lib/utils/notify';
import SmartInterval from '~/smart_interval';
@@ -24,51 +24,47 @@ const returnPromise = (data) =>
});
});
-describe('mrWidgetOptions', () => {
- let vm;
+describe('MrWidgetOptions', () => {
+ let wrapper;
let mock;
- let MrWidgetOptions;
const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch';
beforeEach(() => {
- // Prevent component mounting
- delete mrWidgetOptions.el;
-
gl.mrWidgetData = { ...mockData };
gon.features = { asyncMrWidget: true };
mock = new MockAdapter(axios);
mock.onGet(mockData.merge_request_widget_path).reply(() => [200, { ...mockData }]);
mock.onGet(mockData.merge_request_cached_widget_path).reply(() => [200, { ...mockData }]);
-
- MrWidgetOptions = Vue.extend(mrWidgetOptions);
});
afterEach(() => {
mock.restore();
- vm.$destroy();
- vm = null;
+ wrapper.destroy();
+ wrapper = null;
gl.mrWidgetData = {};
gon.features = {};
});
const createComponent = (mrData = mockData) => {
- if (vm) {
- vm.$destroy();
+ if (wrapper) {
+ wrapper.destroy();
}
- vm = mountComponent(MrWidgetOptions, {
- mrData: { ...mrData },
+ wrapper = mount(MrWidgetOptions, {
+ propsData: {
+ mrData: { ...mrData },
+ },
});
return axios.waitForAll();
};
- const findSuggestPipeline = () => vm.$el.querySelector('[data-testid="mr-suggest-pipeline"]');
- const findSuggestPipelineButton = () => findSuggestPipeline().querySelector('button');
- const findSecurityMrWidget = () => vm.$el.querySelector('[data-testid="security-mr-widget"]');
+ const findSuggestPipeline = () => wrapper.find('[data-testid="mr-suggest-pipeline"]');
+ const findSuggestPipelineButton = () => findSuggestPipeline().find('button');
+ const findSecurityMrWidget = () => wrapper.find('[data-testid="security-mr-widget"]');
describe('default', () => {
beforeEach(() => {
@@ -77,147 +73,147 @@ describe('mrWidgetOptions', () => {
describe('data', () => {
it('should instantiate Store and Service', () => {
- expect(vm.mr).toBeDefined();
- expect(vm.service).toBeDefined();
+ expect(wrapper.vm.mr).toBeDefined();
+ expect(wrapper.vm.service).toBeDefined();
});
});
describe('computed', () => {
describe('componentName', () => {
it('should return merged component', () => {
- expect(vm.componentName).toEqual('mr-widget-merged');
+ expect(wrapper.vm.componentName).toEqual('mr-widget-merged');
});
it('should return conflicts component', () => {
- vm.mr.state = 'conflicts';
+ wrapper.vm.mr.state = 'conflicts';
- expect(vm.componentName).toEqual('mr-widget-conflicts');
+ expect(wrapper.vm.componentName).toEqual('mr-widget-conflicts');
});
});
describe('shouldRenderMergeHelp', () => {
it('should return false for the initial merged state', () => {
- expect(vm.shouldRenderMergeHelp).toBeFalsy();
+ expect(wrapper.vm.shouldRenderMergeHelp).toBeFalsy();
});
it('should return true for a state which requires help widget', () => {
- vm.mr.state = 'conflicts';
+ wrapper.vm.mr.state = 'conflicts';
- expect(vm.shouldRenderMergeHelp).toBeTruthy();
+ expect(wrapper.vm.shouldRenderMergeHelp).toBeTruthy();
});
});
describe('shouldRenderPipelines', () => {
it('should return true when hasCI is true', () => {
- vm.mr.hasCI = true;
+ wrapper.vm.mr.hasCI = true;
- expect(vm.shouldRenderPipelines).toBeTruthy();
+ expect(wrapper.vm.shouldRenderPipelines).toBeTruthy();
});
it('should return false when hasCI is false', () => {
- vm.mr.hasCI = false;
+ wrapper.vm.mr.hasCI = false;
- expect(vm.shouldRenderPipelines).toBeFalsy();
+ expect(wrapper.vm.shouldRenderPipelines).toBeFalsy();
});
});
describe('shouldRenderRelatedLinks', () => {
it('should return false for the initial data', () => {
- expect(vm.shouldRenderRelatedLinks).toBeFalsy();
+ expect(wrapper.vm.shouldRenderRelatedLinks).toBeFalsy();
});
it('should return true if there is relatedLinks in MR', () => {
- Vue.set(vm.mr, 'relatedLinks', {});
+ Vue.set(wrapper.vm.mr, 'relatedLinks', {});
- expect(vm.shouldRenderRelatedLinks).toBeTruthy();
+ expect(wrapper.vm.shouldRenderRelatedLinks).toBeTruthy();
});
});
describe('shouldRenderSourceBranchRemovalStatus', () => {
beforeEach(() => {
- vm.mr.state = 'readyToMerge';
+ wrapper.vm.mr.state = 'readyToMerge';
});
it('should return true when cannot remove source branch and branch will be removed', () => {
- vm.mr.canRemoveSourceBranch = false;
- vm.mr.shouldRemoveSourceBranch = true;
+ wrapper.vm.mr.canRemoveSourceBranch = false;
+ wrapper.vm.mr.shouldRemoveSourceBranch = true;
- expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(true);
+ expect(wrapper.vm.shouldRenderSourceBranchRemovalStatus).toEqual(true);
});
it('should return false when can remove source branch and branch will be removed', () => {
- vm.mr.canRemoveSourceBranch = true;
- vm.mr.shouldRemoveSourceBranch = true;
+ wrapper.vm.mr.canRemoveSourceBranch = true;
+ wrapper.vm.mr.shouldRemoveSourceBranch = true;
- expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ expect(wrapper.vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
it('should return false when cannot remove source branch and branch will not be removed', () => {
- vm.mr.canRemoveSourceBranch = false;
- vm.mr.shouldRemoveSourceBranch = false;
+ wrapper.vm.mr.canRemoveSourceBranch = false;
+ wrapper.vm.mr.shouldRemoveSourceBranch = false;
- expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ expect(wrapper.vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
it('should return false when in merged state', () => {
- vm.mr.canRemoveSourceBranch = false;
- vm.mr.shouldRemoveSourceBranch = true;
- vm.mr.state = 'merged';
+ wrapper.vm.mr.canRemoveSourceBranch = false;
+ wrapper.vm.mr.shouldRemoveSourceBranch = true;
+ wrapper.vm.mr.state = 'merged';
- expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ expect(wrapper.vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
it('should return false when in nothing to merge state', () => {
- vm.mr.canRemoveSourceBranch = false;
- vm.mr.shouldRemoveSourceBranch = true;
- vm.mr.state = 'nothingToMerge';
+ wrapper.vm.mr.canRemoveSourceBranch = false;
+ wrapper.vm.mr.shouldRemoveSourceBranch = true;
+ wrapper.vm.mr.state = 'nothingToMerge';
- expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ expect(wrapper.vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
});
describe('shouldRenderCollaborationStatus', () => {
describe('when collaboration is allowed', () => {
beforeEach(() => {
- vm.mr.allowCollaboration = true;
+ wrapper.vm.mr.allowCollaboration = true;
});
describe('when merge request is opened', () => {
beforeEach((done) => {
- vm.mr.isOpen = true;
- vm.$nextTick(done);
+ wrapper.vm.mr.isOpen = true;
+ nextTick(done);
});
it('should render collaboration status', () => {
- expect(vm.$el.textContent).toContain(COLLABORATION_MESSAGE);
+ expect(wrapper.text()).toContain(COLLABORATION_MESSAGE);
});
});
describe('when merge request is not opened', () => {
beforeEach((done) => {
- vm.mr.isOpen = false;
- vm.$nextTick(done);
+ wrapper.vm.mr.isOpen = false;
+ nextTick(done);
});
it('should not render collaboration status', () => {
- expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE);
+ expect(wrapper.text()).not.toContain(COLLABORATION_MESSAGE);
});
});
});
describe('when collaboration is not allowed', () => {
beforeEach(() => {
- vm.mr.allowCollaboration = false;
+ wrapper.vm.mr.allowCollaboration = false;
});
describe('when merge request is opened', () => {
beforeEach((done) => {
- vm.mr.isOpen = true;
- vm.$nextTick(done);
+ wrapper.vm.mr.isOpen = true;
+ nextTick(done);
});
it('should not render collaboration status', () => {
- expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE);
+ expect(wrapper.text()).not.toContain(COLLABORATION_MESSAGE);
});
});
});
@@ -226,55 +222,55 @@ describe('mrWidgetOptions', () => {
describe('showMergePipelineForkWarning', () => {
describe('when the source project and target project are the same', () => {
beforeEach((done) => {
- Vue.set(vm.mr, 'mergePipelinesEnabled', true);
- Vue.set(vm.mr, 'sourceProjectId', 1);
- Vue.set(vm.mr, 'targetProjectId', 1);
- vm.$nextTick(done);
+ Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', true);
+ Vue.set(wrapper.vm.mr, 'sourceProjectId', 1);
+ Vue.set(wrapper.vm.mr, 'targetProjectId', 1);
+ nextTick(done);
});
it('should be false', () => {
- expect(vm.showMergePipelineForkWarning).toEqual(false);
+ expect(wrapper.vm.showMergePipelineForkWarning).toEqual(false);
});
});
describe('when merge pipelines are not enabled', () => {
beforeEach((done) => {
- Vue.set(vm.mr, 'mergePipelinesEnabled', false);
- Vue.set(vm.mr, 'sourceProjectId', 1);
- Vue.set(vm.mr, 'targetProjectId', 2);
- vm.$nextTick(done);
+ Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', false);
+ Vue.set(wrapper.vm.mr, 'sourceProjectId', 1);
+ Vue.set(wrapper.vm.mr, 'targetProjectId', 2);
+ nextTick(done);
});
it('should be false', () => {
- expect(vm.showMergePipelineForkWarning).toEqual(false);
+ expect(wrapper.vm.showMergePipelineForkWarning).toEqual(false);
});
});
describe('when merge pipelines are enabled _and_ the source project and target project are different', () => {
beforeEach((done) => {
- Vue.set(vm.mr, 'mergePipelinesEnabled', true);
- Vue.set(vm.mr, 'sourceProjectId', 1);
- Vue.set(vm.mr, 'targetProjectId', 2);
- vm.$nextTick(done);
+ Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', true);
+ Vue.set(wrapper.vm.mr, 'sourceProjectId', 1);
+ Vue.set(wrapper.vm.mr, 'targetProjectId', 2);
+ nextTick(done);
});
it('should be true', () => {
- expect(vm.showMergePipelineForkWarning).toEqual(true);
+ expect(wrapper.vm.showMergePipelineForkWarning).toEqual(true);
});
});
});
describe('formattedHumanAccess', () => {
it('when user is a tool admin but not a member of project', () => {
- vm.mr.humanAccess = null;
+ wrapper.vm.mr.humanAccess = null;
- expect(vm.formattedHumanAccess).toEqual('');
+ expect(wrapper.vm.formattedHumanAccess).toEqual('');
});
it('when user a member of the project', () => {
- vm.mr.humanAccess = 'Owner';
+ wrapper.vm.mr.humanAccess = 'Owner';
- expect(vm.formattedHumanAccess).toEqual('owner');
+ expect(wrapper.vm.formattedHumanAccess).toEqual('owner');
});
});
});
@@ -285,9 +281,9 @@ describe('mrWidgetOptions', () => {
let isCbExecuted;
beforeEach(() => {
- jest.spyOn(vm.service, 'checkStatus').mockReturnValue(returnPromise(mockData));
- jest.spyOn(vm.mr, 'setData').mockImplementation(() => {});
- jest.spyOn(vm, 'handleNotification').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm.service, 'checkStatus').mockReturnValue(returnPromise(mockData));
+ jest.spyOn(wrapper.vm.mr, 'setData').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm, 'handleNotification').mockImplementation(() => {});
isCbExecuted = false;
cb = () => {
@@ -296,12 +292,12 @@ describe('mrWidgetOptions', () => {
});
it('should tell service to check status if document is visible', () => {
- vm.checkStatus(cb);
+ wrapper.vm.checkStatus(cb);
- return vm.$nextTick().then(() => {
- expect(vm.service.checkStatus).toHaveBeenCalled();
- expect(vm.mr.setData).toHaveBeenCalled();
- expect(vm.handleNotification).toHaveBeenCalledWith(mockData);
+ return nextTick().then(() => {
+ expect(wrapper.vm.service.checkStatus).toHaveBeenCalled();
+ expect(wrapper.vm.mr.setData).toHaveBeenCalled();
+ expect(wrapper.vm.handleNotification).toHaveBeenCalledWith(mockData);
expect(isCbExecuted).toBeTruthy();
});
});
@@ -309,11 +305,11 @@ describe('mrWidgetOptions', () => {
describe('initPolling', () => {
it('should call SmartInterval', () => {
- vm.initPolling();
+ wrapper.vm.initPolling();
expect(SmartInterval).toHaveBeenCalledWith(
expect.objectContaining({
- callback: vm.checkStatus,
+ callback: wrapper.vm.checkStatus,
}),
);
});
@@ -321,11 +317,11 @@ describe('mrWidgetOptions', () => {
describe('initDeploymentsPolling', () => {
it('should call SmartInterval', () => {
- vm.initDeploymentsPolling();
+ wrapper.vm.initDeploymentsPolling();
expect(SmartInterval).toHaveBeenCalledWith(
expect.objectContaining({
- callback: vm.fetchPreMergeDeployments,
+ callback: wrapper.vm.fetchPreMergeDeployments,
}),
);
});
@@ -334,15 +330,15 @@ describe('mrWidgetOptions', () => {
describe('fetchDeployments', () => {
it('should fetch deployments', () => {
jest
- .spyOn(vm.service, 'fetchDeployments')
+ .spyOn(wrapper.vm.service, 'fetchDeployments')
.mockReturnValue(returnPromise([{ id: 1, status: SUCCESS }]));
- vm.fetchPreMergeDeployments();
+ wrapper.vm.fetchPreMergeDeployments();
- return vm.$nextTick().then(() => {
- expect(vm.service.fetchDeployments).toHaveBeenCalled();
- expect(vm.mr.deployments.length).toEqual(1);
- expect(vm.mr.deployments[0].id).toBe(1);
+ return nextTick().then(() => {
+ expect(wrapper.vm.service.fetchDeployments).toHaveBeenCalled();
+ expect(wrapper.vm.mr.deployments.length).toEqual(1);
+ expect(wrapper.vm.mr.deployments[0].id).toBe(1);
});
});
});
@@ -350,13 +346,13 @@ describe('mrWidgetOptions', () => {
describe('fetchActionsContent', () => {
it('should fetch content of Cherry Pick and Revert modals', () => {
jest
- .spyOn(vm.service, 'fetchMergeActionsContent')
+ .spyOn(wrapper.vm.service, 'fetchMergeActionsContent')
.mockReturnValue(returnPromise('hello world'));
- vm.fetchActionsContent();
+ wrapper.vm.fetchActionsContent();
- return vm.$nextTick().then(() => {
- expect(vm.service.fetchMergeActionsContent).toHaveBeenCalled();
+ return nextTick().then(() => {
+ expect(wrapper.vm.service.fetchMergeActionsContent).toHaveBeenCalled();
expect(document.body.textContent).toContain('hello world');
});
});
@@ -371,40 +367,40 @@ describe('mrWidgetOptions', () => {
${'EnablePolling'} | ${'resumePolling'} | ${() => []}
${'DisablePolling'} | ${'stopPolling'} | ${() => []}
`('should bind to $event', ({ event, method, methodArgs }) => {
- jest.spyOn(vm, method).mockImplementation();
+ jest.spyOn(wrapper.vm, method).mockImplementation();
const eventArg = {};
eventHub.$emit(event, eventArg);
- expect(vm[method]).toHaveBeenCalledWith(...methodArgs(eventArg));
+ expect(wrapper.vm[method]).toHaveBeenCalledWith(...methodArgs(eventArg));
});
it('should bind to SetBranchRemoveFlag', () => {
- expect(vm.mr.isRemovingSourceBranch).toBe(false);
+ expect(wrapper.vm.mr.isRemovingSourceBranch).toBe(false);
eventHub.$emit('SetBranchRemoveFlag', [true]);
- expect(vm.mr.isRemovingSourceBranch).toBe(true);
+ expect(wrapper.vm.mr.isRemovingSourceBranch).toBe(true);
});
it('should bind to FailedToMerge', () => {
- vm.mr.state = '';
- vm.mr.mergeError = '';
+ wrapper.vm.mr.state = '';
+ wrapper.vm.mr.mergeError = '';
const mergeError = 'Something bad happened!';
eventHub.$emit('FailedToMerge', mergeError);
- expect(vm.mr.state).toBe('failedToMerge');
- expect(vm.mr.mergeError).toBe(mergeError);
+ expect(wrapper.vm.mr.state).toBe('failedToMerge');
+ expect(wrapper.vm.mr.mergeError).toBe(mergeError);
});
it('should bind to UpdateWidgetData', () => {
- jest.spyOn(vm.mr, 'setData').mockImplementation();
+ jest.spyOn(wrapper.vm.mr, 'setData').mockImplementation();
const data = { ...mockData };
eventHub.$emit('UpdateWidgetData', data);
- expect(vm.mr.setData).toHaveBeenCalledWith(data);
+ expect(wrapper.vm.mr.setData).toHaveBeenCalledWith(data);
});
});
@@ -425,16 +421,17 @@ describe('mrWidgetOptions', () => {
});
it('should call setFavicon method', async () => {
- vm.mr.ciStatusFaviconPath = overlayDataUrl;
+ wrapper.vm.mr.ciStatusFaviconPath = overlayDataUrl;
- await vm.setFaviconHelper();
+ await wrapper.vm.setFaviconHelper();
expect(setFaviconOverlay).toHaveBeenCalledWith(overlayDataUrl);
});
it('should not call setFavicon when there is no ciStatusFaviconPath', (done) => {
- vm.mr.ciStatusFaviconPath = null;
- vm.setFaviconHelper()
+ wrapper.vm.mr.ciStatusFaviconPath = null;
+ wrapper.vm
+ .setFaviconHelper()
.then(() => {
expect(faviconElement.getAttribute('href')).toEqual(null);
done();
@@ -453,12 +450,12 @@ describe('mrWidgetOptions', () => {
beforeEach(() => {
jest.spyOn(notify, 'notifyMe').mockImplementation(() => {});
- vm.mr.ciStatus = 'failed';
- vm.mr.gitlabLogo = 'logo.png';
+ wrapper.vm.mr.ciStatus = 'failed';
+ wrapper.vm.mr.gitlabLogo = 'logo.png';
});
it('should call notifyMe', () => {
- vm.handleNotification(data);
+ wrapper.vm.handleNotification(data);
expect(notify.notifyMe).toHaveBeenCalledWith(
'Pipeline running-label',
@@ -468,15 +465,15 @@ describe('mrWidgetOptions', () => {
});
it('should not call notifyMe if the status has not changed', () => {
- vm.mr.ciStatus = data.ci_status;
+ wrapper.vm.mr.ciStatus = data.ci_status;
- vm.handleNotification(data);
+ wrapper.vm.handleNotification(data);
expect(notify.notifyMe).not.toHaveBeenCalled();
});
it('should not notify if no pipeline provided', () => {
- vm.handleNotification({
+ wrapper.vm.handleNotification({
...data,
pipeline: undefined,
});
@@ -487,47 +484,49 @@ describe('mrWidgetOptions', () => {
describe('resumePolling', () => {
it('should call stopTimer on pollingInterval', () => {
- jest.spyOn(vm.pollingInterval, 'resume').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm.pollingInterval, 'resume').mockImplementation(() => {});
- vm.resumePolling();
+ wrapper.vm.resumePolling();
- expect(vm.pollingInterval.resume).toHaveBeenCalled();
+ expect(wrapper.vm.pollingInterval.resume).toHaveBeenCalled();
});
});
describe('stopPolling', () => {
it('should call stopTimer on pollingInterval', () => {
- jest.spyOn(vm.pollingInterval, 'stopTimer').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm.pollingInterval, 'stopTimer').mockImplementation(() => {});
- vm.stopPolling();
+ wrapper.vm.stopPolling();
- expect(vm.pollingInterval.stopTimer).toHaveBeenCalled();
+ expect(wrapper.vm.pollingInterval.stopTimer).toHaveBeenCalled();
});
});
});
describe('rendering relatedLinks', () => {
- beforeEach((done) => {
- vm.mr.relatedLinks = {
- assignToMe: null,
- closing: `
- <a class="close-related-link" href="#">
- Close
- </a>
- `,
- mentioned: '',
- };
- Vue.nextTick(done);
+ beforeEach(() => {
+ createComponent({
+ ...mockData,
+ issues_links: {
+ closing: `
+ <a class="close-related-link" href="#">
+ Close
+ </a>
+ `,
+ },
+ });
+
+ return nextTick();
});
it('renders if there are relatedLinks', () => {
- expect(vm.$el.querySelector('.close-related-link')).toBeDefined();
+ expect(wrapper.find('.close-related-link').exists()).toBe(true);
});
it('does not render if state is nothingToMerge', (done) => {
- vm.mr.state = stateKey.nothingToMerge;
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.close-related-link')).toBeNull();
+ wrapper.vm.mr.state = stateKey.nothingToMerge;
+ nextTick(() => {
+ expect(wrapper.find('.close-related-link').exists()).toBe(false);
done();
});
});
@@ -535,15 +534,15 @@ describe('mrWidgetOptions', () => {
describe('rendering source branch removal status', () => {
it('renders when user cannot remove branch and branch should be removed', (done) => {
- vm.mr.canRemoveSourceBranch = false;
- vm.mr.shouldRemoveSourceBranch = true;
- vm.mr.state = 'readyToMerge';
+ wrapper.vm.mr.canRemoveSourceBranch = false;
+ wrapper.vm.mr.shouldRemoveSourceBranch = true;
+ wrapper.vm.mr.state = 'readyToMerge';
- vm.$nextTick(() => {
- const tooltip = vm.$el.querySelector('[data-testid="question-o-icon"]');
+ nextTick(() => {
+ const tooltip = wrapper.find('[data-testid="question-o-icon"]');
- expect(vm.$el.textContent).toContain('Deletes source branch');
- expect(tooltip.getAttribute('title')).toBe(
+ expect(wrapper.text()).toContain('Deletes source branch');
+ expect(tooltip.attributes('title')).toBe(
'A user with write access to the source branch selected this option',
);
@@ -552,13 +551,13 @@ describe('mrWidgetOptions', () => {
});
it('does not render in merged state', (done) => {
- vm.mr.canRemoveSourceBranch = false;
- vm.mr.shouldRemoveSourceBranch = true;
- vm.mr.state = 'merged';
+ wrapper.vm.mr.canRemoveSourceBranch = false;
+ wrapper.vm.mr.shouldRemoveSourceBranch = true;
+ wrapper.vm.mr.state = 'merged';
- vm.$nextTick(() => {
- expect(vm.$el.textContent).toContain('The source branch has been deleted');
- expect(vm.$el.textContent).not.toContain('Deletes source branch');
+ nextTick(() => {
+ expect(wrapper.text()).toContain('The source branch has been deleted');
+ expect(wrapper.text()).not.toContain('Deletes source branch');
done();
});
@@ -596,7 +595,7 @@ describe('mrWidgetOptions', () => {
};
beforeEach((done) => {
- vm.mr.deployments.push(
+ wrapper.vm.mr.deployments.push(
{
...deploymentMockData,
},
@@ -606,33 +605,32 @@ describe('mrWidgetOptions', () => {
},
);
- vm.$nextTick(done);
+ nextTick(done);
});
it('renders multiple deployments', () => {
- expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
+ expect(wrapper.findAll('.deploy-heading').length).toBe(2);
});
it('renders dropdpown with multiple file changes', () => {
expect(
- vm.$el
- .querySelector('.js-mr-wigdet-deployment-dropdown')
- .querySelectorAll('.js-filtered-dropdown-result').length,
+ wrapper.find('.js-mr-wigdet-deployment-dropdown').findAll('.js-filtered-dropdown-result')
+ .length,
).toEqual(changes.length);
});
});
describe('code quality widget', () => {
it('renders the component', () => {
- expect(vm.$el.querySelector('.js-codequality-widget')).toExist();
+ expect(wrapper.find('.js-codequality-widget').exists()).toBe(true);
});
});
describe('pipeline for target branch after merge', () => {
describe('with information for target branch pipeline', () => {
beforeEach((done) => {
- vm.mr.state = 'merged';
- vm.mr.mergePipeline = {
+ wrapper.vm.mr.state = 'merged';
+ wrapper.vm.mr.mergePipeline = {
id: 127,
user: {
id: 1,
@@ -738,16 +736,16 @@ describe('mrWidgetOptions', () => {
},
cancel_path: '/root/ci-web-terminal/pipelines/127/cancel',
};
- vm.$nextTick(done);
+ nextTick(done);
});
it('renders pipeline block', () => {
- expect(vm.$el.querySelector('.js-post-merge-pipeline')).not.toBeNull();
+ expect(wrapper.find('.js-post-merge-pipeline').exists()).toBe(true);
});
describe('with post merge deployments', () => {
beforeEach((done) => {
- vm.mr.postMergeDeployments = [
+ wrapper.vm.mr.postMergeDeployments = [
{
id: 15,
name: 'review/diplo',
@@ -779,46 +777,46 @@ describe('mrWidgetOptions', () => {
},
];
- vm.$nextTick(done);
+ nextTick(done);
});
it('renders post deployment information', () => {
- expect(vm.$el.querySelector('.js-post-deployment')).not.toBeNull();
+ expect(wrapper.find('.js-post-deployment').exists()).toBe(true);
});
});
});
describe('without information for target branch pipeline', () => {
beforeEach((done) => {
- vm.mr.state = 'merged';
+ wrapper.vm.mr.state = 'merged';
- vm.$nextTick(done);
+ nextTick(done);
});
it('does not render pipeline block', () => {
- expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ expect(wrapper.find('.js-post-merge-pipeline').exists()).toBe(false);
});
});
describe('when state is not merged', () => {
beforeEach((done) => {
- vm.mr.state = 'archived';
+ wrapper.vm.mr.state = 'archived';
- vm.$nextTick(done);
+ nextTick(done);
});
it('does not render pipeline block', () => {
- expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ expect(wrapper.find('.js-post-merge-pipeline').exists()).toBe(false);
});
it('does not render post deployment information', () => {
- expect(vm.$el.querySelector('.js-post-deployment')).toBeNull();
+ expect(wrapper.find('.js-post-deployment').exists()).toBe(false);
});
});
});
it('should not suggest pipelines when feature flag is not present', () => {
- expect(findSuggestPipeline()).toBeNull();
+ expect(findSuggestPipeline().exists()).toBe(false);
});
});
@@ -847,11 +845,11 @@ describe('mrWidgetOptions', () => {
if (shouldRender) {
it('renders', () => {
- expect(findSecurityMrWidget()).toEqual(expect.any(HTMLElement));
+ expect(findSecurityMrWidget().exists()).toBe(true);
});
} else {
it('does not render', () => {
- expect(findSecurityMrWidget()).toBeNull();
+ expect(findSecurityMrWidget().exists()).toBe(false);
});
}
});
@@ -860,21 +858,17 @@ describe('mrWidgetOptions', () => {
describe('suggestPipeline', () => {
beforeEach(() => {
mock.onAny().reply(200);
-
- // This is needed because some grandchildren Bootstrap components throw warnings
- // https://gitlab.com/gitlab-org/gitlab/issues/208458
- jest.spyOn(console, 'warn').mockImplementation();
});
describe('given feature flag is enabled', () => {
beforeEach(() => {
createComponent();
- vm.mr.hasCI = false;
+ wrapper.vm.mr.hasCI = false;
});
it('should suggest pipelines when none exist', () => {
- expect(findSuggestPipeline()).toEqual(expect.any(Element));
+ expect(findSuggestPipeline().exists()).toBe(true);
});
it.each([
@@ -882,19 +876,17 @@ describe('mrWidgetOptions', () => {
{ mergeRequestAddCiConfigPath: null },
{ hasCI: true },
])('with %s, should not suggest pipeline', async (obj) => {
- Object.assign(vm.mr, obj);
+ Object.assign(wrapper.vm.mr, obj);
- await vm.$nextTick();
+ await nextTick();
- expect(findSuggestPipeline()).toBeNull();
+ expect(findSuggestPipeline().exists()).toBe(false);
});
it('should allow dismiss of the suggest pipeline message', async () => {
- findSuggestPipelineButton().click();
-
- await vm.$nextTick();
+ await findSuggestPipelineButton().trigger('click');
- expect(findSuggestPipeline()).toBeNull();
+ expect(findSuggestPipeline().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_shared/components/deprecated_modal_2_spec.js b/spec/frontend/vue_shared/components/deprecated_modal_2_spec.js
deleted file mode 100644
index 6b569e876ed..00000000000
--- a/spec/frontend/vue_shared/components/deprecated_modal_2_spec.js
+++ /dev/null
@@ -1,258 +0,0 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue';
-
-const modalComponent = Vue.extend(DeprecatedModal2);
-
-describe('DeprecatedModal2', () => {
- let vm;
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('props', () => {
- describe('with id', () => {
- const props = {
- id: 'my-modal',
- };
-
- beforeEach(() => {
- vm = mountComponent(modalComponent, props);
- });
-
- it('assigns the id to the modal', () => {
- expect(vm.$el.id).toBe(props.id);
- });
- });
-
- describe('without id', () => {
- beforeEach(() => {
- vm = mountComponent(modalComponent, {});
- });
-
- it('does not add an id attribute to the modal', () => {
- expect(vm.$el.hasAttribute('id')).toBe(false);
- });
- });
-
- describe('with headerTitleText', () => {
- const props = {
- headerTitleText: 'my title text',
- };
-
- beforeEach(() => {
- vm = mountComponent(modalComponent, props);
- });
-
- it('sets the modal title', () => {
- const modalTitle = vm.$el.querySelector('.modal-title');
-
- expect(modalTitle.innerHTML.trim()).toBe(props.headerTitleText);
- });
- });
-
- describe('with footerPrimaryButtonVariant', () => {
- const props = {
- footerPrimaryButtonVariant: 'danger',
- };
-
- beforeEach(() => {
- vm = mountComponent(modalComponent, props);
- });
-
- it('sets the primary button class', () => {
- const primaryButton = vm.$el.querySelector('.modal-footer button:last-of-type');
-
- expect(primaryButton).toHaveClass(`btn-${props.footerPrimaryButtonVariant}`);
- });
- });
-
- describe('with footerPrimaryButtonText', () => {
- const props = {
- footerPrimaryButtonText: 'my button text',
- };
-
- beforeEach(() => {
- vm = mountComponent(modalComponent, props);
- });
-
- it('sets the primary button text', () => {
- const primaryButton = vm.$el.querySelector('.js-modal-primary-action .gl-button-text');
-
- expect(primaryButton.innerHTML.trim()).toBe(props.footerPrimaryButtonText);
- });
- });
- });
-
- it('works with data-toggle="modal"', () => {
- setFixtures(`
- <button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
- <div id="modal-container"></div>
- `);
-
- const modalContainer = document.getElementById('modal-container');
- const modalButton = document.getElementById('modal-button');
- vm = mountComponent(
- modalComponent,
- {
- id: 'my-modal',
- },
- modalContainer,
- );
- const modalElement = document.getElementById('my-modal');
-
- modalButton.click();
-
- expect(modalElement).not.toHaveClass('show');
-
- // let the modal fade in
- jest.runOnlyPendingTimers();
-
- expect(modalElement).toHaveClass('show');
- });
-
- describe('methods', () => {
- const dummyEvent = 'not really an event';
-
- beforeEach(() => {
- vm = mountComponent(modalComponent, {});
- jest.spyOn(vm, '$emit').mockImplementation(() => {});
- });
-
- describe('emitCancel', () => {
- it('emits a cancel event', () => {
- vm.emitCancel(dummyEvent);
-
- expect(vm.$emit).toHaveBeenCalledWith('cancel', dummyEvent);
- });
- });
-
- describe('emitSubmit', () => {
- it('emits a submit event', () => {
- vm.emitSubmit(dummyEvent);
-
- expect(vm.$emit).toHaveBeenCalledWith('submit', dummyEvent);
- });
- });
-
- describe('opened', () => {
- it('emits a open event', () => {
- vm.opened();
-
- expect(vm.$emit).toHaveBeenCalledWith('open');
- });
- });
-
- describe('closed', () => {
- it('emits a closed event', () => {
- vm.closed();
-
- expect(vm.$emit).toHaveBeenCalledWith('closed');
- });
- });
- });
-
- describe('slots', () => {
- const slotContent = 'this should go into the slot';
-
- const modalWithSlot = (slot) => {
- return Vue.extend({
- components: {
- DeprecatedModal2,
- },
- render: (h) =>
- h('deprecated-modal-2', [slot ? h('template', { slot }, slotContent) : slotContent]),
- });
- };
-
- describe('default slot', () => {
- beforeEach(() => {
- vm = mountComponent(modalWithSlot());
- });
-
- it('sets the modal body', () => {
- const modalBody = vm.$el.querySelector('.modal-body');
-
- expect(modalBody.innerHTML).toBe(slotContent);
- });
- });
-
- describe('header slot', () => {
- beforeEach(() => {
- vm = mountComponent(modalWithSlot('header'));
- });
-
- it('sets the modal header', () => {
- const modalHeader = vm.$el.querySelector('.modal-header');
-
- expect(modalHeader.innerHTML).toBe(slotContent);
- });
- });
-
- describe('title slot', () => {
- beforeEach(() => {
- vm = mountComponent(modalWithSlot('title'));
- });
-
- it('sets the modal title', () => {
- const modalTitle = vm.$el.querySelector('.modal-title');
-
- expect(modalTitle.innerHTML).toBe(slotContent);
- });
- });
-
- describe('footer slot', () => {
- beforeEach(() => {
- vm = mountComponent(modalWithSlot('footer'));
- });
-
- it('sets the modal footer', () => {
- const modalFooter = vm.$el.querySelector('.modal-footer');
-
- expect(modalFooter.innerHTML).toBe(slotContent);
- });
- });
- });
-
- describe('handling sizes', () => {
- it('should render modal-sm', () => {
- vm = mountComponent(modalComponent, {
- modalSize: 'sm',
- });
-
- expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-sm')).toEqual(true);
- });
-
- it('should render modal-lg', () => {
- vm = mountComponent(modalComponent, {
- modalSize: 'lg',
- });
-
- expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-lg')).toEqual(true);
- });
-
- it('should render modal-xl', () => {
- vm = mountComponent(modalComponent, {
- modalSize: 'xl',
- });
-
- expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-xl')).toEqual(true);
- });
-
- it('should not add modal size classes when md size is passed', () => {
- vm = mountComponent(modalComponent, {
- modalSize: 'md',
- });
-
- expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-md')).toEqual(false);
- });
-
- it('should not add modal size classes by default', () => {
- vm = mountComponent(modalComponent, {});
-
- expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-sm')).toEqual(false);
- expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-lg')).toEqual(false);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap b/spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap
index b8a9143bc79..c454166e30b 100644
--- a/spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap
+++ b/spec/frontend/vue_shared/components/markdown/__snapshots__/suggestion_diff_spec.js.snap
@@ -7,6 +7,7 @@ exports[`Suggestion Diff component matches snapshot 1`] = `
<suggestion-diff-header-stub
batchsuggestionscount="1"
class="qa-suggestion-diff-header js-suggestion-diff-header"
+ defaultcommitmessage="Apply suggestion"
helppagepath="path_to_docs"
isapplyingbatch="true"
isbatched="true"
diff --git a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
index 36bea798056..b9f0d88548d 100644
--- a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
@@ -3,7 +3,7 @@ import { GlDropdown, GlFormTextarea, GlButton } from '@gitlab/ui';
import ApplySuggestionComponent from '~/vue_shared/components/markdown/apply_suggestion.vue';
describe('Apply Suggestion component', () => {
- const propsData = { fileName: 'test.js', disabled: false };
+ const propsData = { defaultCommitMessage: 'Apply suggestion', disabled: false };
let wrapper;
const createWrapper = (props) => {
@@ -27,7 +27,6 @@ describe('Apply Suggestion component', () => {
expect(dropdown.exists()).toBe(true);
expect(dropdown.props('text')).toBe('Apply suggestion');
- expect(dropdown.props('headerText')).toBe('Apply suggestion commit message');
expect(dropdown.props('disabled')).toBe(false);
});
@@ -35,7 +34,7 @@ describe('Apply Suggestion component', () => {
const textArea = findTextArea();
expect(textArea.exists()).toBe(true);
- expect(textArea.attributes('placeholder')).toBe('Apply suggestion on test.js');
+ expect(textArea.attributes('placeholder')).toBe('Apply suggestion');
});
it('renders an apply button', () => {
@@ -55,11 +54,11 @@ describe('Apply Suggestion component', () => {
});
describe('apply suggestion', () => {
- it('emits an apply event with a default message if no message was added', () => {
+ it('emits an apply event with no message if no message was added', () => {
findTextArea().vm.$emit('input', null);
findApplyButton().vm.$emit('click');
- expect(wrapper.emitted('apply')).toEqual([['Apply suggestion on test.js']]);
+ expect(wrapper.emitted('apply')).toEqual([[null]]);
});
it('emits an apply event with a user-defined message', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index c0a000690f8..bf65adc866d 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -9,6 +9,7 @@ const DEFAULT_PROPS = {
isBatched: false,
isApplyingBatch: false,
helpPagePath: 'path_to_docs',
+ defaultCommitMessage: 'Apply suggestion',
};
describe('Suggestion Diff component', () => {
@@ -91,7 +92,7 @@ describe('Suggestion Diff component', () => {
});
it('emits apply', () => {
- expect(wrapper.emitted().apply).toEqual([[expect.any(Function)]]);
+ expect(wrapper.emitted().apply).toEqual([[expect.any(Function), undefined]]);
});
it('does not render apply suggestion and add to batch buttons', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
index 232feb126dc..5bd6bda2d2c 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -42,6 +42,7 @@ const MOCK_DATA = {
is_applying_batch: true,
},
helpPagePath: 'path_to_docs',
+ defaultCommitMessage: 'Apply suggestion',
batchSuggestionsInfo: [{ suggestionId }],
};
diff --git a/spec/frontend/vue_shared/components/markdown/suggestions_spec.js b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
index 87bf5b97fb2..6fcac2df0b6 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
@@ -44,6 +44,7 @@ const MOCK_DATA = {
`,
isApplied: false,
helpPagePath: 'path_to_docs',
+ defaultCommitMessage: 'Apply suggestion',
};
describe('Suggestion component', () => {
diff --git a/spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb b/spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb
deleted file mode 100644
index 59ec743f6ca..00000000000
--- a/spec/lib/gitlab/middleware/multipart/handler_for_jwt_params_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Middleware::Multipart::HandlerForJWTParams do
- using RSpec::Parameterized::TableSyntax
-
- let_it_be(:env) { Rack::MockRequest.env_for('/', method: 'post', params: {}) }
- let_it_be(:message) { { 'rewritten_fields' => {} } }
-
- describe '#allowed_paths' do
- let_it_be(:expected_allowed_paths) do
- [
- Dir.tmpdir,
- ::FileUploader.root,
- ::Gitlab.config.uploads.storage_path,
- ::JobArtifactUploader.workhorse_upload_path,
- ::LfsObjectUploader.workhorse_upload_path,
- File.join(Rails.root, 'public/uploads/tmp')
- ]
- end
-
- let_it_be(:expected_with_packages_path) { expected_allowed_paths + [::Packages::PackageFileUploader.workhorse_upload_path] }
-
- subject { described_class.new(env, message).send(:allowed_paths) }
-
- where(:package_features_enabled, :object_storage_enabled, :direct_upload_enabled, :expected_paths) do
- false | false | true | :expected_allowed_paths
- false | false | false | :expected_allowed_paths
- false | true | true | :expected_allowed_paths
- false | true | false | :expected_allowed_paths
- true | false | true | :expected_with_packages_path
- true | false | false | :expected_with_packages_path
- true | true | true | :expected_allowed_paths
- true | true | false | :expected_with_packages_path
- end
-
- with_them do
- before do
- stub_config(packages: {
- enabled: package_features_enabled,
- object_store: {
- enabled: object_storage_enabled,
- direct_upload: direct_upload_enabled
- },
- storage_path: '/any/dir'
- })
- end
-
- it { is_expected.to eq(send(expected_paths)) }
- end
- end
-end
diff --git a/spec/lib/gitlab/middleware/multipart_with_handler_for_jwt_params_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index a1e9ac6e425..65ec3535271 100644
--- a/spec/lib/gitlab/middleware/multipart_with_handler_for_jwt_params_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -21,10 +21,6 @@ RSpec.describe Gitlab::Middleware::Multipart do
middleware.call(env)
end
- before do
- stub_feature_flags(upload_middleware_jwt_params_handler: true)
- end
-
context 'remote file mode' do
let(:mode) { :remote }
@@ -34,7 +30,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(key: 'file', filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') }
+ let(:params) { upload_parameters_for(key: 'file', mode: mode, filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') }
it 'builds an UploadedFile' do
expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
@@ -55,14 +51,14 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:allowed_paths) { [Dir.tmpdir] }
before do
- expect_next_instance_of(::Gitlab::Middleware::Multipart::HandlerForJWTParams) do |handler|
+ expect_next_instance_of(::Gitlab::Middleware::Multipart::Handler) do |handler|
expect(handler).to receive(:allowed_paths).and_return(allowed_paths)
end
end
context 'in allowed paths' do
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename) }
+ let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename) }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, size: uploaded_file.size, params_path: %w(file))
@@ -75,7 +71,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:allowed_paths) { [] }
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file') }
+ let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode) }
it 'returns an error' do
result = subject
@@ -89,7 +85,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
context 'with dummy params in remote mode' do
let(:rewritten_fields) { { 'file' => 'should/not/be/read' } }
- let(:params) { upload_parameters_for(key: 'file') }
+ let(:params) { upload_parameters_for(key: 'file', mode: mode) }
let(:mode) { :remote }
context 'with an invalid secret' do
@@ -128,7 +124,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
RSpec.shared_examples 'rejecting the invalid key' do |key_in_header:, key_in_upload_params:, error_message:|
let(:rewritten_fields) { rewritten_fields_hash(key_in_header => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, filename: filename, remote_id: remote_id) }
+ let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, mode: mode, filename: filename, remote_id: remote_id) }
it 'raises an error' do
expect { subject }.to raise_error(RuntimeError, error_message)
@@ -171,7 +167,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:crafted_payload) { Base64.urlsafe_encode64({ 'path' => 'test' }.to_json) }
let(:params) do
- upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id).tap do |params|
+ upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename, remote_id: remote_id).tap do |params|
header, _, sig = params['file.gitlab-workhorse-upload'].split('.')
params['file.gitlab-workhorse-upload'] = [header, crafted_payload, sig].join('.')
end
@@ -187,7 +183,7 @@ RSpec.describe Gitlab::Middleware::Multipart do
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) do
- upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id).tap do |params|
+ upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename, remote_id: remote_id).tap do |params|
header, payload, sig = params['file.gitlab-workhorse-upload'].split('.')
params['file.gitlab-workhorse-upload'] = [header, payload, "#{sig}modified"].join('.')
end
diff --git a/spec/lib/gitlab/middleware/multipart_with_handler_spec.rb b/spec/lib/gitlab/middleware/multipart_with_handler_spec.rb
deleted file mode 100644
index 8c2af775574..00000000000
--- a/spec/lib/gitlab/middleware/multipart_with_handler_spec.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Middleware::Multipart do
- include MultipartHelpers
-
- describe '#call' do
- let(:app) { double(:app) }
- let(:middleware) { described_class.new(app) }
- let(:secret) { Gitlab::Workhorse.secret }
- let(:issuer) { 'gitlab-workhorse' }
-
- subject do
- env = post_env(
- rewritten_fields: rewritten_fields,
- params: params,
- secret: secret,
- issuer: issuer
- )
- middleware.call(env)
- end
-
- before do
- stub_feature_flags(upload_middleware_jwt_params_handler: false)
- end
-
- context 'remote file mode' do
- let(:mode) { :remote }
-
- it_behaves_like 'handling all upload parameters conditions'
-
- context 'and a path set' do
- include_context 'with one temporary file for multipart'
-
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(key: 'file', filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') }
-
- it 'builds an UploadedFile' do
- expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
-
- subject
- end
- end
- end
-
- context 'local file mode' do
- let(:mode) { :local }
-
- it_behaves_like 'handling all upload parameters conditions'
-
- context 'when file is' do
- include_context 'with one temporary file for multipart'
-
- let(:allowed_paths) { [Dir.tmpdir] }
-
- before do
- expect_next_instance_of(::Gitlab::Middleware::Multipart::Handler) do |handler|
- expect(handler).to receive(:allowed_paths).and_return(allowed_paths)
- end
- end
-
- context 'in allowed paths' do
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename) }
-
- it 'builds an UploadedFile' do
- expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, size: uploaded_file.size, params_path: %w(file))
-
- subject
- end
- end
-
- context 'not in allowed paths' do
- let(:allowed_paths) { [] }
-
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file') }
-
- it 'returns an error' do
- result = subject
-
- expect(result[0]).to eq(400)
- expect(result[2]).to include('insecure path used')
- end
- end
- end
- end
-
- context 'with dummy params in remote mode' do
- let(:rewritten_fields) { { 'file' => 'should/not/be/read' } }
- let(:params) { upload_parameters_for(key: 'file') }
- let(:mode) { :remote }
-
- context 'with an invalid secret' do
- let(:secret) { 'INVALID_SECRET' }
-
- it { expect { subject }.to raise_error(JWT::VerificationError) }
- end
-
- context 'with an invalid issuer' do
- let(:issuer) { 'INVALID_ISSUER' }
-
- it { expect { subject }.to raise_error(JWT::InvalidIssuerError) }
- end
-
- context 'with invalid rewritten field key' do
- invalid_keys = [
- '[file]',
- ';file',
- 'file]',
- ';file]',
- 'file]]',
- 'file;;'
- ]
-
- invalid_keys.each do |invalid_key|
- context invalid_key do
- let(:rewritten_fields) { { invalid_key => 'should/not/be/read' } }
-
- it { expect { subject }.to raise_error(RuntimeError, "invalid field: \"#{invalid_key}\"") }
- end
- end
- end
-
- context 'with invalid key in parameters' do
- include_context 'with one temporary file for multipart'
-
- let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'wrong_key', filename: filename, remote_id: remote_id) }
-
- it 'builds no UploadedFile' do
- expect(app).to receive(:call) do |env|
- received_params = get_params(env)
- expect(received_params['file']).to be_nil
- expect(received_params['wrong_key']).to be_nil
- end
-
- subject
- end
- end
-
- context 'with invalid key in header' do
- include_context 'with one temporary file for multipart'
-
- RSpec.shared_examples 'rejecting the invalid key' do |key_in_header:, key_in_upload_params:, error_message:|
- let(:rewritten_fields) { rewritten_fields_hash(key_in_header => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, filename: filename, remote_id: remote_id) }
-
- it 'raises an error' do
- expect { subject }.to raise_error(RuntimeError, error_message)
- end
- end
-
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'user[avatar',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "user[avatar"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: '[user]avatar',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "[user]avatar"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'user[]avatar',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "user[]avatar"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'user[avatar[image[url]]]',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "user[avatar[image[url]]]"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: '[]',
- key_in_upload_params: 'user[avatar]',
- error_message: 'invalid field: "[]"'
- it_behaves_like 'rejecting the invalid key',
- key_in_header: 'x' * 11000,
- key_in_upload_params: 'user[avatar]',
- error_message: "invalid field: \"#{'x' * 11000}\""
- end
-
- context 'with key with unbalanced brackets in header' do
- include_context 'with one temporary file for multipart'
-
- let(:invalid_key) { 'user[avatar' }
- let(:rewritten_fields) { rewritten_fields_hash( invalid_key => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'user[avatar]', filename: filename, remote_id: remote_id) }
-
- it 'builds no UploadedFile' do
- expect(app).not_to receive(:call)
-
- expect { subject }.to raise_error(RuntimeError, "invalid field: \"#{invalid_key}\"")
- end
- end
- end
- end
-end
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index 8425e1dbd46..ececc84bc93 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -27,12 +27,12 @@ RSpec.describe UploadedFile do
end
it 'handles a blank path' do
- params['file.path'] = ''
+ params['path'] = ''
# Not a real file, so can't determine size itself
- params['file.size'] = 1.byte
+ params['size'] = 1.byte
- expect { described_class.from_params(params, :file, upload_path) }
+ expect { described_class.from_params(params, upload_path) }
.not_to raise_error
end
end
@@ -50,7 +50,7 @@ RSpec.describe UploadedFile do
end
end
- describe '.from_params_without_field' do
+ describe '.from_params' do
let(:upload_path) { nil }
after do
@@ -58,7 +58,7 @@ RSpec.describe UploadedFile do
end
subject do
- described_class.from_params_without_field(params, [upload_path, Dir.tmpdir])
+ described_class.from_params(params, [upload_path, Dir.tmpdir])
end
context 'when valid file is specified' do
@@ -170,190 +170,6 @@ RSpec.describe UploadedFile do
end
end
end
-
- describe '.from_params' do
- let(:upload_path) { nil }
- let(:file_path_override) { nil }
-
- after do
- FileUtils.rm_r(upload_path) if upload_path
- end
-
- subject do
- described_class.from_params(params, :file, [upload_path, Dir.tmpdir], file_path_override)
- end
-
- RSpec.shared_context 'filepath override' do
- let(:temp_file_override) { Tempfile.new(%w[override override], temp_dir) }
- let(:file_path_override) { temp_file_override.path }
-
- before do
- FileUtils.touch(temp_file_override)
- end
-
- after do
- FileUtils.rm_f(temp_file_override)
- end
- end
-
- context 'when valid file is specified' do
- context 'only local path is specified' do
- let(:params) do
- { 'file.path' => temp_file.path }
- end
-
- it { is_expected.not_to be_nil }
-
- it "generates filename from path" do
- expect(subject.original_filename).to eq(::File.basename(temp_file.path))
- end
- end
-
- context 'all parameters are specified' do
- context 'with a filepath' do
- let(:params) do
- { 'file.path' => temp_file.path,
- 'file.name' => 'dir/my file&.txt',
- 'file.type' => 'my/type',
- 'file.sha256' => 'sha256' }
- end
-
- it_behaves_like 'using the file path',
- filename: 'my_file_.txt',
- content_type: 'my/type',
- sha256: 'sha256',
- path_suffix: 'test'
- end
-
- context 'with a filepath override' do
- include_context 'filepath override'
-
- let(:params) do
- { 'file.path' => temp_file.path,
- 'file.name' => 'dir/my file&.txt',
- 'file.type' => 'my/type',
- 'file.sha256' => 'sha256' }
- end
-
- it_behaves_like 'using the file path',
- filename: 'my_file_.txt',
- content_type: 'my/type',
- sha256: 'sha256',
- path_suffix: 'override'
- end
-
- context 'with a remote id' do
- let(:params) do
- {
- 'file.name' => 'dir/my file&.txt',
- 'file.sha256' => 'sha256',
- 'file.remote_url' => 'http://localhost/file',
- 'file.remote_id' => '1234567890',
- 'file.etag' => 'etag1234567890',
- 'file.size' => '123456'
- }
- end
-
- it_behaves_like 'using the remote id',
- filename: 'my_file_.txt',
- content_type: 'application/octet-stream',
- sha256: 'sha256',
- size: 123456,
- remote_id: '1234567890'
- end
-
- context 'with a path and a remote id' do
- let(:params) do
- {
- 'file.path' => temp_file.path,
- 'file.name' => 'dir/my file&.txt',
- 'file.sha256' => 'sha256',
- 'file.remote_url' => 'http://localhost/file',
- 'file.remote_id' => '1234567890',
- 'file.etag' => 'etag1234567890',
- 'file.size' => '123456'
- }
- end
-
- it_behaves_like 'using the remote id',
- filename: 'my_file_.txt',
- content_type: 'application/octet-stream',
- sha256: 'sha256',
- size: 123456,
- remote_id: '1234567890'
- end
-
- context 'with a path override and a remote id' do
- include_context 'filepath override'
-
- let(:params) do
- {
- 'file.name' => 'dir/my file&.txt',
- 'file.sha256' => 'sha256',
- 'file.remote_url' => 'http://localhost/file',
- 'file.remote_id' => '1234567890',
- 'file.etag' => 'etag1234567890',
- 'file.size' => '123456'
- }
- end
-
- it_behaves_like 'using the remote id',
- filename: 'my_file_.txt',
- content_type: 'application/octet-stream',
- sha256: 'sha256',
- size: 123456,
- remote_id: '1234567890'
- end
- end
- end
-
- context 'when no params are specified' do
- let(:params) do
- {}
- end
-
- it "does not return an object" do
- is_expected.to be_nil
- end
- end
-
- context 'when verifying allowed paths' do
- let(:params) do
- { 'file.path' => temp_file.path }
- end
-
- context 'when file is stored in system temporary folder' do
- let(:temp_dir) { Dir.tmpdir }
-
- it "succeeds" do
- is_expected.not_to be_nil
- end
- end
-
- context 'when file is stored in user provided upload path' do
- let(:upload_path) { Dir.mktmpdir }
- let(:temp_dir) { upload_path }
-
- it "succeeds" do
- is_expected.not_to be_nil
- end
- end
-
- context 'when file is stored outside of user provided upload path' do
- let!(:generated_dir) { Dir.mktmpdir }
- let!(:temp_dir) { Dir.mktmpdir }
-
- before do
- # We overwrite default temporary path
- allow(Dir).to receive(:tmpdir).and_return(generated_dir)
- end
-
- it "raises an error" do
- expect { subject }.to raise_error(UploadedFile::InvalidPathError, /insecure path used/)
- end
- end
- end
- end
end
describe '.initialize' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 4a63de594e1..c2029b9240b 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -4775,22 +4775,6 @@ RSpec.describe Ci::Build do
describe '#debug_mode?' do
subject { build.debug_mode? }
- context 'when feature is disabled' do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- end
-
- it { is_expected.to eq false }
-
- context 'when in variables' do
- before do
- create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
- end
-
- it { is_expected.to eq false }
- end
- end
-
context 'when CI_DEBUG_TRACE=true is in variables' do
context 'when in instance variables' do
before do
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index aeb8e3642ed..2ea237469b1 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -30,6 +30,10 @@ RSpec.describe API::Invitations do
api("/#{source.model_name.plural}/#{source.id}/invitations", user)
end
+ def invite_member_by_email(source, source_type, email, created_by)
+ create(:"#{source_type}_member", invite_token: '123', invite_email: email, source: source, user: nil, created_by: created_by)
+ end
+
shared_examples 'POST /:source_type/:id/invitations' do |source_type|
context "with :source_type == #{source_type.pluralize}" do
it_behaves_like 'a 404 response when source is private' do
@@ -280,10 +284,6 @@ RSpec.describe API::Invitations do
expect(json_response.first['created_by_name']).to eq(developer.name)
expect(json_response.first['user_name']).to eq(nil)
end
-
- def invite_member_by_email(source, source_type, email, created_by)
- create(:"#{source_type}_member", invite_token: '123', invite_email: email, source: source, user: nil, created_by: created_by)
- end
end
end
@@ -298,4 +298,80 @@ RSpec.describe API::Invitations do
let(:source) { group }
end
end
+
+ shared_examples 'DELETE /:source_type/:id/invitations/:email' do |source_type|
+ def invite_api(source, user, email)
+ api("/#{source.model_name.plural}/#{source.id}/invitations/#{email}", user)
+ end
+
+ context "with :source_type == #{source_type.pluralize}" do
+ let!(:invite) { invite_member_by_email(source, source_type, developer.email, developer) }
+
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/invitations/#{invite.invite_email}", stranger) }
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+
+ delete invite_api(source, user, invite.invite_email)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a member and deleting themself' do
+ it 'does not delete the member' do
+ expect do
+ delete invite_api(source, developer, invite.invite_email)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end.not_to change { source.members.count }
+ end
+ end
+
+ context 'when authenticated as a maintainer/owner' do
+ it 'deletes the member and returns 204 with no content' do
+ expect do
+ delete invite_api(source, maintainer, invite.invite_email)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end.to change { source.members.count }.by(-1)
+ end
+ end
+
+ it 'returns 404 if member does not exist' do
+ delete invite_api(source, maintainer, non_existing_record_id)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 422 for a valid request if the resource was not destroyed' do
+ allow_next_instance_of(::Members::DestroyService) do |instance|
+ allow(instance).to receive(:execute).with(invite).and_return(invite)
+ end
+
+ delete invite_api(source, maintainer, invite.invite_email)
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/inviations/:email' do
+ it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'project' do
+ let(:source) { project }
+ end
+ end
+
+ describe 'DELETE /groups/:id/inviations/:email' do
+ it_behaves_like 'DELETE /:source_type/:id/invitations/:email', 'group' do
+ let(:source) { group }
+ end
+ end
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 4d594c8ac27..6f854a28cec 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -827,32 +827,6 @@ RSpec.describe API::Jobs do
expect(response).to have_gitlab_http_status(expected_status)
end
end
-
- context 'with restrict_access_to_build_debug_mode feature disabled' do
- before do
- stub_feature_flags(restrict_access_to_build_debug_mode: false)
- end
-
- where(:public_builds, :user_project_role, :expected_status) do
- true | 'developer' | :ok
- true | 'guest' | :ok
- false | 'developer' | :ok
- false | 'guest' | :forbidden
- end
-
- with_them do
- before do
- project.update!(public_builds: public_builds)
- project.add_role(user, user_project_role)
-
- get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
- end
-
- it 'renders trace to authorized users' do
- expect(response).to have_gitlab_http_status(expected_status)
- end
- end
- end
end
end
diff --git a/spec/support/helpers/multipart_helpers.rb b/spec/support/helpers/multipart_helpers.rb
index bcb184f84c5..8438a83aa8a 100644
--- a/spec/support/helpers/multipart_helpers.rb
+++ b/spec/support/helpers/multipart_helpers.rb
@@ -13,29 +13,23 @@ module MultipartHelpers
)
end
- # This function assumes a `mode` variable to be set
- def upload_parameters_for(filepath: nil, key: nil, filename: 'filename', remote_id: 'remote_id')
+ def upload_parameters_for(filepath: nil, key: nil, mode: nil, filename: 'filename', remote_id: 'remote_id')
result = {
- "#{key}.name" => filename,
- "#{key}.type" => "application/octet-stream",
- "#{key}.sha256" => "1234567890"
+ "name" => filename,
+ "type" => "application/octet-stream",
+ "sha256" => "1234567890"
}
case mode
when :local
- result["#{key}.path"] = filepath
+ result["path"] = filepath
when :remote
- result["#{key}.remote_id"] = remote_id
- result["#{key}.size"] = 3.megabytes
+ result["remote_id"] = remote_id
+ result["size"] = 3.megabytes
else
raise ArgumentError, "can't handle #{mode} mode"
end
- return result if ::Feature.disabled?(:upload_middleware_jwt_params_handler, default_enabled: true)
-
- # the HandlerForJWTParams expects a jwt token with the upload parameters
- # *without* the "#{key}." prefix
- result.deep_transform_keys! { |k| k.remove("#{key}.") }
{
"#{key}.gitlab-workhorse-upload" => jwt_token(data: { 'upload' => result })
}
diff --git a/spec/support/shared_examples/features/file_uploads_shared_examples.rb b/spec/support/shared_examples/features/file_uploads_shared_examples.rb
index ea8c8d44501..d586bf03b59 100644
--- a/spec/support/shared_examples/features/file_uploads_shared_examples.rb
+++ b/spec/support/shared_examples/features/file_uploads_shared_examples.rb
@@ -2,28 +2,6 @@
RSpec.shared_examples 'handling file uploads' do |shared_examples_name|
context 'with object storage disabled' do
- context 'with upload_middleware_jwt_params_handler disabled' do
- before do
- stub_feature_flags(upload_middleware_jwt_params_handler: false)
-
- expect_next_instance_of(Gitlab::Middleware::Multipart::Handler) do |handler|
- expect(handler).to receive(:with_open_files).and_call_original
- end
- end
-
- it_behaves_like shared_examples_name
- end
-
- context 'with upload_middleware_jwt_params_handler enabled' do
- before do
- stub_feature_flags(upload_middleware_jwt_params_handler: true)
-
- expect_next_instance_of(Gitlab::Middleware::Multipart::HandlerForJWTParams) do |handler|
- expect(handler).to receive(:with_open_files).and_call_original
- end
- end
-
- it_behaves_like shared_examples_name
- end
+ it_behaves_like shared_examples_name
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb
index 6327367fcc2..40deaa27955 100644
--- a/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/middleware/multipart_shared_examples.rb
@@ -5,7 +5,7 @@ RSpec.shared_examples 'handling all upload parameters conditions' do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
- let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', filename: filename, remote_id: remote_id) }
+ let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename, remote_id: remote_id) }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file))
@@ -19,8 +19,8 @@ RSpec.shared_examples 'handling all upload parameters conditions' do
let(:rewritten_fields) { rewritten_fields_hash('file1' => uploaded_filepath, 'file2' => uploaded_filepath2) }
let(:params) do
- upload_parameters_for(filepath: uploaded_filepath, key: 'file1', filename: filename, remote_id: remote_id).merge(
- upload_parameters_for(filepath: uploaded_filepath2, key: 'file2', filename: filename2, remote_id: remote_id2)
+ upload_parameters_for(filepath: uploaded_filepath, key: 'file1', mode: mode, filename: filename, remote_id: remote_id).merge(
+ upload_parameters_for(filepath: uploaded_filepath2, key: 'file2', mode: mode, filename: filename2, remote_id: remote_id2)
)
end
@@ -38,7 +38,7 @@ RSpec.shared_examples 'handling all upload parameters conditions' do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('user[avatar]' => uploaded_filepath) }
- let(:params) { { 'user' => { 'avatar' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id) } } }
+ let(:params) { { 'user' => { 'avatar' => upload_parameters_for(filepath: uploaded_filepath, mode: mode, filename: filename, remote_id: remote_id) } } }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar))
@@ -54,8 +54,8 @@ RSpec.shared_examples 'handling all upload parameters conditions' do
let(:params) do
{
'user' => {
- 'avatar' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id),
- 'screenshot' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2)
+ 'avatar' => upload_parameters_for(filepath: uploaded_filepath, mode: mode, filename: filename, remote_id: remote_id),
+ 'screenshot' => upload_parameters_for(filepath: uploaded_filepath2, mode: mode, filename: filename2, remote_id: remote_id2)
}
}
end
@@ -74,7 +74,7 @@ RSpec.shared_examples 'handling all upload parameters conditions' do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('user[avatar][bananas]' => uploaded_filepath) }
- let(:params) { { 'user' => { 'avatar' => { 'bananas' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id) } } } }
+ let(:params) { { 'user' => { 'avatar' => { 'bananas' => upload_parameters_for(filepath: uploaded_filepath, mode: mode, filename: filename, remote_id: remote_id) } } } }
it 'builds an UploadedFile' do
expect_uploaded_files(filepath: uploaded_file, original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(user avatar bananas))
@@ -91,10 +91,10 @@ RSpec.shared_examples 'handling all upload parameters conditions' do
{
'user' => {
'avatar' => {
- 'bananas' => upload_parameters_for(filepath: uploaded_filepath, filename: filename, remote_id: remote_id)
+ 'bananas' => upload_parameters_for(filepath: uploaded_filepath, mode: mode, filename: filename, remote_id: remote_id)
},
'friend' => {
- 'ananas' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2)
+ 'ananas' => upload_parameters_for(filepath: uploaded_filepath2, mode: mode, filename: filename2, remote_id: remote_id2)
}
}
}
@@ -122,11 +122,11 @@ RSpec.shared_examples 'handling all upload parameters conditions' do
end
let(:params) do
- upload_parameters_for(filepath: uploaded_filepath, filename: filename, key: 'file', remote_id: remote_id).merge(
+ upload_parameters_for(filepath: uploaded_filepath, filename: filename, key: 'file', mode: mode, remote_id: remote_id).merge(
'user' => {
- 'avatar' => upload_parameters_for(filepath: uploaded_filepath2, filename: filename2, remote_id: remote_id2),
+ 'avatar' => upload_parameters_for(filepath: uploaded_filepath2, mode: mode, filename: filename2, remote_id: remote_id2),
'friend' => {
- 'avatar' => upload_parameters_for(filepath: uploaded_filepath3, filename: filename3, remote_id: remote_id3)
+ 'avatar' => upload_parameters_for(filepath: uploaded_filepath3, mode: mode, filename: filename3, remote_id: remote_id3)
}
}
)
diff --git a/tmp/.gitignore b/tmp/.gitignore
index a383b23fb78..450053eb4f3 100644
--- a/tmp/.gitignore
+++ b/tmp/.gitignore
@@ -1,5 +1,10 @@
*
-!*/
!.gitignore
!.gitkeep
+
+# explicitly list ignored sub directories to speed up performance
+/cache/
+/capybara/
+/letter_opener/
+/rubocop_cache/
/tests/