summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue132
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/token.vue28
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/actions.js28
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/index.js2
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/state.js1
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/saved_replies_dropdown.vue29
-rw-r--r--app/assets/stylesheets/pages/commits.scss15
-rw-r--r--app/finders/context_commits_finder.rb8
-rw-r--r--app/models/application_setting.rb14
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/services/security/ci_configuration/base_create_service.rb3
-rw-r--r--config/feature_flags/development/use_marker_ranges.yml8
-rw-r--r--doc/administration/instance_limits.md36
-rw-r--r--lib/gitlab/diff/highlight.rb20
-rw-r--r--lib/gitlab/diff/highlight_cache.rb1
-rw-r--r--lib/gitlab/diff/inline_diff.rb21
-rw-r--r--locale/gitlab.pot38
-rwxr-xr-xscripts/pipeline/create_test_failure_issues.rb67
-rw-r--r--spec/features/profiles/user_uses_saved_reply_spec.rb2
-rw-r--r--spec/finders/context_commits_finder_spec.rb21
-rw-r--r--spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap11
-rw-r--r--spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js33
-rw-r--r--spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js30
-rw-r--r--spec/lib/gitlab/diff/highlight_cache_spec.rb16
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb15
-rw-r--r--spec/models/application_setting_spec.rb7
-rw-r--r--spec/models/repository_spec.rb9
-rw-r--r--spec/scripts/pipeline/create_test_failure_issues_spec.rb169
-rw-r--r--spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb3
-rw-r--r--workhorse/go.mod2
-rw-r--r--workhorse/go.sum4
33 files changed, 553 insertions, 233 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 342561afd0c..7a493ac2f09 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-4a6eb3bfb6ecd8324a39089cb3b59e282936331c
+6db6349ef26137d13a7176897bcb01232b289521
diff --git a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
index c66b595ffdc..a5f8f369604 100644
--- a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
+++ b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
@@ -1,10 +1,15 @@
<script>
-import { GlModal, GlTabs, GlTab, GlSearchBoxByType, GlSprintf, GlBadge } from '@gitlab/ui';
+import { GlModal, GlTabs, GlTab, GlSprintf, GlBadge, GlFilteredSearch } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue';
import { createAlert } from '~/alert';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
-import { s__ } from '~/locale';
+import { __, s__ } from '~/locale';
+import {
+ OPERATORS_IS,
+ TOKEN_TYPE_AUTHOR,
+} from '~/vue_shared/components/filtered_search_bar/constants';
+import UserToken from '~/vue_shared/components/filtered_search_bar/tokens/user_token.vue';
import eventHub from '../event_hub';
import {
findCommitIndex,
@@ -12,6 +17,7 @@ import {
removeIfReadyToBeRemoved,
removeIfPresent,
} from '../utils';
+import Token from './token.vue';
export default {
components: {
@@ -19,9 +25,9 @@ export default {
GlTabs,
GlTab,
ReviewTabContainer,
- GlSearchBoxByType,
GlSprintf,
GlBadge,
+ GlFilteredSearch,
},
props: {
contextCommitsPath: {
@@ -41,6 +47,49 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ availableTokens: [
+ {
+ icon: 'pencil',
+ title: __('Author'),
+ type: TOKEN_TYPE_AUTHOR,
+ operators: OPERATORS_IS,
+ token: UserToken,
+ defaultAuthors: [],
+ unique: true,
+ fetchAuthors: this.fetchAuthors,
+ initialAuthors: [],
+ },
+ {
+ formattedKey: __('Committed-before'),
+ key: 'committed-before',
+ type: 'committed-before-date',
+ param: '',
+ symbol: '',
+ icon: 'clock',
+ tag: 'committed_before',
+ title: __('Committed-before'),
+ operators: OPERATORS_IS,
+ token: Token,
+ unique: true,
+ },
+ {
+ formattedKey: __('Committed-after'),
+ key: 'committed-after',
+ type: 'committed-after-date',
+ param: '',
+ symbol: '',
+ icon: 'clock',
+ tag: 'committed_after',
+ title: __('Committed-after'),
+ operators: OPERATORS_IS,
+ token: Token,
+ unique: true,
+ },
+ ],
+ };
+ },
computed: {
...mapState([
'tabIndex',
@@ -98,8 +147,6 @@ export default {
},
beforeDestroy() {
eventHub.$off('openModal', this.openModal);
- clearTimeout(this.timeout);
- this.timeout = null;
},
methods: {
...mapActions([
@@ -114,10 +161,8 @@ export default {
'setSearchText',
'setToRemoveCommits',
'resetModalState',
+ 'fetchAuthors',
]),
- focusSearch() {
- this.$refs.searchInput.focusInput();
- },
openModal() {
this.searchCommits();
this.fetchContextCommits();
@@ -125,7 +170,6 @@ export default {
},
handleTabChange(tabIndex) {
if (tabIndex === 0) {
- this.focusSearch();
if (this.shouldPurge) {
this.setSelectedCommits(
[...this.commits, ...this.selectedCommits].filter((commit) => commit.isSelected),
@@ -133,17 +177,36 @@ export default {
}
}
},
- handleSearchCommits(value) {
- // We only call the service, if we have 3 characters or we don't have any characters
- if (value.length >= 3) {
- clearTimeout(this.timeout);
- this.timeout = setTimeout(() => {
- this.searchCommits(value);
- }, 500);
- } else if (value.length === 0) {
- this.searchCommits();
+ blurSearchInput() {
+ const searchInputEl = this.$refs.filteredSearchInput.$el.querySelector(
+ '.gl-filtered-search-token-segment-input',
+ );
+ if (searchInputEl) {
+ searchInputEl.blur();
}
- this.setSearchText(value);
+ },
+ handleSearchCommits(value = []) {
+ const searchValues = value.reduce((acc, searchFilter) => {
+ const isEqualSearch = searchFilter?.value?.operator === '=';
+
+ if (!isEqualSearch && typeof searchFilter === 'object') return acc;
+
+ if (typeof searchFilter === 'string' && searchFilter.length >= 3) {
+ acc.searchText = searchFilter;
+ } else if (searchFilter?.type === 'author' && searchFilter?.value?.data?.length >= 3) {
+ acc.author = searchFilter?.value?.data;
+ } else if (searchFilter?.type === 'committed-before-date') {
+ acc.committed_before = searchFilter?.value?.data;
+ } else if (searchFilter?.type === 'committed-after-date') {
+ acc.committed_after = searchFilter?.value?.data;
+ }
+
+ return acc;
+ }, {});
+
+ this.searchCommits(searchValues);
+ this.blurSearchInput();
+ this.setSearchText(searchValues.searchText);
},
handleCommitRowSelect(event) {
const index = event[0];
@@ -208,11 +271,12 @@ export default {
},
handleModalClose() {
this.resetModalState();
- clearTimeout(this.timeout);
},
handleModalHide() {
this.resetModalState();
- clearTimeout(this.timeout);
+ },
+ shouldShowInputDateFormat(value) {
+ return ['Committed-before', 'Committed-after'].indexOf(value) !== -1;
},
},
};
@@ -223,13 +287,14 @@ export default {
ref="modal"
cancel-variant="light"
size="md"
+ no-focus-on-show
+ modal-class="add-review-item-modal"
body-class="add-review-item pt-0"
:scrollable="true"
:ok-title="__('Save changes')"
modal-id="add-review-item"
:title="__('Add or remove previously merged commits')"
:ok-disabled="disableSaveButton"
- @shown="focusSearch"
@ok="handleCreateContextCommits"
@cancel="handleModalClose"
@close="handleModalClose"
@@ -245,11 +310,24 @@ export default {
</gl-sprintf>
</template>
<div class="gl-mt-3">
- <gl-search-box-by-type
- ref="searchInput"
- :placeholder="__(`Search by commit title or SHA`)"
- @input="handleSearchCommits"
- />
+ <gl-filtered-search
+ ref="filteredSearchInput"
+ class="flex-grow-1"
+ :placeholder="__(`Search or filter commits`)"
+ :available-tokens="availableTokens"
+ @clear="handleSearchCommits"
+ @submit="handleSearchCommits"
+ >
+ <template #title="{ value }">
+ <div>
+ {{ value }}
+ <span v-if="shouldShowInputDateFormat(value)" class="title-hint-text">
+ &lt;{{ __('yyyy-mm-dd') }}&gt;
+ </span>
+ </div>
+ </template>
+ </gl-filtered-search>
+
<review-tab-container
:is-loading="isLoadingCommits"
:loading-error="commitsLoadingError"
diff --git a/app/assets/javascripts/add_context_commits_modal/components/token.vue b/app/assets/javascripts/add_context_commits_modal/components/token.vue
new file mode 100644
index 00000000000..c403adbbf60
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/components/token.vue
@@ -0,0 +1,28 @@
+<script>
+import { GlFilteredSearchToken } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlFilteredSearchToken,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ val: '',
+ };
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners" />
+</template>
diff --git a/app/assets/javascripts/add_context_commits_modal/store/actions.js b/app/assets/javascripts/add_context_commits_modal/store/actions.js
index de9c7488ace..f085b0d0e5e 100644
--- a/app/assets/javascripts/add_context_commits_modal/store/actions.js
+++ b/app/assets/javascripts/add_context_commits_modal/store/actions.js
@@ -1,8 +1,11 @@
import _ from 'lodash';
+import * as Sentry from '@sentry/browser';
import Api from '~/api';
import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
+import { joinPaths } from '~/lib/utils/url_utility';
+import { ACTIVE_AND_BLOCKED_USER_STATES } from '~/users_select/constants';
import * as types from './mutation_types';
export const setBaseConfig = ({ commit }, options) => {
@@ -11,14 +14,14 @@ export const setBaseConfig = ({ commit }, options) => {
export const setTabIndex = ({ commit }, tabIndex) => commit(types.SET_TABINDEX, tabIndex);
-export const searchCommits = ({ dispatch, commit, state }, searchText) => {
+export const searchCommits = ({ dispatch, commit, state }, search = {}) => {
commit(types.FETCH_COMMITS);
let params = {};
- if (searchText) {
+ if (search) {
params = {
params: {
- search: searchText,
+ ...search,
per_page: 40,
},
};
@@ -37,7 +40,7 @@ export const searchCommits = ({ dispatch, commit, state }, searchText) => {
}
return c;
});
- if (!searchText) {
+ if (!search) {
dispatch('setCommits', { commits: [...commits, ...state.contextCommits] });
} else {
dispatch('setCommits', { commits });
@@ -131,6 +134,23 @@ export const setSelectedCommits = ({ commit }, selected) => {
commit(types.SET_SELECTED_COMMITS, selectedCommits);
};
+export const fetchAuthors = ({ dispatch, state }, author = null) => {
+ const { projectId } = state;
+ return axios
+ .get(joinPaths(gon.relative_url_root || '', '/-/autocomplete/users.json'), {
+ params: {
+ project_id: projectId,
+ states: ACTIVE_AND_BLOCKED_USER_STATES,
+ search: author,
+ },
+ })
+ .then(({ data }) => data)
+ .catch((error) => {
+ Sentry.captureException(error);
+ dispatch('receiveAuthorsError');
+ });
+};
+
export const setSearchText = ({ commit }, searchText) => commit(types.SET_SEARCH_TEXT, searchText);
export const setToRemoveCommits = ({ commit }, data) => commit(types.SET_TO_REMOVE_COMMITS, data);
diff --git a/app/assets/javascripts/add_context_commits_modal/store/index.js b/app/assets/javascripts/add_context_commits_modal/store/index.js
index 0bf3441379b..560834a26ae 100644
--- a/app/assets/javascripts/add_context_commits_modal/store/index.js
+++ b/app/assets/javascripts/add_context_commits_modal/store/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import Vuex from 'vuex';
+import filters from '~/vue_shared/components/filtered_search_bar/store/modules/filters';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
@@ -12,4 +13,5 @@ export default () =>
state: state(),
actions,
mutations,
+ modules: { filters },
});
diff --git a/app/assets/javascripts/add_context_commits_modal/store/state.js b/app/assets/javascripts/add_context_commits_modal/store/state.js
index 37239adccbb..fed3148bc9e 100644
--- a/app/assets/javascripts/add_context_commits_modal/store/state.js
+++ b/app/assets/javascripts/add_context_commits_modal/store/state.js
@@ -1,4 +1,5 @@
export default () => ({
+ projectId: '',
contextCommitsPath: '',
tabIndex: 0,
isLoadingCommits: false,
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 2d5e9bc91f2..a2873622682 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -371,7 +371,7 @@ export function insertMarkdownText({
});
}
-function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagContent }) {
+export function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagContent }) {
const $textArea = $(textArea);
textArea = $textArea.get(0);
const text = $textArea.val();
@@ -627,10 +627,9 @@ export function addMarkdownListeners(form) {
});
const $allToolbarBtns = $(form)
- .off('click', '.js-md, .saved-replies-dropdown li')
- .on('click', '.js-md, .saved-replies-dropdown li', function () {
- const $savedReplyContent = $('.js-saved-reply-content', this);
- const $toolbarBtn = $savedReplyContent.length ? $savedReplyContent : $(this);
+ .off('click', '.js-md')
+ .on('click', '.js-md', function () {
+ const $toolbarBtn = $(this);
return updateTextForToolbarBtn($toolbarBtn);
});
diff --git a/app/assets/javascripts/vue_shared/components/markdown/saved_replies_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/saved_replies_dropdown.vue
index 989b14f8711..b6581eed71a 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/saved_replies_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/saved_replies_dropdown.vue
@@ -1,6 +1,7 @@
<script>
import { GlCollapsibleListbox, GlIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import { updateText } from '~/lib/utils/text_markdown';
import savedRepliesQuery from './saved_replies.query.graphql';
export default {
@@ -51,6 +52,24 @@ export default {
setSavedRepliesSearch(search) {
this.savedRepliesSearch = search;
},
+ onSelect(id) {
+ const savedReply = this.savedReplies.find((r) => r.id === id);
+ const textArea = this.$el.closest('.md-area')?.querySelector('textarea');
+
+ if (savedReply && textArea) {
+ updateText({
+ textArea,
+ tag: savedReply.content,
+ cursorOffset: 0,
+ wrap: false,
+ });
+
+ // Wait for text to be added into textarea
+ requestAnimationFrame(() => {
+ textArea.focus();
+ });
+ }
+ },
},
};
</script>
@@ -65,6 +84,7 @@ export default {
:searching="$apollo.queries.savedReplies.loading"
@shown="fetchSavedReplies"
@search="setSavedRepliesSearch"
+ @select="onSelect"
>
<template #toggle>
<gl-button
@@ -74,19 +94,14 @@ export default {
category="tertiary"
class="gl-px-3!"
data-testid="saved-replies-dropdown-toggle"
+ @keydown.prevent
>
<gl-icon name="symlink" class="gl-mr-0!" />
<gl-icon name="chevron-down" />
</gl-button>
</template>
<template #list-item="{ item }">
- <div
- class="gl-display-flex js-saved-reply-content"
- :data-md-tag="item.content"
- data-md-cursor-offset="0"
- data-md-prepend="true"
- data-testid="saved-reply-dropdown-item"
- >
+ <div class="gl-display-flex js-saved-reply-content">
<div class="gl-text-truncate">
<strong>{{ item.text }}</strong
><span class="gl-ml-2">{{ item.content }}</span>
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 225c32c1989..83f51588f43 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -332,3 +332,18 @@
height: 100%;
}
}
+
+.add-review-item-modal {
+ .modal-content {
+ position: absolute;
+ top: 5%;
+ }
+
+ .title-hint-text {
+ color: $gl-text-color-secondary;
+ }
+
+ .gl-filtered-search-suggestion-list.dropdown-menu {
+ width: $gl-max-dropdown-max-height;
+ }
+}
diff --git a/app/finders/context_commits_finder.rb b/app/finders/context_commits_finder.rb
index 4a45817cc61..a186ca92c7b 100644
--- a/app/finders/context_commits_finder.rb
+++ b/app/finders/context_commits_finder.rb
@@ -21,20 +21,24 @@ class ContextCommitsFinder
attr_reader :project, :merge_request, :search, :author, :committed_before, :committed_after, :limit
def init_collection
- if search.present?
+ if search_params_present?
search_commits
else
project.repository.commits(merge_request.target_branch, { limit: limit })
end
end
+ def search_params_present?
+ [search, author, committed_before, committed_after].map(&:present?).any?
+ end
+
def filter_existing_commits(commits)
commits.select! { |commit| already_included_ids.exclude?(commit.id) }
commits
end
def search_commits
- key = search.strip
+ key = search&.strip
commits = []
if Commit.valid_hash?(key)
mr_existing_commits_ids = merge_request.commits.map(&:id)
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 71434931d8c..56c8ec4cea3 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -26,6 +26,10 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
# rather than the persisted value.
ADDRESSABLE_URL_VALIDATION_OPTIONS = { deny_all_requests_except_allowed: ->(settings) { settings.deny_all_requests_except_allowed } }.freeze
+ HUMANIZED_ATTRIBUTES = {
+ archive_builds_in_seconds: 'Archive job value'
+ }.freeze
+
enum whats_new_variant: { all_tiers: 0, current_tier: 1, disabled: 2 }, _prefix: true
enum email_confirmation_setting: { off: 0, soft: 1, hard: 2 }, _prefix: true
@@ -336,7 +340,11 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
validates :archive_builds_in_seconds,
allow_nil: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds }
+ numericality: {
+ only_integer: true,
+ greater_than_or_equal_to: 1.day.seconds,
+ message: N_('must be at least 1 day')
+ }
validates :local_markdown_version,
allow_nil: true,
@@ -873,6 +881,10 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
private
+ def self.human_attribute_name(attribute, *options)
+ HUMANIZED_ATTRIBUTES[attribute.to_sym] || super
+ end
+
def parsed_grafana_url
@parsed_grafana_url ||= Gitlab::Utils.parse_url(grafana_url)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 587b71315c2..b4a0eaf0324 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -199,7 +199,7 @@ class Repository
def list_commits_by(query, ref, author: nil, before: nil, after: nil, limit: 1000)
return [] unless exists?
return [] unless has_visible_content?
- return [] unless query.present? && ref.present?
+ return [] unless ref.present?
commits = raw_repository.list_commits_by(
query, ref, author: author, before: before, after: after, limit: limit).map do |c|
diff --git a/app/services/security/ci_configuration/base_create_service.rb b/app/services/security/ci_configuration/base_create_service.rb
index 0534925aaec..b60a949fd4e 100644
--- a/app/services/security/ci_configuration/base_create_service.rb
+++ b/app/services/security/ci_configuration/base_create_service.rb
@@ -59,7 +59,8 @@ module Security
YAML.safe_load(@gitlab_ci_yml) if @gitlab_ci_yml
rescue Psych::BadAlias
raise Gitlab::Graphql::Errors::MutationError,
- ".gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually."
+ Gitlab::Utils::ErrorMessage.to_user_facing(
+ _(".gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually."))
rescue Psych::Exception => e
Gitlab::AppLogger.error("Failed to process existing .gitlab-ci.yml: #{e.message}")
raise Gitlab::Graphql::Errors::MutationError,
diff --git a/config/feature_flags/development/use_marker_ranges.yml b/config/feature_flags/development/use_marker_ranges.yml
deleted file mode 100644
index 068e403e2cf..00000000000
--- a/config/feature_flags/development/use_marker_ranges.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: use_marker_ranges
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/56361
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/324638
-milestone: '13.10'
-type: development
-group: group::source code
-default_enabled: false
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index e276d3b25af..c113d1aeff4 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -632,6 +632,42 @@ To update this limit to a new value on a self-managed installation, run the foll
Plan.default.actual_limits.update!(ci_instance_level_variables: 30)
```
+### Number of group level variables
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/362227) in GitLab 15.7.
+
+The total number of group level CI/CD variables is limited at the instance level.
+This limit is checked each time a new group level variable is created. If a new variable
+would cause the total number of variables to exceed the limit, the new variable is not created.
+
+On self-managed instances this limit is defined for the `default` plan. By default,
+this limit is set to `30000`.
+
+To update this limit to a new value on a self-managed installation, run the following in the
+[GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session):
+
+```ruby
+Plan.default.actual_limits.update!(group_ci_variables: 40000)
+```
+
+### Number of project level variables
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/362227) in GitLab 15.7.
+
+The total number of project level CI/CD variables is limited at the instance level.
+This limit is checked each time a new project level variable is created. If a new variable
+would cause the total number of variables to exceed the limit, the new variable is not created.
+
+On self-managed instances this limit is defined for the `default` plan. By default,
+this limit is set to `8000`.
+
+To update this limit to a new value on a self-managed installation, run the following in the
+[GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session):
+
+```ruby
+Plan.default.actual_limits.update!(project_ci_variables: 10000)
+```
+
### Maximum file size per type of artifact
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37226) in GitLab 13.3.
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 225b4f7cf86..95ea3fe9f0f 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -24,15 +24,15 @@ module Gitlab
end
def highlight
- populate_marker_ranges if Feature.enabled?(:use_marker_ranges, project)
+ populate_marker_ranges
- @diff_lines.map.with_index do |diff_line, index|
+ @diff_lines.map do |diff_line|
diff_line = diff_line.dup
# ignore highlighting for "match" lines
next diff_line if diff_line.meta?
rich_line = apply_syntax_highlight(diff_line)
- rich_line = apply_marker_ranges_highlight(diff_line, rich_line, index)
+ rich_line = apply_marker_ranges_highlight(diff_line, rich_line)
diff_line.rich_text = rich_line
@@ -60,12 +60,8 @@ module Gitlab
highlight_line(diff_line) || ERB::Util.html_escape(diff_line.text)
end
- def apply_marker_ranges_highlight(diff_line, rich_line, index)
- marker_ranges = if Feature.enabled?(:use_marker_ranges, project)
- diff_line.marker_ranges
- else
- inline_diffs[index]
- end
+ def apply_marker_ranges_highlight(diff_line, rich_line)
+ marker_ranges = diff_line.marker_ranges
return rich_line if marker_ranges.blank?
@@ -134,12 +130,6 @@ module Gitlab
end
end
- # Deprecated: https://gitlab.com/gitlab-org/gitlab/-/issues/324638
- # ------------------------------------------------------------------------
- def inline_diffs
- @inline_diffs ||= InlineDiff.for_lines(@raw_lines)
- end
-
def old_lines
@old_lines ||= highlighted_blob_lines(diff_file.old_blob)
end
diff --git a/lib/gitlab/diff/highlight_cache.rb b/lib/gitlab/diff/highlight_cache.rb
index 5128b09aef4..63a437b021d 100644
--- a/lib/gitlab/diff/highlight_cache.rb
+++ b/lib/gitlab/diff/highlight_cache.rb
@@ -71,7 +71,6 @@ module Gitlab
strong_memoize(:redis_key) do
options = [
diff_options,
- Feature.enabled?(:use_marker_ranges, diffable.project),
Feature.enabled?(:diff_line_syntax_highlighting, diffable.project)
]
options_for_key = OpenSSL::Digest::SHA256.hexdigest(options.join)
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index 802da50cfc6..7f760a23f45 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -17,27 +17,6 @@ module Gitlab
CharDiff.new(old_line, new_line).changed_ranges(offset: offset)
end
-
- # Deprecated: https://gitlab.com/gitlab-org/gitlab/-/issues/324638
- class << self
- def for_lines(lines)
- pair_selector = Gitlab::Diff::PairSelector.new(lines)
-
- inline_diffs = []
-
- pair_selector.each do |old_index, new_index|
- old_line = lines[old_index]
- new_line = lines[new_index]
-
- old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
-
- inline_diffs[old_index] = old_diffs
- inline_diffs[new_index] = new_diffs
- end
-
- inline_diffs
- end
- end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ead55c0a94a..8f16eef3a4b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1478,6 +1478,9 @@ msgstr ""
msgid "."
msgstr ""
+msgid ".gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually."
+msgstr ""
+
msgid "/"
msgstr ""
@@ -8999,7 +9002,16 @@ msgstr ""
msgid "CiCatalog|CI/CD catalog"
msgstr ""
-msgid "CiCatalog|Repositories of reusable pipeline components available in this namespace."
+msgid "CiCatalog|Create a pipeline component repository and make reusing pipeline configurations faster and easier."
+msgstr ""
+
+msgid "CiCatalog|Get started with the CI/CD catalog"
+msgstr ""
+
+msgid "CiCatalog|Learn more"
+msgstr ""
+
+msgid "CiCatalog|Repositories of pipeline components available in this namespace."
msgstr ""
msgid "CiCdAnalytics|Date range: %{range}"
@@ -10562,6 +10574,12 @@ msgstr ""
msgid "Committed by"
msgstr ""
+msgid "Committed-after"
+msgstr ""
+
+msgid "Committed-before"
+msgstr ""
+
msgid "CommonJS module"
msgstr ""
@@ -35450,6 +35468,9 @@ msgstr ""
msgid "ProtectedEnvironment|Unprotect"
msgstr ""
+msgid "ProtectedEnvironment|Users with at least the Developer role can write to unprotected environments. Are you sure you want to unprotect %{environment_name}?"
+msgstr ""
+
msgid "ProtectedEnvironment|Your environment can't be unprotected"
msgstr ""
@@ -38551,9 +38572,6 @@ msgstr ""
msgid "Search by author"
msgstr ""
-msgid "Search by commit title or SHA"
-msgstr ""
-
msgid "Search by message"
msgstr ""
@@ -38599,6 +38617,9 @@ msgstr ""
msgid "Search or create tag"
msgstr ""
+msgid "Search or filter commits"
+msgstr ""
+
msgid "Search or filter results..."
msgstr ""
@@ -40615,6 +40636,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
+msgid "Show less"
+msgstr ""
+
msgid "Show list"
msgstr ""
@@ -52385,6 +52409,9 @@ msgstr ""
msgid "must be associated with a Group or a Project"
msgstr ""
+msgid "must be at least 1 day"
+msgstr ""
+
msgid "must be greater than start date"
msgstr ""
@@ -52991,6 +53018,9 @@ msgstr ""
msgid "your settings"
msgstr ""
+msgid "yyyy-mm-dd"
+msgstr ""
+
msgid "{group}"
msgstr ""
diff --git a/scripts/pipeline/create_test_failure_issues.rb b/scripts/pipeline/create_test_failure_issues.rb
index 6312d392760..efe86984fc9 100755
--- a/scripts/pipeline/create_test_failure_issues.rb
+++ b/scripts/pipeline/create_test_failure_issues.rb
@@ -24,7 +24,7 @@ class CreateTestFailureIssues
puts "[CreateTestFailureIssues] No failed tests!" if failed_tests.empty?
failed_tests.each_with_object([]) do |failed_test, existing_issues|
- CreateTestFailureIssue.new(options.dup).comment_or_create(failed_test, existing_issues).tap do |issue|
+ CreateTestFailureIssue.new(options.dup).upsert(failed_test, existing_issues).tap do |issue|
existing_issues << issue
File.write(File.join(options[:issue_json_folder], "issue-#{issue.iid}.json"), JSON.pretty_generate(issue.to_h))
end
@@ -52,14 +52,18 @@ class CreateTestFailureIssue
WWW_GITLAB_COM_GROUPS_JSON = "#{WWW_GITLAB_COM_SITE}/groups.json".freeze
WWW_GITLAB_COM_CATEGORIES_JSON = "#{WWW_GITLAB_COM_SITE}/categories.json".freeze
FEATURE_CATEGORY_METADATA_REGEX = /(?<=feature_category: :)\w+/
- DEFAULT_LABELS = ['type::maintenance', 'failure::flaky-test'].freeze
+ DEFAULT_LABELS = ['type::maintenance', 'test'].freeze
+ PROJECT_PATH = ENV.fetch('CI_PROJECT_PATH', 'gitlab-org/gitlab')
+ JOB_BASE_URL = "https://gitlab.com/#{PROJECT_PATH}/-/jobs/".freeze
+ FILE_BASE_URL = "https://gitlab.com/#{PROJECT_PATH}/-/blob/master/".freeze
+ REPORT_ITEM_REGEX = %r{^1\. \d{4}-\d{2}-\d{2}: #{JOB_BASE_URL}.+$}
def initialize(options)
@project = options.delete(:project)
@api_token = options.delete(:api_token)
end
- def comment_or_create(failed_test, existing_issues = [])
+ def upsert(failed_test, existing_issues = [])
existing_issue = find(failed_test, existing_issues)
if existing_issue
@@ -70,12 +74,16 @@ class CreateTestFailureIssue
end
end
+ private
+
+ attr_reader :project, :api_token
+
def find(failed_test, existing_issues = [])
- failed_test_issue_title = failed_test_issue_title(failed_test)
- issue_from_existing_issues = existing_issues.find { |issue| issue.title == failed_test_issue_title }
+ test_id = failed_test_id(failed_test)
+ issue_from_existing_issues = existing_issues.find { |issue| issue.title.include?(test_id) }
issue_from_issue_tracker = FindIssues
.new(project: project, api_token: api_token)
- .execute(state: 'opened', search: failed_test_issue_title)
+ .execute(state: :opened, search: test_id, in: :title, per_page: 1)
.first
existing_issue = issue_from_existing_issues || issue_from_issue_tracker
@@ -88,10 +96,24 @@ class CreateTestFailureIssue
end
def update_reports(existing_issue, failed_test)
- new_issue_description = "#{existing_issue.description}\n- #{failed_test['job_url']} (#{ENV['CI_PIPELINE_URL']})"
+ # We count the number of existing reports.
+ reports_count = existing_issue.description
+ .scan(REPORT_ITEM_REGEX)
+ .size.to_i + 1
+
+ # We include the number of reports in the header, for visibility.
+ issue_description = existing_issue.description.sub(/^### Reports.*$/, "### Reports (#{reports_count})")
+
+ # We add the current failure to the list of reports.
+ issue_description = "#{issue_description}\n#{report_list_item(failed_test)}"
+
UpdateIssue
.new(project: project, api_token: api_token)
- .execute(existing_issue.iid, description: new_issue_description)
+ .execute(
+ existing_issue.iid,
+ description: issue_description,
+ weight: reports_count
+ )
puts "[CreateTestFailureIssue] Added a report in '#{existing_issue.title}': #{existing_issue.web_url}!"
end
@@ -99,7 +121,8 @@ class CreateTestFailureIssue
payload = {
title: failed_test_issue_title(failed_test),
description: failed_test_issue_description(failed_test),
- labels: failed_test_issue_labels(failed_test)
+ labels: failed_test_issue_labels(failed_test),
+ weight: 1
}
CreateIssue.new(project: project, api_token: api_token).execute(payload).tap do |issue|
@@ -107,36 +130,40 @@ class CreateTestFailureIssue
end
end
- private
-
- attr_reader :project, :api_token
-
def failed_test_id(failed_test)
- Digest::SHA256.hexdigest(search_safe(failed_test['name']))[0...12]
+ Digest::SHA256.hexdigest(failed_test['file'] + failed_test['name'])[0...12]
end
def failed_test_issue_title(failed_test)
- title = "#{failed_test['file']} - ID: #{failed_test_id(failed_test)}"
+ title = "#{failed_test['file']} [test-hash:#{failed_test_id(failed_test)}]"
raise "Title is too long!" if title.size > MAX_TITLE_LENGTH
title
end
+ def test_file_link(failed_test)
+ "[`#{failed_test['file']}`](#{FILE_BASE_URL}#{failed_test['file']})"
+ end
+
+ def report_list_item(failed_test)
+ "1. #{Time.new.utc.strftime('%F')}: #{failed_test['job_url']} (#{ENV['CI_PIPELINE_URL']})"
+ end
+
def failed_test_issue_description(failed_test)
<<~DESCRIPTION
- ### Full description
+ ### Test description
`#{search_safe(failed_test['name'])}`
- ### File path
+ ### Test file path
- `#{failed_test['file']}`
+ #{test_file_link(failed_test)}
<!-- Don't add anything after the report list since it's updated automatically -->
- ### Reports
+ ### Reports (1)
- - #{failed_test['job_url']} (#{ENV['CI_PIPELINE_URL']})
+ #{report_list_item(failed_test)}
DESCRIPTION
end
diff --git a/spec/features/profiles/user_uses_saved_reply_spec.rb b/spec/features/profiles/user_uses_saved_reply_spec.rb
index f9a4f4a7fa6..4954a8ce67c 100644
--- a/spec/features/profiles/user_uses_saved_reply_spec.rb
+++ b/spec/features/profiles/user_uses_saved_reply_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe 'User uses saved reply', :js,
wait_for_requests
- find('[data-testid="saved-reply-dropdown-item"]').click
+ find('.gl-new-dropdown-item').click
expect(find('.note-textarea').value).to eq(saved_reply.content)
end
diff --git a/spec/finders/context_commits_finder_spec.rb b/spec/finders/context_commits_finder_spec.rb
index c22675bc67d..3de1d29b695 100644
--- a/spec/finders/context_commits_finder_spec.rb
+++ b/spec/finders/context_commits_finder_spec.rb
@@ -26,27 +26,30 @@ RSpec.describe ContextCommitsFinder do
end
it 'returns commits based in author filter' do
- params = { search: 'test text', author: 'Job van der Voort' }
+ params = { author: 'Job van der Voort' }
commits = described_class.new(project, merge_request, params).execute
expect(commits.length).to eq(1)
expect(commits[0].id).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0')
end
- it 'returns commits based in before filter' do
- params = { search: 'test text', committed_before: 1474828200 }
+ it 'returns commits based in committed before and after filter' do
+ params = { committed_before: 1471631400, committed_after: 1471458600 } # August 18, 2016 - # August 20, 2016
commits = described_class.new(project, merge_request, params).execute
- expect(commits.length).to eq(1)
- expect(commits[0].id).to eq('498214de67004b1da3d820901307bed2a68a8ef6')
+ expect(commits.length).to eq(2)
+ expect(commits[0].id).to eq('1b12f15a11fc6e62177bef08f47bc7b5ce50b141')
+ expect(commits[1].id).to eq('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e')
end
- it 'returns commits based in after filter' do
- params = { search: 'test text', committed_after: 1474828200 }
- commits = described_class.new(project, merge_request, params).execute
+ it 'returns commits from target branch if no filter is applied' do
+ expect(project.repository).to receive(:commits).with(merge_request.target_branch, anything).and_call_original
- expect(commits.length).to eq(1)
+ commits = described_class.new(project, merge_request).execute
+
+ expect(commits.length).to eq(37)
expect(commits[0].id).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0')
+ expect(commits[1].id).to eq('498214de67004b1da3d820901307bed2a68a8ef6')
end
end
end
diff --git a/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
index 2c2151bfb41..e379aba094c 100644
--- a/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
+++ b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
@@ -6,8 +6,9 @@ exports[`AddContextCommitsModal renders modal with 2 tabs 1`] = `
body-class="add-review-item pt-0"
cancel-variant="light"
dismisslabel="Close"
- modalclass=""
+ modalclass="add-review-item-modal"
modalid="add-review-item"
+ nofocusonshow="true"
ok-disabled="true"
ok-title="Save changes"
scrollable="true"
@@ -27,9 +28,13 @@ exports[`AddContextCommitsModal renders modal with 2 tabs 1`] = `
<div
class="gl-mt-3"
>
- <gl-search-box-by-type-stub
+ <gl-filtered-search-stub
+ availabletokens="[object Object],[object Object],[object Object]"
+ class="flex-grow-1"
clearbuttontitle="Clear"
- placeholder="Search by commit title or SHA"
+ placeholder="Search or filter commits"
+ searchbuttonattributes="[object Object]"
+ searchinputattributes="[object Object]"
value=""
/>
diff --git a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
index 5e96da9af7e..27fe010c354 100644
--- a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
+++ b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
@@ -1,4 +1,4 @@
-import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
+import { GlModal, GlFilteredSearch } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
@@ -49,7 +49,7 @@ describe('AddContextCommitsModal', () => {
};
const findModal = () => wrapper.findComponent(GlModal);
- const findSearch = () => wrapper.findComponent(GlSearchBoxByType);
+ const findSearch = () => wrapper.findComponent(GlFilteredSearch);
beforeEach(() => {
wrapper = createWrapper();
@@ -68,12 +68,29 @@ describe('AddContextCommitsModal', () => {
expect(findSearch().exists()).toBe(true);
});
- it('when user starts entering text in search box, it calls action "searchCommits" after waiting for 500s', () => {
- const searchText = 'abcd';
- findSearch().vm.$emit('input', searchText);
- expect(searchCommits).not.toHaveBeenCalled();
- jest.advanceTimersByTime(500);
- expect(searchCommits).toHaveBeenCalledWith(expect.anything(), searchText);
+ it('when user submits after entering filters in search box, then it calls action "searchCommits"', () => {
+ const search = [
+ 'abcd',
+ {
+ type: 'author',
+ value: { operator: '=', data: 'abhi' },
+ },
+ {
+ type: 'committed-before-date',
+ value: { operator: '=', data: '2022-10-31' },
+ },
+ {
+ type: 'committed-after-date',
+ value: { operator: '=', data: '2022-10-28' },
+ },
+ ];
+ findSearch().vm.$emit('submit', search);
+ expect(searchCommits).toHaveBeenCalledWith(expect.anything(), {
+ searchText: 'abcd',
+ author: 'abhi',
+ committed_before: '2022-10-31',
+ committed_after: '2022-10-28',
+ });
});
it('disabled ok button when no row is selected', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js
index 8ad9ad30c1d..1b1c30f7739 100644
--- a/spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js
@@ -4,9 +4,13 @@ import savedRepliesResponse from 'test_fixtures/graphql/saved_replies/saved_repl
import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { updateText } from '~/lib/utils/text_markdown';
import SavedRepliesDropdown from '~/vue_shared/components/markdown/saved_replies_dropdown.vue';
import savedRepliesQuery from '~/vue_shared/components/markdown/saved_replies.query.graphql';
+jest.mock('~/lib/utils/text_markdown');
+
let wrapper;
let savedRepliesResp;
@@ -24,6 +28,7 @@ function createComponent(options = {}) {
const { mockApollo } = options;
return mountExtended(SavedRepliesDropdown, {
+ attachTo: '#root',
propsData: {
newSavedRepliesPath: '/new',
},
@@ -32,6 +37,14 @@ function createComponent(options = {}) {
}
describe('Saved replies dropdown', () => {
+ beforeEach(() => {
+ setHTMLFixture('<div class="md-area"><textarea></textarea><div id="root"></div></div>');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('fetches data when dropdown gets opened', async () => {
const mockApollo = createMockApolloProvider(savedRepliesResponse);
wrapper = createComponent({ mockApollo });
@@ -43,7 +56,7 @@ describe('Saved replies dropdown', () => {
expect(savedRepliesResp).toHaveBeenCalled();
});
- it('adds markdown toolbar attributes to dropdown items', async () => {
+ it('adds content to textarea', async () => {
const mockApollo = createMockApolloProvider(savedRepliesResponse);
wrapper = createComponent({ mockApollo });
@@ -51,12 +64,13 @@ describe('Saved replies dropdown', () => {
await waitForPromises();
- expect(wrapper.findByTestId('saved-reply-dropdown-item').attributes()).toEqual(
- expect.objectContaining({
- 'data-md-cursor-offset': '0',
- 'data-md-prepend': 'true',
- 'data-md-tag': 'Saved Reply Content',
- }),
- );
+ wrapper.find('.gl-new-dropdown-item').trigger('click');
+
+ expect(updateText).toHaveBeenCalledWith({
+ textArea: document.querySelector('textarea'),
+ tag: savedRepliesResponse.data.currentUser.savedReplies.nodes[0].content,
+ cursorOffset: 0,
+ wrap: false,
+ });
});
});
diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb
index 33e9360ee01..43e4f28b4df 100644
--- a/spec/lib/gitlab/diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache, feature_category: :source_code_management do
let_it_be(:merge_request) { create(:merge_request_with_diffs) }
let(:diff_hash) do
@@ -282,17 +282,7 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
end
it 'returns cache key' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, true, true])}")
- end
-
- context 'when the `use_marker_ranges` feature flag is disabled' do
- before do
- stub_feature_flags(use_marker_ranges: false)
- end
-
- it 'returns the original version of the cache' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, false, true])}")
- end
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, true])}")
end
context 'when the `diff_line_syntax_highlighting` feature flag is disabled' do
@@ -301,7 +291,7 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
end
it 'returns the original version of the cache' do
- is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, true, false])}")
+ is_expected.to eq("highlighted-diff-files:#{cache.diffable.cache_key}:2:#{options_hash([cache.diff_options, false])}")
end
end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index c378ecb8134..233dddbdad7 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Diff::Highlight do
+RSpec.describe Gitlab::Diff::Highlight, feature_category: :source_code_management do
include RepoHelpers
let_it_be(:project) { create(:project, :repository) }
@@ -15,7 +15,6 @@ RSpec.describe Gitlab::Diff::Highlight do
let(:code) { '<h2 onmouseover="alert(2)">Test</h2>' }
before do
- allow(Gitlab::Diff::InlineDiff).to receive(:for_lines).and_return([])
allow_any_instance_of(Gitlab::Diff::Line).to receive(:text).and_return(code)
end
@@ -121,18 +120,6 @@ RSpec.describe Gitlab::Diff::Highlight do
end
end
- context 'when `use_marker_ranges` feature flag is disabled' do
- it 'returns the same result' do
- with_feature_flag = described_class.new(diff_file, repository: project.repository).highlight
-
- stub_feature_flags(use_marker_ranges: false)
-
- without_feature_flag = described_class.new(diff_file, repository: project.repository).highlight
-
- expect(with_feature_flag.map(&:rich_text)).to eq(without_feature_flag.map(&:rich_text))
- end
- end
-
context 'when no inline diffs' do
it_behaves_like 'without inline diffs'
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 8387f4021b6..72e900125c6 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -534,6 +534,13 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
.is_less_than(65536)
end
+ specify do
+ is_expected.to validate_numericality_of(:archive_builds_in_seconds)
+ .only_integer
+ .is_greater_than_or_equal_to(1.day.seconds.to_i)
+ .with_message('must be at least 1 day')
+ end
+
describe 'usage_ping_enabled setting' do
shared_examples 'usage ping enabled' do
it do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index f970e818db9..72011693e20 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -597,6 +597,15 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
describe '#list_commits_by' do
+ it 'returns commits when no filter is applied' do
+ commit_ids = repository.list_commits_by(nil, 'master', limit: 2).map(&:id)
+
+ expect(commit_ids).to include(
+ 'b83d6e391c22777fca1ed3012fce84f633d7fed0',
+ '498214de67004b1da3d820901307bed2a68a8ef6'
+ )
+ end
+
it 'returns commits with messages containing a given string' do
commit_ids = repository.list_commits_by('test text', 'master').map(&:id)
diff --git a/spec/scripts/pipeline/create_test_failure_issues_spec.rb b/spec/scripts/pipeline/create_test_failure_issues_spec.rb
index fa27727542e..e8849c32255 100644
--- a/spec/scripts/pipeline/create_test_failure_issues_spec.rb
+++ b/spec/scripts/pipeline/create_test_failure_issues_spec.rb
@@ -3,16 +3,19 @@
# rubocop:disable RSpec/VerifiedDoubles
require 'fast_spec_helper'
+require 'active_support/testing/time_helpers'
require 'rspec-parameterized'
require_relative '../../../scripts/pipeline/create_test_failure_issues'
RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
describe CreateTestFailureIssue do
+ include ActiveSupport::Testing::TimeHelpers
+
let(:env) do
{
- 'CI_JOB_URL' => 'ci_job_url',
- 'CI_PIPELINE_URL' => 'ci_pipeline_url'
+ 'CI_JOB_URL' => 'https://gitlab.com/gitlab-org/gitlab/-/jobs/1234',
+ 'CI_PIPELINE_URL' => 'https://gitlab.com/gitlab-org/gitlab/-/pipelines/5678'
}
end
@@ -36,7 +39,7 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
{
'name' => test_name,
'file' => test_file,
- 'job_url' => 'job_url'
+ 'job_url' => env['CI_JOB_URL']
}
end
@@ -57,87 +60,143 @@ RSpec.describe CreateTestFailureIssues, feature_category: :tooling do
}
end
+ let(:test_id) { Digest::SHA256.hexdigest(failed_test['file'] + failed_test['name'])[0...12] }
+ let(:latest_format_issue_title) { "#{failed_test['file']} [test-hash:#{test_id}]" }
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
before do
stub_env(env)
+ allow(creator).to receive(:puts)
end
- describe '#find' do
- let(:expected_payload) do
+ describe '#upsert' do
+ let(:expected_search_payload) do
{
- state: 'opened',
- search: "#{failed_test['file']} - ID: #{Digest::SHA256.hexdigest(failed_test['name'])[0...12]}"
+ state: :opened,
+ search: test_id,
+ in: :title,
+ per_page: 1
}
end
let(:find_issue_stub) { double('FindIssues') }
- let(:issue_stub) { double(title: expected_payload[:title], web_url: 'issue_web_url') }
+ let(:issue_stub) { double('Issue', title: latest_format_issue_title, web_url: 'issue_web_url') }
before do
- allow(creator).to receive(:puts)
+ allow(File).to receive(:open).and_call_original
+ allow(File).to receive(:open).with(File.expand_path(File.join('..', '..', '..', test_file), __dir__))
+ .and_return(test_file_stub)
+ allow(creator).to receive(:categories_mapping).and_return(categories_mapping)
+ allow(creator).to receive(:groups_mapping).and_return(groups_mapping)
end
- it 'calls FindIssues#execute(payload)' do
- expect(FindIssues).to receive(:new).with(project: project, api_token: api_token).and_return(find_issue_stub)
- expect(find_issue_stub).to receive(:execute).with(expected_payload).and_return([issue_stub])
+ context 'when no issues are found' do
+ let(:expected_description) do
+ <<~DESCRIPTION
+ ### Test description
- creator.find(failed_test)
- end
+ `#{failed_test['name']}`
- context 'when no issues are found' do
- it 'calls FindIssues#execute(payload)' do
- expect(FindIssues).to receive(:new).with(project: project, api_token: api_token).and_return(find_issue_stub)
- expect(find_issue_stub).to receive(:execute).with(expected_payload).and_return([])
+ ### Test file path
+
+ [`#{failed_test['file']}`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/#{failed_test['file']})
+
+ <!-- Don't add anything after the report list since it's updated automatically -->
+ ### Reports (1)
- creator.find(failed_test)
+ 1. #{Time.new.utc.strftime('%F')}: #{failed_test['job_url']} (#{env['CI_PIPELINE_URL']})
+ DESCRIPTION
+ end
+
+ let(:create_issue_stub) { double('CreateIssue') }
+
+ let(:expected_create_payload) do
+ {
+ title: latest_format_issue_title,
+ description: expected_description,
+ labels: described_class::DEFAULT_LABELS.map { |label| "wip-#{label}" } + [
+ "wip-#{categories_mapping['source_code_management']['label']}",
+ "wip-#{groups_mapping['source_code']['label']}"
+ ],
+ weight: 1
+ }
+ end
+
+ before do
+ allow(FindIssues).to receive(:new).with(project: project, api_token: api_token).and_return(find_issue_stub)
+ allow(find_issue_stub).to receive(:execute).with(expected_search_payload).and_return([])
+ end
+
+ it 'calls CreateIssue#execute(payload)' do
+ expect(CreateIssue).to receive(:new).with(project: project, api_token: api_token)
+ .and_return(create_issue_stub)
+ expect(create_issue_stub).to receive(:execute).with(expected_create_payload).and_return(issue_stub)
+
+ creator.upsert(failed_test)
end
end
- end
- describe '#create' do
- let(:expected_description) do
- <<~DESCRIPTION
- ### Full description
+ context 'when issues are found' do
+ let(:failed_test_report_line) do
+ "1. #{Time.new.utc.strftime('%F')}: #{failed_test['job_url']} (#{env['CI_PIPELINE_URL']})"
+ end
- `#{failed_test['name']}`
+ let(:latest_format_issue_description) do
+ <<~DESCRIPTION
+ ### Test description
- ### File path
+ `#{failed_test['name']}`
- `#{failed_test['file']}`
+ ### Test file path
- <!-- Don't add anything after the report list since it's updated automatically -->
- ### Reports
+ [`#{failed_test['file']}`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/#{failed_test['file']})
- - #{failed_test['job_url']} (#{env['CI_PIPELINE_URL']})
- DESCRIPTION
- end
+ <!-- Don't add anything after the report list since it's updated automatically -->
+ ### Reports (1)
- let(:expected_payload) do
- {
- title: "#{failed_test['file']} - ID: #{Digest::SHA256.hexdigest(failed_test['name'])[0...12]}",
- description: expected_description,
- labels: described_class::DEFAULT_LABELS.map { |label| "wip-#{label}" } + [
- "wip-#{categories_mapping['source_code_management']['label']}", "wip-#{groups_mapping['source_code']['label']}" # rubocop:disable Layout/LineLength
- ]
- }
- end
+ #{failed_test_report_line}
+ DESCRIPTION
+ end
- let(:create_issue_stub) { double('CreateIssue') }
- let(:issue_stub) { double(title: expected_payload[:title], web_url: 'issue_web_url') }
+ let(:issue_stub) do
+ double('Issue', iid: 42, title: issue_title, description: issue_description, web_url: 'issue_web_url')
+ end
- before do
- allow(creator).to receive(:puts)
- allow(File).to receive(:open).and_call_original
- allow(File).to receive(:open).with(File.expand_path(File.join('..', '..', '..', test_file), __dir__))
- .and_return(test_file_stub)
- allow(creator).to receive(:categories_mapping).and_return(categories_mapping)
- allow(creator).to receive(:groups_mapping).and_return(groups_mapping)
- end
+ let(:update_issue_stub) { double('UpdateIssue') }
- it 'calls CreateIssue#execute(payload)' do
- expect(CreateIssue).to receive(:new).with(project: project, api_token: api_token).and_return(create_issue_stub)
- expect(create_issue_stub).to receive(:execute).with(expected_payload).and_return(issue_stub)
+ let(:expected_update_payload) do
+ {
+ description: latest_format_issue_description.sub(/^### Reports.*$/, '### Reports (2)') +
+ "\n#{failed_test_report_line}",
+ weight: 2
+ }
+ end
+
+ before do
+ allow(FindIssues).to receive(:new).with(project: project, api_token: api_token).and_return(find_issue_stub)
+ allow(find_issue_stub).to receive(:execute).with(expected_search_payload).and_return([issue_stub])
+ end
- creator.create(failed_test) # rubocop:disable Rails/SaveBang
+ # This shared example can be useful if we want to test migration to a new format in the future
+ shared_examples 'existing issue update' do
+ it 'calls UpdateIssue#execute(payload)' do
+ expect(UpdateIssue).to receive(:new).with(project: project, api_token: api_token)
+ .and_return(update_issue_stub)
+ expect(update_issue_stub).to receive(:execute).with(42, **expected_update_payload).and_return(issue_stub)
+
+ creator.upsert(failed_test)
+ end
+ end
+
+ context 'when issue already has the latest format' do
+ let(:issue_description) { latest_format_issue_description }
+ let(:issue_title) { latest_format_issue_title }
+
+ it_behaves_like 'existing issue update'
+ end
end
end
end
diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
index 9fcdd296ebe..094c91f2ab5 100644
--- a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb
@@ -114,7 +114,8 @@ RSpec.shared_examples_for 'services security ci configuration create service' do
it 'fails with error' do
expect(project).to receive(:ci_config_for).and_return(unsupported_yaml)
- expect { result }.to raise_error(Gitlab::Graphql::Errors::MutationError, '.gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually.')
+ expect { result }.to raise_error(Gitlab::Graphql::Errors::MutationError, Gitlab::Utils::ErrorMessage.to_user_facing(
+ _(".gitlab-ci.yml with aliases/anchors is not supported. Please change the CI configuration manually.")))
end
end
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 6403e68b4da..25107ca4338 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -7,7 +7,7 @@ require (
github.com/BurntSushi/toml v1.2.1
github.com/FZambia/sentinel v1.1.1
github.com/alecthomas/chroma/v2 v2.6.0
- github.com/aws/aws-sdk-go v1.44.218
+ github.com/aws/aws-sdk-go v1.44.224
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.2.0
github.com/golang-jwt/jwt/v4 v4.5.0
diff --git a/workhorse/go.sum b/workhorse/go.sum
index 8b87771139c..ec9949436a3 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -569,8 +569,8 @@ github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4
github.com/aws/aws-sdk-go v1.44.156/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.187/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.200/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
-github.com/aws/aws-sdk-go v1.44.218 h1:p707+xOCazWhkSpZOeyhtTcg7Z+asxxvueGgYPSitn4=
-github.com/aws/aws-sdk-go v1.44.218/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go v1.44.224 h1:09CiaaF35nRmxrzWZ2uRq5v6Ghg/d2RiPjZnSgtt+RQ=
+github.com/aws/aws-sdk-go v1.44.224/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY=
github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=