Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-22 09:10:46 +00:00
parent c86c0e0146
commit 2e9f877e8b
63 changed files with 401 additions and 711 deletions

View File

@ -18,6 +18,7 @@ inherit_from:
inherit_mode:
merge:
- Include
- Exclude
AllCops:
TargetRubyVersion: 2.7
@ -597,3 +598,14 @@ FactoryBot/InlineAssociation:
Include:
- 'spec/factories/**/*.rb'
- 'ee/spec/factories/**/*.rb'
# WIP: https://gitlab.com/gitlab-org/gitlab/-/issues/321982
Gitlab/NamespacedClass:
Exclude:
- 'config/**/*.rb'
- 'db/**/*.rb'
- 'ee/bin/**/*'
- 'ee/db/**/*.rb'
- 'ee/elastic/**/*.rb'
- 'scripts/**/*'
- 'spec/migrations/**/*.rb'

View File

@ -10,6 +10,7 @@
# - guidelines for use found in
# https://docs.gitlab.com/ee/development/contributing/style_guides.html#resolving-rubocop-exceptions.
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/267606
FactoryBot/InlineAssociation:
Exclude:
- 'ee/spec/factories/analytics/cycle_analytics/group_stages.rb'
@ -28,6 +29,7 @@ FactoryBot/InlineAssociation:
- 'spec/factories/uploads.rb'
- 'spec/factories/wiki_pages.rb'
# WIP: See https://gitlab.com/gitlab-org/gitlab/-/issues/220040
Rails/SaveBang:
Exclude:
- 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
@ -1174,22 +1176,9 @@ RSpec/AnyInstanceOf:
- 'spec/workers/wait_for_cluster_creation_worker_spec.rb'
- 'ee/spec/workers/security/auto_fix_worker_spec.rb'
# WIP: https://gitlab.com/gitlab-org/gitlab/-/issues/321982
Gitlab/NamespacedClass:
Exclude:
- 'config/**/*.rb'
- 'db/**/*.rb'
- 'ee/bin/**/*'
- 'ee/db/**/*.rb'
- 'ee/elastic/**/*.rb'
- 'scripts/**/*'
- 'spec/migrations/**/*.rb'
# The list above represents the permanent exclusions for this rule
# due to the fact these files are related to infrastructure code.
# This list should eventually be moved to .rubocop.yml after all TODOs
# are addressed.
#
# The list below represents the classes that require
# a namespace as they make the domain related code.
- 'app/channels/issues_channel.rb'
- 'app/controllers/abuse_reports_controller.rb'
- 'app/controllers/acme_challenges_controller.rb'

View File

