Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-13 12:09:03 +00:00
parent c21064ccfd
commit 7f59234892
44 changed files with 242 additions and 360 deletions

View File

@ -164,7 +164,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
@ -186,7 +186,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
@ -210,7 +210,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"
@ -231,7 +231,7 @@
- "vendor/assets/**/*"
- ".gitlab/ci/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,scss-lint}.yml"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo,rubocop_manual_todo,scss-lint}.yml"
- "*_VERSION"
- "Gemfile{,.lock}"
- "Rakefile"

View File

@ -43,8 +43,6 @@ Graphql/IDType:
Graphql/ResolverType:
Exclude:
- 'app/graphql/resolvers/assigned_merge_requests_resolver.rb'
- 'app/graphql/resolvers/authored_merge_requests_resolver.rb'
- 'app/graphql/resolvers/base_resolver.rb'
- 'app/graphql/resolvers/ci/pipeline_stages_resolver.rb'
- 'app/graphql/resolvers/commit_pipelines_resolver.rb'
@ -61,7 +59,6 @@ Graphql/ResolverType:
- 'app/graphql/resolvers/project_pipelines_resolver.rb'
- 'app/graphql/resolvers/projects/snippets_resolver.rb'
- 'app/graphql/resolvers/snippets_resolver.rb'
- 'app/graphql/resolvers/user_merge_requests_resolver.rb'
- 'app/graphql/resolvers/users/group_count_resolver.rb'
- 'app/graphql/resolvers/users/snippets_resolver.rb'
- 'ee/app/graphql/resolvers/ci/jobs_resolver.rb'

View File

@ -1 +1 @@
8.53.0
8.54.0

View File