@ -36,11 +36,11 @@ export function formatIssue(issue) {
}
export function formatListIssues(listIssues) {
const issues = {};
let listIssuesCount;
const boardItems = {};
let listItemsCount;
const listData = listIssues.nodes.reduce((map, list) => {
listIssuesCount = list.issues.count;
listItemsCount = list.issues.count;
let sortedIssues = list.issues.edges.map((issueNode) => ({
...issueNode.node,
}));
@ -58,14 +58,14 @@ export function formatListIssues(listIssues) {
assignees: i.assignees?.nodes || [],
};
issues[id] = listIssue;
boardItems[id] = listIssue;
return id;
}),
};
}, {});
return { listData, issues, listIssuesCount };
return { listData, boardItems, listItemsCount };
}
export function formatListsPageInfo(lists) {

View File

@ -32,12 +32,12 @@ export default {
},
computed: {
...mapState(['filterParams', 'highlightedLists']),
...mapGetters(['getIssuesByList']),
...mapGetters(['getBoardItemsByList']),
highlighted() {
return this.highlightedLists.includes(this.list.id);
},
listIssues() {
return this.getIssuesByList(this.list.id);
listItems() {
return this.getBoardItemsByList(this.list.id);
},
isListDraggable() {
return isListDraggable(this.list);
@ -87,7 +87,7 @@ export default {
<board-list
ref="board-list"
:disabled="disabled"
:issues="listIssues"
:board-items="listItems"
:list="list"
:can-admin-list="canAdminList"
/>

View File

@ -12,8 +12,8 @@ import BoardNewIssue from './board_new_issue.vue';
export default {
name: 'BoardList',
i18n: {
loadingIssues: __('Loading issues'),
loadingMoreissues: __('Loading more issues'),
loading: __('Loading'),
loadingMoreboardItems: __('Loading more'),
showingAllIssues: __('Showing all issues'),
},
components: {
@ -30,7 +30,7 @@ export default {
type: Object,
required: true,
},
issues: {
boardItems: {
type: Array,
required: true,
},
@ -51,11 +51,11 @@ export default {
...mapState(['pageInfoByListId', 'listsFlags']),
paginatedIssueText() {
return sprintf(__('Showing %{pageSize} of %{total} issues'), {
pageSize: this.issues.length,
pageSize: this.boardItems.length,
total: this.list.issuesCount,
});
},
issuesSizeExceedsMax() {
boardItemsSizeExceedsMax() {
return this.list.maxIssueCount > 0 && this.list.issuesCount > this.list.maxIssueCount;
},
hasNextPage() {
@ -72,7 +72,7 @@ export default {
return this.canAdminList ? this.$refs.list.$el : this.$refs.list;
},
showingAllIssues() {
return this.issues.length === this.list.issuesCount;
return this.boardItems.length === this.list.issuesCount;
},
treeRootWrapper() {
return this.canAdminList ? Draggable : 'ul';
@ -85,14 +85,14 @@ export default {
tag: 'ul',
'ghost-class': 'board-card-drag-active',
'data-list-id': this.list.id,
value: this.issues,
value: this.boardItems,
};
return this.canAdminList ? options : {};
},
},
watch: {
issues() {
boardItems() {
this.$nextTick(() => {
this.showCount = this.scrollHeight() > Math.ceil(this.listHeight());
});
@ -201,7 +201,7 @@ export default {
<div
v-if="loading"
class="gl-mt-4 gl-text-center"
:aria-label="$options.i18n.loadingIssues"
:aria-label="$options.i18n.loading"
data-testid="board_list_loading"
>
<gl-loading-icon />
@ -214,25 +214,25 @@ export default {
v-bind="treeRootOptions"
:data-board="list.id"
:data-board-type="list.listType"
:class="{ 'bg-danger-100': issuesSizeExceedsMax }"
:class="{ 'bg-danger-100': boardItemsSizeExceedsMax }"
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
data-testid="tree-root-wrapper"
@start="handleDragOnStart"
@end="handleDragOnEnd"
>
<board-card
v-for="(issue, index) in issues"
v-for="(item, index) in boardItems"
ref="issue"
:key="issue.id"
:key="item.id"
:index="index"
:list="list"
:issue="issue"
:issue="item"
:disabled="disabled"
/>
<li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1">
<gl-loading-icon
v-if="loadingMore"
:label="$options.i18n.loadingMoreissues"
:label="$options.i18n.loadingMoreboardItems"
data-testid="count-loading-icon"
/>
<span v-if="showingAllIssues">{{ $options.i18n.showingAllIssues }}</span>

View File

@ -289,9 +289,9 @@ export default {
})
.then(({ data }) => {
const { lists } = data[boardType]?.board;
const listIssues = formatListIssues(lists);
const listItems = formatListIssues(lists);
const listPageInfo = formatListsPageInfo(lists);
commit(types.RECEIVE_ITEMS_FOR_LIST_SUCCESS, { listIssues, listPageInfo, listId });
commit(types.RECEIVE_ITEMS_FOR_LIST_SUCCESS, { listItems, listPageInfo, listId });
})
.catch(() => commit(types.RECEIVE_ITEMS_FOR_LIST_FAILURE, listId));
},
@ -304,8 +304,8 @@ export default {
{ state, commit },
{ issueId, issueIid, issuePath, fromListId, toListId, moveBeforeId, moveAfterId },
) => {
const originalIssue = state.issues[issueId];
const fromList = state.issuesByListId[fromListId];
const originalIssue = state.boardItems[issueId];
const fromList = state.boardItemsByListId[fromListId];
const originalIndex = fromList.indexOf(Number(issueId));
commit(types.MOVE_ISSUE, { originalIssue, fromListId, toListId, moveBeforeId, moveAfterId });

View File

@ -4,17 +4,17 @@ import { inactiveId } from '../constants';
export default {
isSidebarOpen: (state) => state.activeId !== inactiveId,
isSwimlanesOn: () => false,
getIssueById: (state) => (id) => {
return state.issues[id] || {};
getBoardItemById: (state) => (id) => {
return state.boardItems[id] || {};
},
getIssuesByList: (state, getters) => (listId) => {
const listIssueIds = state.issuesByListId[listId] || [];
return listIssueIds.map((id) => getters.getIssueById(id));
getBoardItemsByList: (state, getters) => (listId) => {
const listItemsIds = state.boardItemsByListId[listId] || [];
return listItemsIds.map((id) => getters.getBoardItemById(id));
},
activeIssue: (state) => {
return state.issues[state.activeId] || {};
return state.boardItems[state.activeId] || {};
},
groupPathForActiveIssue: (_, getters) => {

View File

@ -11,13 +11,13 @@ const notImplemented = () => {
};
export const removeIssueFromList = ({ state, listId, issueId }) => {
Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
Vue.set(state.boardItemsByListId, listId, pull(state.boardItemsByListId[listId], issueId));
const list = state.boardLists[listId];
Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount - 1 });
};
export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => {
const listIssues = state.issuesByListId[listId];
const listIssues = state.boardItemsByListId[listId];
let newIndex = atIndex || 0;
if (moveBeforeId) {
newIndex = listIssues.indexOf(moveBeforeId) + 1;
@ -25,7 +25,7 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter
newIndex = listIssues.indexOf(moveAfterId);
}
listIssues.splice(newIndex, 0, issueId);
Vue.set(state.issuesByListId, listId, listIssues);
Vue.set(state.boardItemsByListId, listId, listIssues);
const list = state.boardLists[listId];
Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount + 1 });
};
@ -108,14 +108,13 @@ export default {
Vue.set(state.listsFlags, listId, { [fetchNext ? 'isLoadingMore' : 'isLoading']: true });
},
[mutationTypes.RECEIVE_ITEMS_FOR_LIST_SUCCESS]: (state, { listIssues, listPageInfo, listId }) => {
const { listData, issues } = listIssues;
Vue.set(state, 'issues', { ...state.issues, ...issues });
[mutationTypes.RECEIVE_ITEMS_FOR_LIST_SUCCESS]: (state, { listItems, listPageInfo, listId }) => {
const { listData, boardItems } = listItems;
Vue.set(state, 'boardItems', { ...state.boardItems, ...boardItems });
Vue.set(
state.issuesByListId,
state.boardItemsByListId,
listId,
union(state.issuesByListId[listId] || [], listData[listId]),
union(state.boardItemsByListId[listId] || [], listData[listId]),
);
Vue.set(state.pageInfoByListId, listId, listPageInfo[listId]);
Vue.set(state.listsFlags, listId, { isLoading: false, isLoadingMore: false });
@ -129,18 +128,18 @@ export default {
},
[mutationTypes.RESET_ISSUES]: (state) => {
Object.keys(state.issuesByListId).forEach((listId) => {
Vue.set(state.issuesByListId, listId, []);
Object.keys(state.boardItemsByListId).forEach((listId) => {
Vue.set(state.boardItemsByListId, listId, []);
});
},
[mutationTypes.UPDATE_ISSUE_BY_ID]: (state, { issueId, prop, value }) => {
if (!state.issues[issueId]) {
if (!state.boardItems[issueId]) {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('No issue found.');
}
Vue.set(state.issues[issueId], prop, value);
Vue.set(state.boardItems[issueId], prop, value);
},
[mutationTypes.SET_ASSIGNEE_LOADING](state, isLoading) {
@ -167,7 +166,7 @@ export default {
const toList = state.boardLists[toListId];
const issue = moveIssueListHelper(originalIssue, fromList, toList);
Vue.set(state.issues, issue.id, issue);
Vue.set(state.boardItems, issue.id, issue);
removeIssueFromList({ state, listId: fromListId, issueId: issue.id });
addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId });
@ -175,7 +174,7 @@ export default {
[mutationTypes.MOVE_ISSUE_SUCCESS]: (state, { issue }) => {
const issueId = getIdFromGraphQLId(issue.id);
Vue.set(state.issues, issueId, formatIssue({ ...issue, id: issueId }));
Vue.set(state.boardItems, issueId, formatIssue({ ...issue, id: issueId }));
},
[mutationTypes.MOVE_ISSUE_FAILURE]: (
@ -183,7 +182,7 @@ export default {
{ originalIssue, fromListId, toListId, originalIndex },
) => {
state.error = s__('Boards|An error occurred while moving the issue. Please try again.');
Vue.set(state.issues, originalIssue.id, originalIssue);
Vue.set(state.boardItems, originalIssue.id, originalIssue);
removeIssueFromList({ state, listId: toListId, issueId: originalIssue.id });
addIssueToList({
state,
@ -216,7 +215,7 @@ export default {
issueId: issue.id,
atIndex: position,
});
Vue.set(state.issues, issue.id, issue);
Vue.set(state.boardItems, issue.id, issue);
},
[mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issueId }) => {
@ -226,7 +225,7 @@ export default {
[mutationTypes.REMOVE_ISSUE_FROM_LIST]: (state, { list, issue }) => {
removeIssueFromList({ state, listId: list.id, issueId: issue.id });
Vue.delete(state.issues, issue.id);
Vue.delete(state.boardItems, issue.id);
},
[mutationTypes.SET_CURRENT_PAGE]: () => {

View File

@ -9,10 +9,10 @@ export default () => ({
sidebarType: '',
boardLists: {},
listsFlags: {},
issuesByListId: {},
boardItemsByListId: {},
isSettingAssignees: false,
pageInfoByListId: {},
issues: {},
boardItems: {},
filterParams: {},
boardConfig: {},
labels: [],

View File

@ -443,7 +443,7 @@ $gl-avatar-size: 40px;
$border-radius-default: 4px;
$border-radius-small: 2px;
$border-radius-large: 8px;
$default-icon-size: 18px;
$default-icon-size: 16px;
$layout-link-gray: #7e7c7c;
$btn-side-margin: 10px;
$btn-sm-side-margin: 7px;

View File

@ -32,11 +32,6 @@
@include media-breakpoint-down(md) {
width: 100%;
}
img {
width: $default-icon-size;
height: $default-icon-size;
}
}
.decline-page {

View File

@ -106,17 +106,6 @@
width: 100%;
}
}
.omniauth-btn {
width: 100%;
padding: $gl-padding-8;
img {
width: $default-icon-size;
height: $default-icon-size;
margin-right: $gl-padding;
}
}
}
.new-session-tabs {

View File

@ -2142,11 +2142,6 @@ table.code {
width: 100%;
padding: 8px;
}
.login-page .omniauth-container .omniauth-btn img {
width: 1.125rem;
height: 1.125rem;
margin-right: 16px;
}
.login-page .new-session-tabs {
display: flex;
box-shadow: 0 0 0 1px #dbdbdb;

View File

@ -13,7 +13,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action do
push_frontend_feature_flag(:pipelines_security_report_summary, project)
push_frontend_feature_flag(:pipelines_security_report_summary, project, default_enabled: :yaml)
push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true)
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml)

View File

@ -138,11 +138,11 @@ module AuthHelper
label = label_for_provider(provider)
if provider_has_custom_icon?(provider)
image_tag(icon_for_provider(provider), alt: label, title: "Sign in with #{label}")
image_tag(icon_for_provider(provider), alt: label, title: "Sign in with #{label}", class: "gl-button-icon")
elsif provider_has_builtin_icon?(provider)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
image_tag("auth_buttons/#{file_name}", alt: label, title: "Sign in with #{label}")
image_tag("auth_buttons/#{file_name}", alt: label, title: "Sign in with #{label}", class: "gl-button-icon")
else
label
end

View File

@ -376,8 +376,7 @@ class MergeRequest < ApplicationRecord
alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds
alias_method :issuing_parent, :target_project
delegate :active?, :builds_with_coverage, to: :head_pipeline, prefix: true, allow_nil: true
delegate :success?, :active?, to: :actual_head_pipeline, prefix: true, allow_nil: true
delegate :builds_with_coverage, to: :head_pipeline, prefix: true, allow_nil: true
RebaseLockTimeout = Class.new(StandardError)
@ -437,6 +436,18 @@ class MergeRequest < ApplicationRecord
target_project.latest_pipeline(target_branch, sha)
end
def head_pipeline_active?
!!head_pipeline&.active?
end
def actual_head_pipeline_active?
!!actual_head_pipeline&.active?
end
def actual_head_pipeline_success?
!!actual_head_pipeline&.success?
end
# Pattern used to extract `!123` merge request references from text
#
# This pattern supports cross-project references.

View File

@ -216,8 +216,10 @@ class Snippet < ApplicationRecord
def blobs
return [] unless repository_exists?
branch = default_branch
list_files(branch).map { |file| Blob.lazy(repository, branch, file) }
files = list_files(default_branch)
items = files.map { |file| [default_branch, file] }
repository.blobs_at(items).compact
end
def hook_attrs

View File

@ -115,10 +115,8 @@ class SnippetRepository < ApplicationRecord
end
def invalid_path_error?(err)
(err.is_a?(Gitlab::Git::Index::IndexError) &&
err.message.downcase.start_with?('invalid path', 'path cannot include directory traversal')) ||
(err.is_a?(Gitlab::Git::CommandError) &&
err.message.include?('CreateFile: invalid path'))
err.is_a?(Gitlab::Git::Index::IndexError) &&
err.message.downcase.start_with?('invalid path', 'path cannot include directory traversal')
end
def invalid_signature_error?(err)

View File

@ -20,4 +20,4 @@
= recaptcha_tags
.submit-container.move-submit-down
= f.submit _('Sign in'), class: 'gl-button btn btn-success', data: { qa_selector: 'sign_in_button' }
= f.submit _('Sign in'), class: 'gl-button btn btn-confirm', data: { qa_selector: 'sign_in_button' }

View File

@ -7,7 +7,7 @@
.d-flex.justify-content-between.flex-wrap
- providers.each do |provider|
- has_icon = provider_has_icon?(provider)
= button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", class: "btn gl-button btn-default d-flex align-items-center omniauth-btn text-left oauth-login #{qa_class_for_provider(provider)}" do
= button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", class: "btn gl-button btn-default omniauth-btn oauth-login #{qa_class_for_provider(provider)}" do
- if has_icon
= provider_image_tag(provider)
%span

View File

@ -0,0 +1,6 @@
---
title: Update the Sign In button to use the new confirm button variant, migrate OAuth
buttons to use the default variant of GlButton.
merge_request: 53254
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix bug when snippet blobs array contain a nil value
merge_request: 54552
author:
type: fixed

View File

@ -1,5 +0,0 @@
---
title: Fix snippet commit bug because of different Gitaly error
merge_request: 54662
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: 'BulkImports: Import Label timestamps'
merge_request: 54678
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Updates authorization for linting endpoint
merge_request: 54492
author:
type: changed

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235943
milestone: '13.0'
type: development
group: group::dynamic analysis
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: marginalia
introduced_by_url:
rollout_issue_url:
milestone:
type: ops
group:
default_enabled: false

View File

@ -4,11 +4,6 @@ require 'marginalia'
::Marginalia::Comment.extend(::Gitlab::Marginalia::Comment)
# Patch to modify 'Marginalia::ActiveRecordInstrumentation.annotate_sql' method with feature check.
# Orignal Marginalia::ActiveRecordInstrumentation is included to ActiveRecord::ConnectionAdapters::PostgreSQLAdapter in the Marginalia Railtie.
# Refer: https://github.com/basecamp/marginalia/blob/v1.8.0/lib/marginalia/railtie.rb#L67
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Gitlab::Marginalia::ActiveRecordInstrumentation)
# By default, PostgreSQL only tracks the first 1024 bytes of a SQL
# query. Prepending the comment allows us to trace the source of the
# query without having to increase the `track_activity_query_size`
@ -25,5 +20,3 @@ Marginalia::Comment.components << :line if Rails.env.development?
Gitlab::Marginalia.set_application_name
Gitlab::Marginalia.enable_sidekiq_instrumentation
Gitlab::Marginalia.set_enabled_from_feature_flag

View File

@ -13,7 +13,7 @@
# end
if git.lines_of_code > 2_000
warn "This merge request is definitely too big (more than #{git.lines_of_code} lines changed), please split it into multiple merge requests."
warn "This merge request is definitely too big (#{git.lines_of_code} lines changed), please split it into multiple merge requests."
elsif git.lines_of_code > 500
warn "This merge request is quite big (more than #{git.lines_of_code} lines changed), please consider splitting it into multiple merge requests."
warn "This merge request is quite big (#{git.lines_of_code} lines changed), please consider splitting it into multiple merge requests."
end

View File

@ -161,6 +161,9 @@ To reveal existing RuboCop exceptions in the code that have been excluded via `.
This allows you to reveal existing RuboCop exceptions during your daily work cycle and fix them along the way.
NOTE:
Permanent `Exclude`s should be defined in `.rubocop.yml` instead of `.rubocop_manual_todo.yml`.
## Database migrations
See the dedicated [Database Migrations Style Guide](../migration_style_guide.md).

View File

@ -47,16 +47,3 @@ Examples of queries with comments as observed in `development.log`:
```sql
/*application:sidekiq,jid:e7d6668a39a991e323009833,job_class:ExpireJobCacheWorker,correlation_id:rYF4mey9CH3,line:/app/workers/expire_job_cache_worker.rb:14:in `perform'*/ SELECT "ci_pipelines".* FROM "ci_pipelines" WHERE "ci_pipelines"."id" = $1 LIMIT $2 [["id", 64], ["LIMIT", 1]]
```
## Enable/Disable the feature
Enabling or disabling the feature requires a **restart/SIGHUP** of the Web and
Sidekiq workers, as the feature flag's state is memoized upon starting up.
The `feature_flag` for this feature is **disabled** by default. You can enable
or disable it with:
```ruby
Feature.enable(:marginalia)
Feature.disable(:marginalia)
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

View File

@ -52,7 +52,7 @@ To use the security dashboards and vulnerability reports:
At the pipeline level, the Security section displays the vulnerabilities present in the branch of
the project the pipeline ran against.
![Pipeline Security Dashboard](img/pipeline_security_dashboard_v13_3.png)
![Pipeline Security Dashboard](img/pipeline_security_dashboard_v13_10.png)
Visit the page for any pipeline that ran any of the [supported reports](#supported-reports). To view
the pipeline's security findings, select the **Security** tab when viewing the pipeline.

View File

@ -24,7 +24,12 @@ The following resources are migrated to the target instance:
- description
- attributes
- subgroups
- Labels ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/292429))
- Group Labels ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/292429))
- title
- description
- color
- created_at ([Introduced in 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300007))
- updated_at ([Introduced in 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300007))
- Members ([Introduced in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/299415))
Group members are associated with the imported group if:
- The user already exists in the target GitLab instance and

View File

@ -11,7 +11,7 @@ module API
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
end
post '/lint' do
unauthorized! unless Gitlab::CurrentSettings.signup_enabled? && current_user
unauthorized! if Gitlab::CurrentSettings.signup_disabled? && current_user.nil?
result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute

View File

@ -19,6 +19,8 @@ module BulkImports
title
description
color
created_at: createdAt
updated_at: updatedAt
}
}
}

View File

@ -3,6 +3,10 @@
module Gitlab
module CurrentSettings
class << self
def signup_disabled?
!signup_enabled?
end
def current_application_settings
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
end

View File

@ -2,8 +2,6 @@
module Gitlab
module Marginalia
cattr_accessor :enabled, default: false
def self.set_application_name
::Marginalia.application_name = Gitlab.process_name
end
@ -13,12 +11,5 @@ module Gitlab
::Marginalia::SidekiqInstrumentation.enable!
end
end
def self.set_enabled_from_feature_flag
# During db:create and db:bootstrap skip feature query as DB is not available yet.
return false unless Gitlab::Database.cached_table_exists?('features')
self.enabled = Feature.enabled?(:marginalia, type: :ops)
end
end
end

View File

@ -1,12 +0,0 @@
# frozen_string_literal: true
# Patch to annotate sql only when the feature is enabled.
module Gitlab
module Marginalia
module ActiveRecordInstrumentation
def annotate_sql(sql)
Gitlab::Marginalia.enabled ? super(sql) : sql
end
end
end
end

View File

@ -4808,6 +4808,9 @@ msgstr ""
msgid "Boards|An error occurred while fetching issues. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board epics. Please reload the page."
msgstr ""
msgid "Boards|An error occurred while fetching the board issues. Please reload the page."
msgstr ""
@ -17897,7 +17900,7 @@ msgstr ""
msgid "Loading issues"
msgstr ""
msgid "Loading more issues"
msgid "Loading more"
msgstr ""
msgid "Loading snippet"

View File

@ -207,14 +207,14 @@ RSpec.describe Projects::SnippetsController do
subject
expect(assigns(:snippet)).to eq(project_snippet)
expect(assigns(:blobs)).to eq(project_snippet.blobs)
expect(assigns(:blobs).map(&:name)).to eq(project_snippet.blobs.map(&:name))
expect(response).to have_gitlab_http_status(:ok)
end
it 'does not show the blobs expanded by default' do
subject
expect(project_snippet.blobs.map(&:expanded?)).to be_all(false)
expect(assigns(:blobs).map(&:expanded?)).to be_all(false)
end
context 'when param expanded is set' do
@ -223,7 +223,7 @@ RSpec.describe Projects::SnippetsController do
it 'shows all blobs expanded' do
subject
expect(project_snippet.blobs.map(&:expanded?)).to be_all(true)
expect(assigns(:blobs).map(&:expanded?)).to be_all(true)
end
end
end

View File

@ -29,8 +29,8 @@ const createComponent = ({
state = {},
} = {}) => {
const store = createStore({
issuesByListId: mockIssuesByListId,
issues,
boardItemsByListId: mockIssuesByListId,
boardItems: issues,
pageInfoByListId: {
'gid://gitlab/List/1': { hasNextPage: true },
'gid://gitlab/List/2': {},
@ -65,7 +65,7 @@ const createComponent = ({
propsData: {
disabled: false,
list,
issues: [issue],
boardItems: [issue],
canAdminList: true,
...componentProps,
},

View File

@ -24,7 +24,7 @@ describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => {
const createWrapper = ({ dueDate = null } = {}) => {
store = createStore();
store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, dueDate } };
store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, dueDate } };
store.state.activeId = TEST_ISSUE.id;
wrapper = shallowMount(BoardSidebarDueDate, {
@ -61,7 +61,7 @@ describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => {
createWrapper();
jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => {
store.state.issues[TEST_ISSUE.id].dueDate = TEST_DUE_DATE;
store.state.boardItems[TEST_ISSUE.id].dueDate = TEST_DUE_DATE;
});
findDatePicker().vm.$emit('input', TEST_PARSED_DATE);
await wrapper.vm.$nextTick();
@ -86,7 +86,7 @@ describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => {
createWrapper();
jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => {
store.state.issues[TEST_ISSUE.id].dueDate = null;
store.state.boardItems[TEST_ISSUE.id].dueDate = null;
});
findDatePicker().vm.$emit('clear');
await wrapper.vm.$nextTick();
@ -104,7 +104,7 @@ describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => {
createWrapper({ dueDate: TEST_DUE_DATE });
jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => {
store.state.issues[TEST_ISSUE.id].dueDate = null;
store.state.boardItems[TEST_ISSUE.id].dueDate = null;
});
findResetButton().vm.$emit('click');
await wrapper.vm.$nextTick();

View File

@ -34,7 +34,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
const createWrapper = (issue = TEST_ISSUE_A) => {
store = createStore();
store.state.issues = { [issue.id]: { ...issue } };
store.state.boardItems = { [issue.id]: { ...issue } };
store.dispatch('setActiveId', { id: issue.id });
wrapper = shallowMount(BoardSidebarIssueTitle, {
@ -74,7 +74,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
createWrapper();
jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => {
store.state.issues[TEST_ISSUE_A.id].title = TEST_TITLE;
store.state.boardItems[TEST_ISSUE_A.id].title = TEST_TITLE;
});
findFormInput().vm.$emit('input', TEST_TITLE);
findForm().vm.$emit('submit', { preventDefault: () => {} });
@ -147,7 +147,7 @@ describe('~/boards/components/sidebar/board_sidebar_issue_title.vue', () => {
createWrapper(TEST_ISSUE_B);
jest.spyOn(wrapper.vm, 'setActiveIssueTitle').mockImplementation(() => {
store.state.issues[TEST_ISSUE_B.id].title = TEST_TITLE;
store.state.boardItems[TEST_ISSUE_B.id].title = TEST_TITLE;
});
findFormInput().vm.$emit('input', TEST_TITLE);
findCancelButton().vm.$emit('click');

View File

@ -25,7 +25,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
const createWrapper = ({ labels = [] } = {}) => {
store = createStore();
store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, labels } };
store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, labels } };
store.state.activeId = TEST_ISSUE.id;
wrapper = shallowMount(BoardSidebarLabelsSelect, {
@ -66,7 +66,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
jest.spyOn(wrapper.vm, 'setActiveIssueLabels').mockImplementation(() => TEST_LABELS);
findLabelsSelect().vm.$emit('updateSelectedLabels', TEST_LABELS_PAYLOAD);
store.state.issues[TEST_ISSUE.id].labels = TEST_LABELS;
store.state.boardItems[TEST_ISSUE.id].labels = TEST_LABELS;
await wrapper.vm.$nextTick();
});

View File

@ -22,7 +22,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
const createWrapper = ({ milestone = null, loading = false } = {}) => {
store = createStore();
store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
store.state.activeId = TEST_ISSUE.id;
wrapper = shallowMount(BoardSidebarMilestoneSelect, {
@ -113,7 +113,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
createWrapper();
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
store.state.issues[TEST_ISSUE.id].milestone = TEST_MILESTONE;
store.state.boardItems[TEST_ISSUE.id].milestone = TEST_MILESTONE;
});
findDropdownItem().vm.$emit('click');
await wrapper.vm.$nextTick();
@ -137,7 +137,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
createWrapper({ milestone: TEST_MILESTONE });
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
store.state.issues[TEST_ISSUE.id].milestone = null;
store.state.boardItems[TEST_ISSUE.id].milestone = null;
});
findUnsetMilestoneItem().vm.$emit('click');
await wrapper.vm.$nextTick();

View File

@ -22,7 +22,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
const createComponent = (activeIssue = { ...mockActiveIssue }) => {
store = createStore();
store.state.issues = { [activeIssue.id]: activeIssue };
store.state.boardItems = { [activeIssue.id]: activeIssue };
store.state.activeId = activeIssue.id;
wrapper = mount(BoardSidebarSubscription, {

View File

@ -573,7 +573,7 @@ describe('fetchItemsForList', () => {
},
{
type: types.RECEIVE_ITEMS_FOR_LIST_SUCCESS,
payload: { listIssues: formattedIssues, listPageInfo, listId },
payload: { listItems: formattedIssues, listPageInfo, listId },
},
],
[],
@ -624,8 +624,8 @@ describe('moveIssue', () => {
boardType: 'group',
disabled: false,
boardLists: mockLists,
issuesByListId: listIssues,
issues,
boardItemsByListId: listIssues,
boardItems: issues,
};
it('should commit MOVE_ISSUE mutation and MOVE_ISSUE_SUCCESS mutation when successful', (done) => {
@ -905,7 +905,7 @@ describe('addListIssue', () => {
});
describe('setActiveIssueLabels', () => {
const state = { issues: { [mockIssue.id]: mockIssue } };
const state = { boardItems: { [mockIssue.id]: mockIssue } };
const getters = { activeIssue: mockIssue };
const testLabelIds = labels.map((label) => label.id);
const input = {
@ -950,7 +950,7 @@ describe('setActiveIssueLabels', () => {
});
describe('setActiveIssueDueDate', () => {
const state = { issues: { [mockIssue.id]: mockIssue } };
const state = { boardItems: { [mockIssue.id]: mockIssue } };
const getters = { activeIssue: mockIssue };
const testDueDate = '2020-02-20';
const input = {
@ -1001,7 +1001,7 @@ describe('setActiveIssueDueDate', () => {
});
describe('setActiveIssueSubscribed', () => {
const state = { issues: { [mockActiveIssue.id]: mockActiveIssue } };
const state = { boardItems: { [mockActiveIssue.id]: mockActiveIssue } };
const getters = { activeIssue: mockActiveIssue };
const subscribedState = true;
const input = {
@ -1052,7 +1052,7 @@ describe('setActiveIssueSubscribed', () => {
});
describe('setActiveIssueMilestone', () => {
const state = { issues: { [mockIssue.id]: mockIssue } };
const state = { boardItems: { [mockIssue.id]: mockIssue } };
const getters = { activeIssue: mockIssue };
const testMilestone = {
...mockMilestone,
@ -1106,7 +1106,7 @@ describe('setActiveIssueMilestone', () => {
});
describe('setActiveIssueTitle', () => {
const state = { issues: { [mockIssue.id]: mockIssue } };
const state = { boardItems: { [mockIssue.id]: mockIssue } };
const getters = { activeIssue: mockIssue };
const testTitle = 'Test Title';
const input = {

View File

@ -38,15 +38,15 @@ describe('Boards - Getters', () => {
});
});
describe('getIssueById', () => {
const state = { issues: { 1: 'issue' } };
describe('getBoardItemById', () => {
const state = { boardItems: { 1: 'issue' } };
it.each`
id | expected
${'1'} | ${'issue'}
${''} | ${{}}
`('returns $expected when $id is passed to state', ({ id, expected }) => {
expect(getters.getIssueById(state)(id)).toEqual(expected);
expect(getters.getBoardItemById(state)(id)).toEqual(expected);
});
});
@ -56,7 +56,7 @@ describe('Boards - Getters', () => {
${'1'} | ${'issue'}
${''} | ${{}}
`('returns $expected when $id is passed to state', ({ id, expected }) => {
const state = { issues: { 1: 'issue' }, activeId: id };
const state = { boardItems: { 1: 'issue' }, activeId: id };
expect(getters.activeIssue(state)).toEqual(expected);
});
@ -94,17 +94,18 @@ describe('Boards - Getters', () => {
});
});
describe('getIssuesByList', () => {
describe('getBoardItemsByList', () => {
const boardsState = {
issuesByListId: mockIssuesByListId,
issues,
boardItemsByListId: mockIssuesByListId,
boardItems: issues,
};
it('returns issues for a given listId', () => {
const getIssueById = (issueId) => [mockIssue, mockIssue2].find(({ id }) => id === issueId);
const getBoardItemById = (issueId) =>
[mockIssue, mockIssue2].find(({ id }) => id === issueId);
expect(getters.getIssuesByList(boardsState, { getIssueById })('gid://gitlab/List/2')).toEqual(
mockIssues,
);
expect(
getters.getBoardItemsByList(boardsState, { getBoardItemById })('gid://gitlab/List/2'),
).toEqual(mockIssues);
});
});

View File

@ -222,24 +222,24 @@ describe('Board Store Mutations', () => {
});
describe('RESET_ISSUES', () => {
it('should remove issues from issuesByListId state', () => {
const issuesByListId = {
it('should remove issues from boardItemsByListId state', () => {
const boardItemsByListId = {
'gid://gitlab/List/1': [mockIssue.id],
};
state = {
...state,
issuesByListId,
boardItemsByListId,
};
mutations[types.RESET_ISSUES](state);
expect(state.issuesByListId).toEqual({ 'gid://gitlab/List/1': [] });
expect(state.boardItemsByListId).toEqual({ 'gid://gitlab/List/1': [] });
});
});
describe('RECEIVE_ITEMS_FOR_LIST_SUCCESS', () => {
it('updates issuesByListId and issues on state', () => {
it('updates boardItemsByListId and issues on state', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id],
};
@ -249,10 +249,10 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: {
boardItemsByListId: {
'gid://gitlab/List/1': [],
},
issues: {},
boardItems: {},
boardLists: initialBoardListsState,
};
@ -264,13 +264,13 @@ describe('Board Store Mutations', () => {
};
mutations.RECEIVE_ITEMS_FOR_LIST_SUCCESS(state, {
listIssues: { listData: listIssues, issues },
listItems: { listData: listIssues, boardItems: issues },
listPageInfo,
listId: 'gid://gitlab/List/1',
});
expect(state.issuesByListId).toEqual(listIssues);
expect(state.issues).toEqual(issues);
expect(state.boardItemsByListId).toEqual(listIssues);
expect(state.boardItems).toEqual(issues);
});
});
@ -306,7 +306,7 @@ describe('Board Store Mutations', () => {
state = {
...state,
error: undefined,
issues: {
boardItems: {
...issue,
},
};
@ -320,7 +320,7 @@ describe('Board Store Mutations', () => {
value,
});
expect(state.issues[issueId]).toEqual({ ...issue[issueId], id: '2' });
expect(state.boardItems[issueId]).toEqual({ ...issue[issueId], id: '2' });
});
});
@ -346,7 +346,7 @@ describe('Board Store Mutations', () => {
});
describe('MOVE_ISSUE', () => {
it('updates issuesByListId, moving issue between lists', () => {
it('updates boardItemsByListId, moving issue between lists', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
'gid://gitlab/List/2': [],
@ -359,9 +359,9 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
boardItemsByListId: listIssues,
boardLists: initialBoardListsState,
issues,
boardItems: issues,
};
mutations.MOVE_ISSUE(state, {
@ -375,7 +375,7 @@ describe('Board Store Mutations', () => {
'gid://gitlab/List/2': [mockIssue2.id],
};
expect(state.issuesByListId).toEqual(updatedListIssues);
expect(state.boardItemsByListId).toEqual(updatedListIssues);
});
});
@ -387,19 +387,19 @@ describe('Board Store Mutations', () => {
state = {
...state,
issues,
boardItems: issues,
};
mutations.MOVE_ISSUE_SUCCESS(state, {
issue: rawIssue,
});
expect(state.issues).toEqual({ 436: { ...mockIssue, id: 436 } });
expect(state.boardItems).toEqual({ 436: { ...mockIssue, id: 436 } });
});
});
describe('MOVE_ISSUE_FAILURE', () => {
it('updates issuesByListId, reverting moving issue between lists, and sets error message', () => {
it('updates boardItemsByListId, reverting moving issue between lists, and sets error message', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id],
'gid://gitlab/List/2': [mockIssue2.id],
@ -407,7 +407,7 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
boardItemsByListId: listIssues,
boardLists: initialBoardListsState,
};
@ -423,7 +423,7 @@ describe('Board Store Mutations', () => {
'gid://gitlab/List/2': [],
};
expect(state.issuesByListId).toEqual(updatedListIssues);
expect(state.boardItemsByListId).toEqual(updatedListIssues);
expect(state.error).toEqual('An error occurred while moving the issue. Please try again.');
});
});
@ -449,7 +449,7 @@ describe('Board Store Mutations', () => {
});
describe('ADD_ISSUE_TO_LIST', () => {
it('adds issue to issues state and issue id in list in issuesByListId', () => {
it('adds issue to issues state and issue id in list in boardItemsByListId', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id],
};
@ -459,8 +459,8 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
issues,
boardItemsByListId: listIssues,
boardItems: issues,
boardLists: initialBoardListsState,
};
@ -468,14 +468,14 @@ describe('Board Store Mutations', () => {
mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 });
expect(state.issuesByListId['gid://gitlab/List/1']).toContain(mockIssue2.id);
expect(state.issues[mockIssue2.id]).toEqual(mockIssue2);
expect(state.boardItemsByListId['gid://gitlab/List/1']).toContain(mockIssue2.id);
expect(state.boardItems[mockIssue2.id]).toEqual(mockIssue2);
expect(state.boardLists['gid://gitlab/List/1'].issuesCount).toBe(2);
});
});
describe('ADD_ISSUE_TO_LIST_FAILURE', () => {
it('removes issue id from list in issuesByListId and sets error message', () => {
it('removes issue id from list in boardItemsByListId and sets error message', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
};
@ -486,20 +486,20 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
issues,
boardItemsByListId: listIssues,
boardItems: issues,
boardLists: initialBoardListsState,
};
mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id });
expect(state.issuesByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
expect(state.error).toBe('An error occurred while creating the issue. Please try again.');
});
});
describe('REMOVE_ISSUE_FROM_LIST', () => {
it('removes issue id from list in issuesByListId and deletes issue from state', () => {
it('removes issue id from list in boardItemsByListId and deletes issue from state', () => {
const listIssues = {
'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
};
@ -510,15 +510,15 @@ describe('Board Store Mutations', () => {
state = {
...state,
issuesByListId: listIssues,
issues,
boardItemsByListId: listIssues,
boardItems: issues,
boardLists: initialBoardListsState,
};
mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issueId: mockIssue2.id });
expect(state.issuesByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
expect(state.issues).not.toContain(mockIssue2);
expect(state.boardItemsByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
expect(state.boardItems).not.toContain(mockIssue2);
});
});

View File

@ -5,16 +5,22 @@ require 'spec_helper'
RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
let(:commit) { create(:commit, project: project) }
let(:commit_without_author) { RepoHelpers.another_sample_commit }
let!(:user) { create(:user, developer_projects: [project], email: commit.author_email) }
let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) }
let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) }
let!(:pipeline_without_commit) { create(:ci_pipeline, status: :success, project: project, sha: '0000') }
let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
render_views
let_it_be(:commit_without_author) { RepoHelpers.another_sample_commit }
let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) }
let!(:build_pipeline_without_author) { create(:ci_build, pipeline: pipeline_without_author, stage: 'test') }
let_it_be(:pipeline_without_commit) { create(:ci_pipeline, status: :success, project: project, sha: '0000') }
let!(:build_pipeline_without_commit) { create(:ci_build, pipeline: pipeline_without_commit, stage: 'test') }
let(:commit) { create(:commit, project: project) }
let(:user) { create(:user, developer_projects: [project], email: commit.author_email) }
let!(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project, sha: commit.id, user: user) }
let!(:build_success) { create(:ci_build, pipeline: pipeline, stage: 'build') }
let!(:build_test) { create(:ci_build, pipeline: pipeline, stage: 'test') }
let!(:build_deploy_failed) { create(:ci_build, status: :failed, pipeline: pipeline, stage: 'deploy') }
before(:all) do
clean_frontend_fixtures('pipelines/')
@ -32,4 +38,14 @@ RSpec.describe Projects::PipelinesController, '(JavaScript fixtures)', type: :co
expect(response).to be_successful
end
it "pipelines/test_report.json" do
get :test_report, params: {
namespace_id: namespace,
project_id: project,
id: pipeline.id
}, format: :json
expect(response).to be_successful
end
end

View File

@ -1,29 +0,0 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Projects::PipelinesController, "(JavaScript fixtures)", type: :controller do
include JavaScriptFixturesHelpers
let(:namespace) { create(:namespace, name: "frontend-fixtures") }
let(:project) { create(:project, :repository, namespace: namespace, path: "pipelines-project") }
let(:commit) { create(:commit, project: project) }
let(:user) { create(:user, developer_projects: [project], email: commit.author_email) }
let(:pipeline) { create(:ci_pipeline, :with_test_reports, project: project, user: user) }
render_views
before do
sign_in(user)
end
it "pipelines/test_report.json" do
get :test_report, params: {
namespace_id: project.namespace,
project_id: project,
id: pipeline.id
}, format: :json
expect(response).to be_successful
end
end

View File

@ -2,328 +2,6 @@ const PIPELINE_RUNNING = 'RUNNING';
const PIPELINE_CANCELED = 'CANCELED';
const PIPELINE_FAILED = 'FAILED';
export const pipelineWithStages = {
id: 20333396,
user: {
id: 128633,
name: 'Rémy Coutable',
username: 'rymai',
state: 'active',
avatar_url:
'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
web_url: 'https://gitlab.com/rymai',
path: '/rymai',
},
active: true,
coverage: '58.24',
source: 'push',
created_at: '2018-04-11T14:04:53.881Z',
updated_at: '2018-04-11T14:05:00.792Z',
path: '/gitlab-org/gitlab/pipelines/20333396',
flags: {
latest: true,
stuck: false,
auto_devops: false,
yaml_errors: false,
retryable: false,
cancelable: true,
failure_reason: false,
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab/pipelines/20333396',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
},
duration: null,
finished_at: null,
stages: [
{
name: 'build',
title: 'build: skipped',
status: {
icon: 'status_skipped',
text: 'skipped',
label: 'skipped',
group: 'skipped',
has_details: true,
details_path: '/gitlab-org/gitlab/pipelines/20333396#build',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_skipped-a2eee568a5bffdb494050c7b62dde241de9189280836288ac8923d369f16222d.ico',
},
path: '/gitlab-org/gitlab/pipelines/20333396#build',
dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=build',
},
{
name: 'prepare',
title: 'prepare: passed',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
has_details: true,
details_path: '/gitlab-org/gitlab/pipelines/20333396#prepare',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_success-26f59841becbef8c6fe414e9e74471d8bfd6a91b5855c19fe7f5923a40a7da47.ico',
},
path: '/gitlab-org/gitlab/pipelines/20333396#prepare',
dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=prepare',
},
{
name: 'test',
title: 'test: running',
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab/pipelines/20333396#test',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
},
path: '/gitlab-org/gitlab/pipelines/20333396#test',
dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=test',
},
{
name: 'post-test',
title: 'post-test: created',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab/pipelines/20333396#post-test',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
},
path: '/gitlab-org/gitlab/pipelines/20333396#post-test',
dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=post-test',
},
{
name: 'pages',
title: 'pages: created',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab/pipelines/20333396#pages',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
},
path: '/gitlab-org/gitlab/pipelines/20333396#pages',
dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=pages',
},
{
name: 'post-cleanup',
title: 'post-cleanup: created',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab/pipelines/20333396#post-cleanup',
favicon:
'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
},
path: '/gitlab-org/gitlab/pipelines/20333396#post-cleanup',
dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=post-cleanup',
},
],
artifacts: [
{
name: 'gitlab:assets:compile',
expired: false,
expire_at: '2018-05-12T14:22:54.730Z',
path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/browse',
},
{
name: 'rspec-mysql 12 28',
expired: false,
expire_at: '2018-05-12T14:22:45.136Z',
path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/browse',
},
{
name: 'rspec-mysql 6 28',
expired: false,
expire_at: '2018-05-12T14:22:41.523Z',
path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/browse',
},
{
name: 'rspec-pg geo 0 1',
expired: false,
expire_at: '2018-05-12T14:22:13.287Z',
path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/browse',
},
{
name: 'rspec-mysql 0 28',
expired: false,
expire_at: '2018-05-12T14:22:06.834Z',
path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/browse',
},
{
name: 'spinach-mysql 0 2',
expired: false,
expire_at: '2018-05-12T14:21:51.409Z',
path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/browse',
},
{
name: 'karma',
expired: false,
expire_at: '2018-05-12T14:21:20.934Z',
path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/browse',
},
{
name: 'spinach-pg 0 2',
expired: false,
expire_at: '2018-05-12T14:20:01.028Z',
path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/browse',
},
{
name: 'spinach-pg 1 2',
expired: false,
expire_at: '2018-05-12T14:19:04.336Z',
path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/browse',
},
{
name: 'sast',
expired: null,
expire_at: null,
path: '/gitlab-org/gitlab/-/jobs/62411442/artifacts/download',
browse_path: '/gitlab-org/gitlab/-/jobs/62411442/artifacts/browse',
},
{
name: 'code_quality',
expired: false,
expire_at: '2018-04-18T14:16:24.484Z',
path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/browse',
},
{
name: 'cache gems',
expired: null,
expire_at: null,
path: '/gitlab-org/gitlab/-/jobs/62411447/artifacts/download',
browse_path: '/gitlab-org/gitlab/-/jobs/62411447/artifacts/browse',
},
{
name: 'dependency_scanning',
expired: null,
expire_at: null,
path: '/gitlab-org/gitlab/-/jobs/62411443/artifacts/download',
browse_path: '/gitlab-org/gitlab/-/jobs/62411443/artifacts/browse',
},
{
name: 'compile-assets',
expired: false,
expire_at: '2018-04-18T14:12:07.638Z',
path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/browse',
},
{
name: 'setup-test-env',
expired: false,
expire_at: '2018-04-18T14:10:27.024Z',
path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/browse',
},
{
name: 'retrieve-tests-metadata',
expired: false,
expire_at: '2018-05-12T14:06:35.926Z',
path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/download',
keep_path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/keep',
browse_path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/browse',
},
],
manual_actions: [
{
name: 'package-and-qa',
path: '/gitlab-org/gitlab/-/jobs/62411330/play',
playable: true,
},
{
name: 'review-docs-deploy',
path: '/gitlab-org/gitlab/-/jobs/62411332/play',
playable: true,
},
],
},
ref: {
name: 'master',
path: '/gitlab-org/gitlab/commits/master',
tag: false,
branch: true,
},
commit: {
id: 'e6a2885c503825792cb8a84a8731295e361bd059',
short_id: 'e6a2885c',
title: "Merge branch 'ce-to-ee-2018-04-11' into 'master'",
created_at: '2018-04-11T14:04:39.000Z',
parent_ids: [
'5d9b5118f6055f72cff1a82b88133609912f2c1d',
'6fdc6ee76a8062fe41b1a33f7c503334a6ebdc02',
],
message:
"Merge branch 'ce-to-ee-2018-04-11' into 'master'\n\nCE upstream - 2018-04-11 12:26 UTC\n\nSee merge request gitlab-org/gitlab-ee!5326",
author_name: 'Rémy Coutable',
author_email: 'remy@rymai.me',
authored_date: '2018-04-11T14:04:39.000Z',
committer_name: 'Rémy Coutable',
committer_email: 'remy@rymai.me',
committed_date: '2018-04-11T14:04:39.000Z',
author: {
id: 128633,
name: 'Rémy Coutable',
username: 'rymai',
state: 'active',
avatar_url:
'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
web_url: 'https://gitlab.com/rymai',
path: '/rymai',
},
author_gravatar_url:
'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
commit_url:
'https://gitlab.com/gitlab-org/gitlab/commit/e6a2885c503825792cb8a84a8731295e361bd059',
commit_path: '/gitlab-org/gitlab/commit/e6a2885c503825792cb8a84a8731295e361bd059',
},
cancel_path: '/gitlab-org/gitlab/pipelines/20333396/cancel',
triggered_by: null,
triggered: [],
};
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);

View File

@ -18,7 +18,7 @@ import Store from '~/pipelines/stores/pipelines_store';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
import { stageReply, users, mockSearch, branches } from './mock_data';
jest.mock('~/flash');
@ -27,6 +27,9 @@ const mockProjectId = '21';
const mockPipelinesEndpoint = `/${mockProjectPath}/pipelines.json`;
const mockPipelinesResponse = getJSONFixture('pipelines/pipelines.json');
const mockPipelinesIds = mockPipelinesResponse.pipelines.map(({ id }) => id);
const mockPipelineWithStages = mockPipelinesResponse.pipelines.find(
(p) => p.details.stages && p.details.stages.length,
);
describe('Pipelines', () => {
let wrapper;
@ -611,14 +614,15 @@ describe('Pipelines', () => {
mock.onGet(mockPipelinesEndpoint, { scope: 'all', page: '1' }).reply(
200,
{
pipelines: [pipelineWithStages],
pipelines: [mockPipelineWithStages],
count: { all: '1' },
},
{
'POLL-INTERVAL': 100,
},
);
mock.onGet(pipelineWithStages.details.stages[0].dropdown_path).reply(200, stageReply);
mock.onGet(mockPipelineWithStages.details.stages[0].dropdown_path).reply(200, stageReply);
createComponent();

View File

@ -155,7 +155,7 @@ describe('Pipelines Table Row', () => {
it('should render an icon for each stage', () => {
expect(
wrapper.findAll(
'.table-section:nth-child(4) [data-testid="mini-pipeline-graph-dropdown-toggle"]',
'.table-section:nth-child(5) [data-testid="mini-pipeline-graph-dropdown-toggle"]',
).length,
).toEqual(pipeline.details.stages.length);
});
@ -182,9 +182,10 @@ describe('Pipelines Table Row', () => {
expect(wrapper.find('.js-pipelines-retry-button').attributes('title')).toMatch('Retry');
expect(wrapper.find('.js-pipelines-cancel-button').exists()).toBe(true);
expect(wrapper.find('.js-pipelines-cancel-button').attributes('title')).toMatch('Cancel');
const dropdownMenu = wrapper.find('.dropdown-menu');
expect(dropdownMenu.text()).toContain(scheduledJobAction.name);
const actionsMenu = wrapper.find('[data-testid="pipelines-manual-actions-dropdown"]');
expect(actionsMenu.text()).toContain(scheduledJobAction.name);
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {

View File

@ -6,6 +6,7 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:cursor) { 'cursor' }
let(:timestamp) { Time.new(2020, 01, 01).utc }
let(:entity) do
create(
:bulk_import_entity,
@ -20,21 +21,23 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
subject { described_class.new(context) }
def extractor_data(title:, has_next_page:, cursor: nil)
data = [
{
'title' => title,
'description' => 'desc',
'color' => '#428BCA'
}
]
def label_data(title)
{
'title' => title,
'description' => 'desc',
'color' => '#428BCA',
'created_at' => timestamp.to_s,
'updated_at' => timestamp.to_s
}
end
def extractor_data(title:, has_next_page:, cursor: nil)
page_info = {
'end_cursor' => cursor,
'has_next_page' => has_next_page
}
BulkImports::Pipeline::ExtractedData.new(data: data, page_info: page_info)
BulkImports::Pipeline::ExtractedData.new(data: [label_data(title)], page_info: page_info)
end
describe '#run' do
@ -55,6 +58,8 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
expect(label.title).to eq('label2')
expect(label.description).to eq('desc')
expect(label.color).to eq('#428BCA')
expect(label.created_at).to eq(timestamp)
expect(label.updated_at).to eq(timestamp)
end
end
@ -92,19 +97,15 @@ RSpec.describe BulkImports::Groups::Pipelines::LabelsPipeline do
describe '#load' do
it 'creates the label' do
data = {
'title' => 'label',
'description' => 'description',
'color' => '#FFFFFF'
}
data = label_data('label')
expect { subject.load(context, data) }.to change(Label, :count).by(1)
label = group.labels.first
expect(label.title).to eq(data['title'])
expect(label.description).to eq(data['description'])
expect(label.color).to eq(data['color'])
data.each do |key, value|
expect(label[key]).to eq(value)
end
end
end

View File

@ -24,6 +24,26 @@ RSpec.describe Gitlab::CurrentSettings do
end
end
describe '.signup_disabled?' do
subject { described_class.signup_disabled? }
context 'when signup is enabled' do
before do
create(:application_setting, signup_enabled: true)
end
it { is_expected.to be_falsey }
end
context 'when signup is disabled' do
before do
create(:application_setting, signup_enabled: false)
end
it { is_expected.to be_truthy }
end
end
describe '#current_application_settings', :use_clean_rails_memory_store_caching do
it 'allows keys to be called directly' do
db_settings = create(:application_setting,

View File

@ -37,26 +37,9 @@ RSpec.describe 'Marginalia spec' do
}
end
context 'when the feature is enabled' do
before do
stub_feature(true)
end
it 'generates a query that includes the component and value' do
component_map.each do |component, value|
expect(recorded.log.last).to include("#{component}:#{value}")
end
end
end
context 'when the feature is disabled' do
before do
stub_feature(false)
end
it 'excludes annotations in generated queries' do
expect(recorded.log.last).not_to include("/*")
expect(recorded.log.last).not_to include("*/")
it 'generates a query that includes the component and value' do
component_map.each do |component, value|
expect(recorded.log.last).to include("#{component}:#{value}")
end
end
end
@ -90,9 +73,27 @@ RSpec.describe 'Marginalia spec' do
}
end
context 'when the feature is enabled' do
before do
stub_feature(true)
it 'generates a query that includes the component and value' do
component_map.each do |component, value|
expect(recorded.log.last).to include("#{component}:#{value}")
end
end
describe 'for ActionMailer delivery jobs' do
let(:delivery_job) { MarginaliaTestMailer.first_user.deliver_later }
let(:recorded) do
ActiveRecord::QueryRecorder.new do
delivery_job.perform_now
end
end
let(:component_map) do
{
"application" => "sidekiq",
"jid" => delivery_job.job_id,
"job_class" => delivery_job.arguments.first
}
end
it 'generates a query that includes the component and value' do
@ -100,47 +101,7 @@ RSpec.describe 'Marginalia spec' do
expect(recorded.log.last).to include("#{component}:#{value}")
end
end
describe 'for ActionMailer delivery jobs' do
let(:delivery_job) { MarginaliaTestMailer.first_user.deliver_later }
let(:recorded) do
ActiveRecord::QueryRecorder.new do
delivery_job.perform_now
end
end
let(:component_map) do
{
"application" => "sidekiq",
"jid" => delivery_job.job_id,
"job_class" => delivery_job.arguments.first
}
end
it 'generates a query that includes the component and value' do
component_map.each do |component, value|
expect(recorded.log.last).to include("#{component}:#{value}")
end
end
end
end
context 'when the feature is disabled' do
before do
stub_feature(false)
end
it 'excludes annotations in generated queries' do
expect(recorded.log.last).not_to include("/*")
expect(recorded.log.last).not_to include("*/")
end
end
end
def stub_feature(value)
stub_feature_flags(marginalia: value)
Gitlab::Marginalia.set_enabled_from_feature_flag
end
def make_request(correlation_id)

View File

@ -3082,32 +3082,83 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
describe "#head_pipeline_active? " do
it do
is_expected
.to delegate_method(:active?)
.to(:head_pipeline)
.with_prefix
.with_arguments(allow_nil: true)
context 'when project lacks a head_pipeline relation' do
before do
subject.head_pipeline = nil
end
it 'returns false' do
expect(subject.head_pipeline_active?).to be false
end
end
context 'when project has a head_pipeline relation' do
let(:pipeline) { create(:ci_empty_pipeline) }
before do
allow(subject).to receive(:head_pipeline) { pipeline }
end
it 'accesses the value from the head_pipeline' do
expect(subject.head_pipeline)
.to receive(:active?)
subject.head_pipeline_active?
end
end
end
describe "#actual_head_pipeline_success? " do
it do
is_expected
.to delegate_method(:success?)
.to(:actual_head_pipeline)
.with_prefix
.with_arguments(allow_nil: true)
context 'when project lacks an actual_head_pipeline relation' do
before do
allow(subject).to receive(:actual_head_pipeline) { nil }
end
it 'returns false' do
expect(subject.actual_head_pipeline_success?).to be false
end
end
context 'when project has a actual_head_pipeline relation' do
let(:pipeline) { create(:ci_empty_pipeline) }
before do
allow(subject).to receive(:actual_head_pipeline) { pipeline }
end
it 'accesses the value from the actual_head_pipeline' do
expect(subject.actual_head_pipeline)
.to receive(:success?)
subject.actual_head_pipeline_success?
end
end
end
describe "#actual_head_pipeline_active? " do
it do
is_expected
.to delegate_method(:active?)
.to(:actual_head_pipeline)
.with_prefix
.with_arguments(allow_nil: true)
context 'when project lacks an actual_head_pipeline relation' do
before do
allow(subject).to receive(:actual_head_pipeline) { nil }
end
it 'returns false' do
expect(subject.actual_head_pipeline_active?).to be false
end
end
context 'when project has a actual_head_pipeline relation' do
let(:pipeline) { create(:ci_empty_pipeline) }
before do
allow(subject).to receive(:actual_head_pipeline) { pipeline }
end
it 'accesses the value from the actual_head_pipeline' do
expect(subject.actual_head_pipeline)
.to receive(:active?)
subject.actual_head_pipeline_active?
end
end
end

View File

@ -496,6 +496,16 @@ RSpec.describe Snippet do
it 'returns array of blobs' do
expect(snippet.blobs).to all(be_a(Blob))
end
context 'when file does not exist' do
it 'removes nil values from the blobs array' do
allow(snippet).to receive(:list_files).and_return(%w(LICENSE non_existent_snippet_file))
blobs = snippet.blobs
expect(blobs.count).to eq 1
expect(blobs.first.name).to eq 'LICENSE'
end
end
end
end

View File

@ -159,7 +159,7 @@ RSpec.describe SnippetPresenter do
let(:snippet) { create(:snippet, :repository, author: user) }
it 'returns repository first blob' do
expect(subject).to eq snippet.blobs.first
expect(subject.name).to eq snippet.blobs.first.name
end
end
end

View File

@ -5,7 +5,9 @@ require 'spec_helper'
RSpec.describe API::Lint do
describe 'POST /ci/lint' do
context 'when signup settings are disabled' do
Gitlab::CurrentSettings.signup_enabled = false
before do
Gitlab::CurrentSettings.signup_enabled = false
end
context 'when unauthenticated' do
it 'returns authentication error' do
@ -16,22 +18,25 @@ RSpec.describe API::Lint do
end
context 'when authenticated' do
it 'returns unauthorized error' do
post api('/ci/lint'), params: { content: 'content' }
let_it_be(:api_user) { create(:user) }
it 'returns authorized' do
post api('/ci/lint', api_user), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'when signup settings are enabled' do
Gitlab::CurrentSettings.signup_enabled = true
before do
Gitlab::CurrentSettings.signup_enabled = true
end
context 'when unauthenticated' do
it 'returns authentication error' do
it 'returns authorized success' do
post api('/ci/lint'), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response).to have_gitlab_http_status(:ok)
end
end

View File

@ -249,9 +249,6 @@ RSpec.configure do |config|
unstub_all_feature_flags
end
# Enable Marginalia feature for all specs in the test suite.
Gitlab::Marginalia.enabled = true
# Stub these calls due to being expensive operations
# It can be reenabled for specific tests via:
#