@ -84,6 +84,14 @@ export default {
}
return {};
},
showDiscussions() {
return typeof this.issuable.userDiscussionsCount === 'number';
},
showIssuableMeta() {
return Boolean(
this.hasSlotContents('status') || this.showDiscussions || this.issuable.assignees,
);
},
},
methods: {
hasSlotContents(slotName) {
@ -166,6 +174,7 @@ export default {
<span class="author">{{ author.name }}</span>
</gl-link>
</span>
<slot name="timeframe"></slot>
&nbsp;
<gl-label
v-for="(label, index) in labels"
@ -181,10 +190,26 @@ export default {
</div>
</div>
<div class="issuable-meta">
<ul v-if="hasSlotContents('status') || issuable.assignees" class="controls">
<ul v-if="showIssuableMeta" class="controls">
<li v-if="hasSlotContents('status')" class="issuable-status">
<slot name="status"></slot>
</li>
<li
v-if="showDiscussions"
data-testid="issuable-discussions"
class="issuable-comments gl-display-none gl-display-sm-block"
>
<gl-link
v-gl-tooltip:tooltipcontainer.top
:title="__('Comments')"
:href="`${issuable.webUrl}#notes`"
:class="{ 'no-comments': !issuable.userDiscussionsCount }"
class="gl-reset-color!"
>
<gl-icon name="comments" />
{{ issuable.userDiscussionsCount }}
</gl-link>
</li>
<li v-if="assignees.length" class="gl-display-flex">
<issuable-assignees
:assignees="issuable.assignees"

View File

@ -269,6 +269,9 @@ export default {
<template #author>
<slot name="author" :author="issuable.author"></slot>
</template>
<template #timeframe>
<slot name="timeframe" :issuable="issuable"></slot>
</template>
<template #status>
<slot name="status" :issuable="issuable"></slot>
</template>

View File

@ -24,5 +24,5 @@ export default {
</script>
<template>
<gl-link :href="path" :class="cssClass" class="btn">{{ text }}</gl-link>
<gl-link :href="path" :class="cssClass" class="btn gl-button">{{ text }}</gl-link>
</template>

View File

@ -61,7 +61,6 @@ export default {
<gl-dropdown
v-if="hasMultipleActions"
v-gl-tooltip="selectedAction.tooltip"
class="gl-button-deprecated-adapter"
:text="selectedAction.text"
:split-href="selectedAction.href"
:variant="variant"

View File

@ -75,6 +75,11 @@ export default {
type: String,
required: true,
},
suggestionsListClass: {
type: String,
required: false,
default: '',
},
},
data() {
let selectedSortOption = this.sortOptions[0]?.sortDirection?.descending;
@ -315,6 +320,7 @@ export default {
:placeholder="searchInputPlaceholder"
:available-tokens="tokens"
:history-items="filteredRecentSearches"
:suggestions-list-class="suggestionsListClass"
class="flex-grow-1"
@history-item-selected="handleHistoryItemSelected"
@clear-history="handleClearHistory"

View File

@ -169,7 +169,7 @@ export default {
</script>
<template>
<div class="d-inline-block gl-ml-3">
<div class="gl-sm-ml-3">
<actions-button
:actions="actions"
:selected-key="selection"

View File

@ -62,7 +62,7 @@
.tree-controls {
margin-bottom: 10px;
.btn,
.btn:not(.dropdown-toggle-split),
.dropdown,
.btn-group {
width: 100%;

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true
module Resolvers
class AssignedMergeRequestsResolver < UserMergeRequestsResolver
class AssignedMergeRequestsResolver < UserMergeRequestsResolverBase
type ::Types::MergeRequestType.connection_type, null: true
accept_author
def user_role

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true
module Resolvers
class AuthoredMergeRequestsResolver < UserMergeRequestsResolver
class AuthoredMergeRequestsResolver < UserMergeRequestsResolverBase
type ::Types::MergeRequestType.connection_type, null: true
accept_assignee
def user_role

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module Resolvers
class UserMergeRequestsResolver < MergeRequestsResolver
class UserMergeRequestsResolverBase < MergeRequestsResolver
include ResolvesProject
argument :project_path, GraphQL::STRING_TYPE,

View File

@ -46,10 +46,10 @@ module Types
resolver: Resolvers::UserStarredProjectsResolver
# Merge request field: MRs can be either authored or assigned:
field :authored_merge_requests, Types::MergeRequestType.connection_type, null: true,
field :authored_merge_requests,
resolver: Resolvers::AuthoredMergeRequestsResolver,
description: 'Merge Requests authored by the user'
field :assigned_merge_requests, Types::MergeRequestType.connection_type, null: true,
field :assigned_merge_requests,
resolver: Resolvers::AssignedMergeRequestsResolver,
description: 'Merge Requests assigned to the user'

View File

@ -54,9 +54,6 @@ module Ci
raise "Unknown store type: #{store}" unless STORE_TYPES.key?(store)
# Can't memoize this because the feature flag may alter this
return fog_store_class.new if store == :fog
STORE_TYPES[store].new
end
@ -86,14 +83,6 @@ module Ci
def metadata_attributes
attribute_names - %w[raw_data]
end
def fog_store_class
if Feature.enabled?(:ci_trace_new_fog_store, default_enabled: true)
Ci::BuildTraceChunks::Fog
else
Ci::BuildTraceChunks::LegacyFog
end
end
end
def data

View File

@ -1,77 +0,0 @@
# frozen_string_literal: true
module Ci
module BuildTraceChunks
class LegacyFog
def available?
object_store.enabled
end
def data(model)
connection.get_object(bucket_name, key(model))[:body]
rescue Excon::Error::NotFound
# If the object does not exist in the object storage, this method returns nil.
end
def set_data(model, new_data)
connection.put_object(bucket_name, key(model), new_data)
end
def append_data(model, new_data, offset)
if offset > 0
truncated_data = data(model).to_s.byteslice(0, offset)
new_data = truncated_data + new_data
end
set_data(model, new_data)
new_data.bytesize
end
def size(model)
data(model).to_s.bytesize
end
def delete_data(model)
delete_keys([[model.build_id, model.chunk_index]])
end
def keys(relation)
return [] unless available?
relation.pluck(:build_id, :chunk_index)
end
def delete_keys(keys)
keys.each do |key|
connection.delete_object(bucket_name, key_raw(*key))
end
end
private
def key(model)
key_raw(model.build_id, model.chunk_index)
end
def key_raw(build_id, chunk_index)
"tmp/builds/#{build_id.to_i}/chunks/#{chunk_index.to_i}.log"
end
def bucket_name
return unless available?
object_store.remote_directory
end
def connection
return unless available?
@connection ||= ::Fog::Storage.new(object_store.connection.to_hash.deep_symbolize_keys)
end
def object_store
Gitlab.config.artifacts.object_store
end
end
end
end

View File

@ -2,5 +2,5 @@
- add_page_specific_style 'page_bundles/signup'
.signup-page
= render 'devise/shared/signup_box'
= render 'devise/shared/signup_box', url: registration_path(resource_name), button_text: _('Register'), show_omniauth_providers: omniauth_enabled? && button_based_providers_enabled?
= render 'devise/shared/sign_in_link'

View File

@ -2,37 +2,36 @@
- max_username_length = 255
- min_username_length = 2
.gl-mb-3.gl-p-4.gl-border-gray-100.gl-border-1.gl-border-solid.gl-rounded-base
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
= form_for(resource, as: "new_#{resource_name}", url: url, html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive' }) do |f|
.devise-errors
= render "devise/shared/error_messages", resource: resource
= render 'devise/shared/error_messages', resource: resource
- if Feature.enabled?(:invisible_captcha)
= invisible_captcha
.name.form-row
.col.form-group
= f.label :first_name, _('First name'), for: 'new_user_first_name', class: 'label-bold'
= f.text_field :first_name, class: 'form-control top js-block-emoji js-validate-length', :data => { :max_length => max_first_name_length, :max_length_message => _("First name is too long (maximum is %{max_length} characters).") % { max_length: max_first_name_length }, :qa_selector => 'new_user_first_name_field' }, required: true, title: _("This field is required.")
= f.text_field :first_name, class: 'form-control top js-block-emoji js-validate-length', :data => { :max_length => max_first_name_length, :max_length_message => s_('SignUp|First name is too long (maximum is %{max_length} characters).') % { max_length: max_first_name_length }, :qa_selector => 'new_user_first_name_field' }, required: true, title: _('This field is required.')
.col.form-group
= f.label :last_name, _('Last name'), for: 'new_user_last_name', class: 'label-bold'
= f.text_field :last_name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_last_name_length, :max_length_message => _("Last name is too long (maximum is %{max_length} characters).") % { max_length: max_last_name_length }, :qa_selector => 'new_user_last_name_field' }, required: true, title: _("This field is required.")
= f.text_field :last_name, class: 'form-control top js-block-emoji js-validate-length', :data => { :max_length => max_last_name_length, :max_length_message => s_('SignUp|Last name is too long (maximum is %{max_length} characters).') % { max_length: max_last_name_length }, :qa_selector => 'new_user_last_name_field' }, required: true, title: _('This field is required.')
.username.form-group
= f.label :username, class: 'label-bold'
= f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :min_length => min_username_length, :min_length_message => s_("SignUp|Username is too short (minimum is %{min_length} characters).") % { min_length: min_username_length }, :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
= f.text_field :username, class: 'form-control middle js-block-emoji js-validate-length js-validate-username', :data => { :api_path => suggestion_path, :min_length => min_username_length, :min_length_message => s_('SignUp|Username is too short (minimum is %{min_length} characters).') % { min_length: min_username_length }, :max_length => max_username_length, :max_length_message => s_('SignUp|Username is too long (maximum is %{max_length} characters).') % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _('Please create a username with only alphanumeric characters.')
%p.validation-error.gl-text-red-500.gl-field-error-ignore.gl-mt-2.field-validation.hide= _('Username is already taken.')
%p.validation-success.gl-text-green-600.gl-field-error-ignore.gl-mt-2.field-validation.hide= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.gl-mt-2.field-validation.hide= _('Checking username availability...')
.form-group
= f.label :email, class: 'label-bold'
= f.email_field :email, value: @invite_email, class: "form-control middle", data: { qa_selector: 'new_user_email_field' }, required: true, title: _("Please provide a valid email address.")
= f.email_field :email, value: @invite_email, class: 'form-control middle', data: { qa_selector: 'new_user_email_field' }, required: true, title: _('Please provide a valid email address.')
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
= f.password_field :password, class: "form-control bottom", data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
%p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length }
= f.password_field :password, class: 'form-control bottom', data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: s_('SignUp|Minimum length is %{minimum_password_length} characters.') % { minimum_password_length: @minimum_password_length }
%p.gl-field-hint.text-secondary= s_('SignUp|Minimum length is %{minimum_password_length} characters.') % { minimum_password_length: @minimum_password_length }
%div
- if show_recaptcha_sign_up?
= recaptcha_tags
.submit-container
= f.submit _("Register"), class: "btn gl-button btn-success", data: { qa_selector: 'new_user_register_button' }
= f.submit button_text, class: 'btn gl-button btn-success', data: { qa_selector: 'new_user_register_button' }
= render 'devise/shared/terms_of_service_notice'
- if omniauth_enabled? && button_based_providers_enabled?
- if show_omniauth_providers
= render 'devise/shared/signup_omniauth_providers'

View File

@ -1,2 +1,2 @@
= link_to project_find_file_path(@project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do
= link_to project_find_file_path(@project, @ref), class: 'gl-button btn shortcuts-find-file', rel: 'nofollow' do
= _('Find file')

View File

@ -2,7 +2,7 @@
- dropdown_class = local_assigns.fetch(:dropdown_class, '')
.git-clone-holder.js-git-clone-holder
%a#clone-dropdown.btn.btn-primary.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%a#clone-dropdown.gl-button.btn.btn-primary.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span.gl-mr-2.js-clone-dropdown-label
= _('Clone')
= sprite_icon("chevron-down", css_class: "icon")

View File

@ -3,7 +3,7 @@
- if !project.empty_repo? && can?(current_user, :download_code, project)
- archive_prefix = "#{project.path}-#{ref.tr('/', '-')}"
.project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static', data: { qa_selector: 'download_source_code_button' } }
%button.gl-button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static', data: { qa_selector: 'download_source_code_button' } }
= sprite_icon('download')
%span.sr-only= _('Select Archive Format')
= sprite_icon("chevron-down")

View File

@ -1,2 +1,2 @@
%a.btn.btn-default{ href: xcode_uri_to_repo(@project) }
%a.gl-button.btn.btn-default{ href: xcode_uri_to_repo(@project) }
= _("Open in Xcode")

View File

@ -13,7 +13,7 @@
= render 'shared/web_ide_button', blob: nil
- if show_xcode_link?(@project)
.project-action-button.project-xcode.inline<
.project-action-button.project-xcode<
= render "projects/buttons/xcode_link"
= render 'projects/buttons/download', project: @project, ref: @ref

View File

@ -0,0 +1,5 @@
---
title: 'MR Analytics: Fix chart tooltip covering filter dropdown'
merge_request: 47274
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Update Workhorse version to 8.54.0
merge_request: 47625
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Update button styles in project tree header
merge_request: 47562
author:
type: other

View File

@ -0,0 +1,6 @@
---
title: Make the Merge Train process flow more resilient by always refreshing merge
requests from beginning
merge_request: 46768
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Remove ci_trace_new_fog_store feature flag
merge_request: 47522
author:
type: changed

View File

@ -1,8 +0,0 @@
---
name: ci_always_refresh_merge_requests_from_beginning
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45232
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/268215
milestone: '13.5'
type: development
group: group::continuous integration
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: ci_trace_new_fog_store
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46209
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/273405
milestone: '13.6'
type: development
group: group::testing
default_enabled: true

View File

@ -33,6 +33,7 @@ Example response:
[
{
"id" : 1,
"name": "board1",
"project": {
"id": 5,
"name": "Diaspora Project Site",

View File

@ -52,8 +52,23 @@ and blocked, go to **Admin Area > Overview > Dashboard** and select **Users stat
in the **Users** section. For more details, see
[Users statistics](../../user/admin_area/index.md#users-statistics).
NOTE: **Note:**
If you have LDAP integration enabled, anyone in the configured domain can sign up for a GitLab account. This can result in an unexpected bill at time of renewal. Consider [disabling new signups](../../user/admin_area/settings/sign_up_restrictions.md) and managing new users manually instead.
### Tips for managing users and subscription seats
Managing the number of users against the number of subscription seats can be a challenge:
- If LDAP integration is enabled, anyone in the configured domain can sign up for a GitLab account.
This can result in an unexpected bill at time of renewal.
- If sign-up is enabled on your instance, anyone who can access the instance can sign up for an
account.
GitLab has several features which can help you manage the number of users:
- Enable the [**Require administrator approval for new sign ups**](../../user/admin_area/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups)
option.
- Enable the [User cap](../../user/admin_area/settings/sign_up_restrictions.md#user-cap)
option. **Available in GitLab 13.6 and later**.
- [Disable new sign-ups](../../user/admin_area/settings/sign_up_restrictions.md), and instead manage new
users manually.
## Obtain a subscription

View File

@ -46,6 +46,14 @@ To enforce confirmation of the email address used for new sign ups:
1. Go to **Admin Area > Settings > General** and expand **Sign-up restrictions**.
1. Select the **Enable email restrictions for sign ups** checkbox, then select **Save changes**.
## User cap **(CORE ONLY)**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4315) in GitLab 13.6.
When the number of users reaches the user cap, any user who is added or requests access must be
[approved](../approving_users.md#approving-a-user) by an administrator before they can start using
their account.
## Soft email confirmation
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2.

View File

@ -543,6 +543,12 @@ msgstr ""
msgid "%{issuesSize} with a limit of %{maxIssueCount}"
msgstr ""
msgid "%{labelStart}Actual response:%{labelEnd} %{headers}"
msgstr ""
msgid "%{labelStart}Assert:%{labelEnd} %{assertion}"
msgstr ""
msgid "%{labelStart}Class:%{labelEnd} %{class}"
msgstr ""
@ -558,9 +564,6 @@ msgstr ""
msgid "%{labelStart}File:%{labelEnd} %{file}"
msgstr ""
msgid "%{labelStart}Headers:%{labelEnd} %{headers}"
msgstr ""
msgid "%{labelStart}Image:%{labelEnd} %{image}"
msgstr ""
@ -576,13 +579,13 @@ msgstr ""
msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
msgstr ""
msgid "%{labelStart}Sent request:%{labelEnd} %{headers}"
msgstr ""
msgid "%{labelStart}Severity:%{labelEnd} %{severity}"
msgstr ""
msgid "%{labelStart}Status:%{labelEnd} %{status}"
msgstr ""
msgid "%{labelStart}URL:%{labelEnd} %{url}"
msgid "%{labelStart}Unmodified response:%{labelEnd} %{headers}"
msgstr ""
msgid "%{label_for_message} unavailable"
@ -9457,9 +9460,33 @@ msgstr ""
msgid "DevopsAdoption|Add new segment"
msgstr ""
msgid "DevopsAdoption|Approvals"
msgstr ""
msgid "DevopsAdoption|Deploys"
msgstr ""
msgid "DevopsAdoption|DevOps adoption uses segments to track adoption across key features. Segments are a way to track multiple related projects and groups at once. For example, you could create a segment for the engineering department or a particular product team."
msgstr ""
msgid "DevopsAdoption|Issues"
msgstr ""
msgid "DevopsAdoption|MRs"
msgstr ""
msgid "DevopsAdoption|Pipelines"
msgstr ""
msgid "DevopsAdoption|Runners"
msgstr ""
msgid "DevopsAdoption|Scanning"
msgstr ""
msgid "DevopsAdoption|Segment"
msgstr ""
msgid "DevopsReport|Adoption"
msgstr ""
@ -11950,9 +11977,6 @@ msgstr ""
msgid "First name"
msgstr ""
msgid "First name is too long (maximum is %{max_length} characters)."
msgstr ""
msgid "First seen"
msgstr ""
@ -15697,9 +15721,6 @@ msgstr ""
msgid "Last name"
msgstr ""
msgid "Last name is too long (maximum is %{max_length} characters)."
msgstr ""
msgid "Last reply by"
msgstr ""
@ -17572,12 +17593,6 @@ msgstr ""
msgid "Minimum interval in days"
msgstr ""
msgid "Minimum length is %{minimum_password_length} characters"
msgstr ""
msgid "Minimum length is %{minimum_password_length} characters."
msgstr ""
msgid "Minimum password length (number of characters)"
msgstr ""
@ -22414,9 +22429,6 @@ msgstr ""
msgid "Remediations"
msgstr ""
msgid "Remember me"
msgstr ""
msgid "Remind later"
msgstr ""
@ -24984,10 +24996,13 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
msgid "SignUp|First Name is too long (maximum is %{max_length} characters)."
msgid "SignUp|First name is too long (maximum is %{max_length} characters)."
msgstr ""
msgid "SignUp|Last Name is too long (maximum is %{max_length} characters)."
msgid "SignUp|Last name is too long (maximum is %{max_length} characters)."
msgstr ""
msgid "SignUp|Minimum length is %{minimum_password_length} characters."
msgstr ""
msgid "SignUp|Username is too long (maximum is %{max_length} characters)."
@ -30051,6 +30066,12 @@ msgstr ""
msgid "Vulnerability|Activity"
msgstr ""
msgid "Vulnerability|Actual received response is the one received when this fault was detected"
msgstr ""
msgid "Vulnerability|Additional Info"
msgstr ""
msgid "Vulnerability|Class"
msgstr ""
@ -30099,10 +30120,7 @@ msgstr ""
msgid "Vulnerability|Project"
msgstr ""
msgid "Vulnerability|Request"
msgstr ""
msgid "Vulnerability|Response"
msgid "Vulnerability|Request/Response"
msgstr ""
msgid "Vulnerability|Scanner"
@ -30117,6 +30135,9 @@ msgstr ""
msgid "Vulnerability|Status"
msgstr ""
msgid "Vulnerability|The unmodified response is the original response that had no mutations done to the request"
msgstr ""
msgid "Wait for the file to load to copy its contents"
msgstr ""
@ -31627,6 +31648,9 @@ msgstr ""
msgid "ciReport|: Loading resulted in an error"
msgstr ""
msgid "ciReport|API Fuzzing"
msgstr ""
msgid "ciReport|All projects"
msgstr ""

View File

@ -44,7 +44,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.175.0",
"@gitlab/ui": "23.6.1",
"@gitlab/ui": "23.8.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-3",
"@rails/ujs": "^6.0.3-2",

View File

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlLabel, GlFormCheckbox } from '@gitlab/ui';
import { GlLink, GlLabel, GlIcon, GlFormCheckbox } from '@gitlab/ui';
import IssuableItem from '~/issuable_list/components/issuable_item.vue';
import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
@ -12,6 +12,7 @@ const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots
issuableSymbol,
issuable,
enableLabelPermalinks: true,
showDiscussions: true,
showCheckbox: false,
},
slots,
@ -142,6 +143,31 @@ describe('IssuableItem', () => {
expect(wrapper.vm.updatedAt).toContain('ago');
});
});
describe('showDiscussions', () => {
it.each`
userDiscussionsCount | returnValue
${0} | ${true}
${1} | ${true}
${undefined} | ${false}
${null} | ${false}
`(
'returns $returnValue when issuable.userDiscussionsCount is $userDiscussionsCount',
({ userDiscussionsCount, returnValue }) => {
const wrapperWithDiscussions = createComponent({
issuableSymbol: '#',
issuable: {
...mockIssuable,
userDiscussionsCount,
},
});
expect(wrapperWithDiscussions.vm.showDiscussions).toBe(returnValue);
wrapperWithDiscussions.destroy();
},
);
});
});
describe('methods', () => {
@ -299,6 +325,24 @@ describe('IssuableItem', () => {
wrapperWithAuthorSlot.destroy();
});
it('renders timeframe via slot', () => {
const wrapperWithTimeframeSlot = createComponent({
issuableSymbol: '#',
issuable: mockIssuable,
slots: {
timeframe: `
<b class="js-timeframe">Jan 1, 2020 - Mar 31, 2020</b>
`,
},
});
const timeframeEl = wrapperWithTimeframeSlot.find('.js-timeframe');
expect(timeframeEl.exists()).toBe(true);
expect(timeframeEl.text()).toBe('Jan 1, 2020 - Mar 31, 2020');
wrapperWithTimeframeSlot.destroy();
});
it('renders gl-label component for each label present within `issuable` prop', () => {
const labelsEl = wrapper.findAll(GlLabel);
@ -332,6 +376,18 @@ describe('IssuableItem', () => {
wrapperWithStatusSlot.destroy();
});
it('renders discussions count', () => {
const discussionsEl = wrapper.find('[data-testid="issuable-discussions"]');
expect(discussionsEl.exists()).toBe(true);
expect(discussionsEl.find(GlLink).attributes()).toMatchObject({
title: 'Comments',
href: `${mockIssuable.webUrl}#notes`,
});
expect(discussionsEl.find(GlIcon).props('name')).toBe('comments');
expect(discussionsEl.find(GlLink).text()).toContain('2');
});
it('renders issuable-assignees component', () => {
const assigneesEl = wrapper.find(IssuableAssignees);

View File

@ -52,6 +52,7 @@ export const mockIssuable = {
nodes: mockLabels,
},
assignees: [mockAuthor],
userDiscussionsCount: 2,
};
export const mockIssuables = [

View File

@ -45,6 +45,7 @@ function createComponent(options = {}) {
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: 'custom-class',
},
stubs,
});

View File

@ -45,6 +45,7 @@ function createComponent(options = {}) {
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: 'custom-class',
},
stubs,
});

View File

@ -50,6 +50,7 @@ function createComponent(options = {}) {
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: 'custom-class',
},
stubs,
});

View File

@ -48,6 +48,7 @@ function createComponent(options = {}) {
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: 'custom-class',
},
stubs,
});

View File

@ -135,30 +135,14 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when data_store is fog' do
let(:data_store) { :fog }
context 'when legacy Fog is enabled' do
before do
stub_feature_flags(ci_trace_new_fog_store: false)
build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
end
it { is_expected.to eq('Sample data in fog') }
it 'returns a LegacyFog store' do
expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::LegacyFog)
end
before do
build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
end
context 'when new Fog is enabled' do
before do
stub_feature_flags(ci_trace_new_fog_store: true)
build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
end
it { is_expected.to eq('Sample data in fog') }
it { is_expected.to eq('Sample data in fog') }
it 'returns a new Fog store' do
expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::Fog)
end
it 'returns a new Fog store' do
expect(described_class.get_store_class(data_store)).to be_a(Ci::BuildTraceChunks::Fog)
end
end
end

View File

@ -1,164 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::BuildTraceChunks::LegacyFog do
let(:data_store) { described_class.new }
before do
stub_artifacts_object_storage
end
describe '#available?' do
subject { data_store.available? }
context 'when object storage is enabled' do
it { is_expected.to be_truthy }
end
context 'when object storage is disabled' do
before do
stub_artifacts_object_storage(enabled: false)
end
it { is_expected.to be_falsy }
end
end
describe '#data' do
subject { data_store.data(model) }
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
it 'returns the data' do
is_expected.to eq('sample data in fog')
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'returns nil' do
expect(data_store.data(model)).to be_nil
end
end
end
describe '#set_data' do
let(:new_data) { 'abc123' }
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
it 'overwrites data' do
expect(data_store.data(model)).to eq('sample data in fog')
data_store.set_data(model, new_data)
expect(data_store.data(model)).to eq new_data
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'sets new data' do
expect(data_store.data(model)).to be_nil
data_store.set_data(model, new_data)
expect(data_store.data(model)).to eq new_data
end
end
end
describe '#delete_data' do
subject { data_store.delete_data(model) }
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'sample data in fog') }
it 'deletes data' do
expect(data_store.data(model)).to eq('sample data in fog')
subject
expect(data_store.data(model)).to be_nil
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'does nothing' do
expect(data_store.data(model)).to be_nil
subject
expect(data_store.data(model)).to be_nil
end
end
end
describe '#size' do
context 'when data exists' do
let(:model) { create(:ci_build_trace_chunk, :fog_with_data, initial_data: 'üabcd') }
it 'returns data bytesize correctly' do
expect(data_store.size(model)).to eq 6
end
end
context 'when data does not exist' do
let(:model) { create(:ci_build_trace_chunk, :fog_without_data) }
it 'returns zero' do
expect(data_store.size(model)).to be_zero
end
end
end
describe '#keys' do
subject { data_store.keys(relation) }
let(:build) { create(:ci_build) }
let(:relation) { build.trace_chunks }
before do
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
end
it 'returns keys' do
is_expected.to eq([[build.id, 0], [build.id, 1]])
end
end
describe '#delete_keys' do
subject { data_store.delete_keys(keys) }
let(:build) { create(:ci_build) }
let(:relation) { build.trace_chunks }
let(:keys) { data_store.keys(relation) }
before do
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 0, build: build)
create(:ci_build_trace_chunk, :fog_with_data, chunk_index: 1, build: build)
end
it 'deletes multiple data' do
::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body]).to be_present
expect(connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body]).to be_present
end
subject
::Fog::Storage.new(JobArtifactUploader.object_store_credentials).tap do |connection|
expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/0.log")[:body] }.to raise_error(Excon::Error::NotFound)
expect { connection.get_object('artifacts', "tmp/builds/#{build.id}/chunks/1.log")[:body] }.to raise_error(Excon::Error::NotFound)
end
end
end
end

View File

@ -866,10 +866,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.175.0.tgz#734f341784af1cd1d62d160a17bcdfb61ff7b04d"
integrity sha512-gXpc87TGSXIzfAr4QER1Qw1v3P47pBO6BXkma52blgwXVmcFNe3nhQzqsqt66wKNzrIrk3lAcB4GUyPHbPVXpg==
"@gitlab/ui@23.6.1":
version "23.6.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-23.6.1.tgz#3b94945166c756a26c9598501ebef0997a3961e7"
integrity sha512-jOEhJUTQmdsV4XEHqY+ray+GHAMa35CsddPFtKjiD8y1YiJFzXES7tVqQr/hXzSAWbBWfxSO1Rvp55+UnCGz5g==
"@gitlab/ui@23.8.0":
version "23.8.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-23.8.0.tgz#fe1807877c191e9e38b476d2cdfc4261facbb36b"
integrity sha512-5CF2jU/d5EX5a1qLHzJujYOTaCze1ZvE9ovK1TbhJ7Va1O0SKB/N53XT8iPOb4MwOzj/zBWDOdsIs+xXxCeOcg==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"