Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
fd27e4f95b
commit
739467f1fa
32 changed files with 574 additions and 253 deletions
|
@ -0,0 +1,120 @@
|
|||
<script>
|
||||
import { GlPopover, GlSprintf, GlButton, GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlPopover,
|
||||
GlSprintf,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlIcon,
|
||||
UserCalloutDismisser,
|
||||
},
|
||||
inject: {
|
||||
message: {
|
||||
default: '',
|
||||
},
|
||||
observerElSelector: {
|
||||
default: '',
|
||||
},
|
||||
observerElToggledClass: {
|
||||
default: '',
|
||||
},
|
||||
featureName: {
|
||||
default: '',
|
||||
},
|
||||
popoverTarget: {
|
||||
default: '',
|
||||
},
|
||||
showAttentionIcon: {
|
||||
default: false,
|
||||
},
|
||||
delay: {
|
||||
default: 0,
|
||||
},
|
||||
popoverCssClass: {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPopover: false,
|
||||
popoverPlacement: this.popoverPosition(),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.observeEl = document.querySelector(this.observerElSelector);
|
||||
this.observer = new MutationObserver(this.callback);
|
||||
this.observer.observe(this.observeEl, {
|
||||
attributes: true,
|
||||
});
|
||||
this.callback();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
this.popoverPlacement = this.popoverPosition();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.observer.disconnect();
|
||||
},
|
||||
methods: {
|
||||
callback() {
|
||||
if (this.showPopover) {
|
||||
this.$root.$emit('bv::hide::popover');
|
||||
}
|
||||
|
||||
setTimeout(() => this.toggleShowPopover(), this.delay);
|
||||
},
|
||||
toggleShowPopover() {
|
||||
this.showPopover = this.observeEl.classList.contains(this.observerElToggledClass);
|
||||
},
|
||||
getPopoverTarget() {
|
||||
return document.querySelector(this.popoverTarget);
|
||||
},
|
||||
popoverPosition() {
|
||||
if (bp.isDesktop()) {
|
||||
return 'left';
|
||||
}
|
||||
|
||||
return 'bottom';
|
||||
},
|
||||
},
|
||||
docsPage: helpPagePath('development/code_review.html'),
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<user-callout-dismisser :feature-name="featureName">
|
||||
<template #default="{ shouldShowCallout, dismiss }">
|
||||
<gl-popover
|
||||
v-if="shouldShowCallout"
|
||||
:show-close-button="false"
|
||||
:target="() => getPopoverTarget()"
|
||||
:show="showPopover"
|
||||
:delay="0"
|
||||
triggers="manual"
|
||||
:placement="popoverPlacement"
|
||||
boundary="window"
|
||||
no-fade
|
||||
:css-classes="[popoverCssClass]"
|
||||
>
|
||||
<p v-for="(m, index) in message" :key="index" class="gl-mb-5">
|
||||
<gl-sprintf :message="m">
|
||||
<template #strong="{ content }">
|
||||
<strong><gl-icon v-if="showAttentionIcon" name="attention" /> {{ content }}</strong>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
<gl-button size="small" variant="confirm" class="gl-mr-5" @click.prevent.stop="dismiss">
|
||||
{{ __('Got it!') }}
|
||||
</gl-button>
|
||||
<gl-link :href="$options.docsPage" target="_blank">{{ __('Learn more') }}</gl-link>
|
||||
</div>
|
||||
</gl-popover>
|
||||
</template>
|
||||
</user-callout-dismisser>
|
||||
</template>
|
73
app/assets/javascripts/attention_requests/index.js
Normal file
73
app/assets/javascripts/attention_requests/index.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { __ } from '~/locale';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import NavigationPopover from './components/navigation_popover.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
export const initTopNavPopover = () => {
|
||||
const el = document.getElementById('js-need-attention-nav-onboarding');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
apolloProvider,
|
||||
provide: {
|
||||
observerElSelector: '.user-counter.dropdown',
|
||||
observerElToggledClass: 'show',
|
||||
message: [
|
||||
__(
|
||||
'%{strongStart}Need your attention%{strongEnd} are the merge requests that need your help to move forward, as an assignee or reviewer.',
|
||||
),
|
||||
],
|
||||
featureName: 'attention_requests_top_nav',
|
||||
popoverTarget: '#js-need-attention-nav',
|
||||
},
|
||||
render(h) {
|
||||
return h(NavigationPopover);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const initSideNavPopover = () => {
|
||||
const el = document.getElementById('js-need-attention-sidebar-onboarding');
|
||||
|
||||
if (!el) return;
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
apolloProvider,
|
||||
provide: {
|
||||
observerElSelector: '.js-right-sidebar',
|
||||
observerElToggledClass: 'right-sidebar-expanded',
|
||||
message: [
|
||||
__(
|
||||
'To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request.',
|
||||
),
|
||||
__(
|
||||
'Some actions remove attention requests, like a reviewer approving or anyone merging the merge request.',
|
||||
),
|
||||
],
|
||||
featureName: 'attention_requests_side_nav',
|
||||
popoverTarget: '.js-attention-request-toggle',
|
||||
showAttentionIcon: true,
|
||||
delay: 500,
|
||||
popoverCssClass: 'attention-request-sidebar-popover',
|
||||
},
|
||||
render(h) {
|
||||
return h(NavigationPopover);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default () => {
|
||||
initTopNavPopover();
|
||||
};
|
|
@ -161,6 +161,12 @@ function deferredInitialisation() {
|
|||
|
||||
// Adding a helper class to activate animations only after all is rendered
|
||||
setTimeout(() => $body.addClass('page-initialised'), 1000);
|
||||
|
||||
if (window.gon?.features?.mrAttentionRequests) {
|
||||
import('~/attention_requests')
|
||||
.then((module) => module.default())
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
const $body = $('body');
|
||||
|
|
|
@ -70,7 +70,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<span v-gl-tooltip.left.viewport="tooltipTitle" class="gl-display-inline-block">
|
||||
<span
|
||||
v-gl-tooltip.left.viewport="tooltipTitle"
|
||||
class="gl-display-inline-block js-attention-request-toggle"
|
||||
>
|
||||
<gl-button
|
||||
:loading="loading"
|
||||
:variant="user.attention_requested ? 'warning' : 'default'"
|
||||
|
|
|
@ -3,7 +3,17 @@ import Mediator from './sidebar_mediator';
|
|||
|
||||
export default (store) => {
|
||||
const mediator = new Mediator(getSidebarOptions());
|
||||
mediator.fetch();
|
||||
mediator
|
||||
.fetch()
|
||||
.then(() => {
|
||||
if (window.gon?.features?.mrAttentionRequests) {
|
||||
return import('~/attention_requests');
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.then((module) => module?.initSideNavPopover())
|
||||
.catch(() => {});
|
||||
|
||||
mountSidebar(mediator, store);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
|
||||
import { GlSafeHtmlDirective as SafeHtml, GlLink } from '@gitlab/ui';
|
||||
import { s__, n__ } from '~/locale';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
|
@ -8,6 +8,9 @@ export default {
|
|||
directives: {
|
||||
SafeHtml,
|
||||
},
|
||||
components: {
|
||||
GlLink,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
relatedLinks: {
|
||||
|
@ -37,6 +40,17 @@ export default {
|
|||
|
||||
return n__('mrWidget|Closes issue', 'mrWidget|Closes issues', this.relatedLinks.closingCount);
|
||||
},
|
||||
assignIssueText() {
|
||||
if (this.relatedLinks.unassignedCount > 1) {
|
||||
return s__('mrWidget|Assign yourself to these issues');
|
||||
}
|
||||
return s__('mrWidget|Assign yourself to this issue');
|
||||
},
|
||||
shouldShowAssignToMeLink() {
|
||||
return (
|
||||
this.relatedLinks.unassignedCount && this.relatedLinks.assignToMe && this.showAssignToMe
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -57,10 +71,14 @@ export default {
|
|||
<span v-safe-html="relatedLinks.mentioned"></span>
|
||||
</p>
|
||||
<p
|
||||
v-if="relatedLinks.assignToMe && showAssignToMe"
|
||||
v-if="shouldShowAssignToMeLink"
|
||||
:class="{ 'gl-display-line gl-m-0': glFeatures.restructuredMrWidget }"
|
||||
>
|
||||
<span v-html="relatedLinks.assignToMe /* eslint-disable-line vue/no-v-html */"></span>
|
||||
<span>
|
||||
<gl-link rel="nofollow" data-method="post" :href="relatedLinks.assignToMe">{{
|
||||
assignIssueText
|
||||
}}</gl-link>
|
||||
</span>
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
@ -82,14 +82,16 @@ export default class MergeRequestStore {
|
|||
const { closing } = links;
|
||||
const mentioned = links.mentioned_but_not_closing;
|
||||
const assignToMe = links.assign_to_closing;
|
||||
const unassignedCount = links.assign_to_closing_count;
|
||||
|
||||
if (closing || mentioned || assignToMe) {
|
||||
if (closing || mentioned || unassignedCount) {
|
||||
this.relatedLinks = {
|
||||
closing,
|
||||
mentioned,
|
||||
assignToMe,
|
||||
closingCount: links.closing_count,
|
||||
mentionedCount: links.mentioned_count,
|
||||
unassignedCount: links.assign_to_closing_count,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -757,3 +757,7 @@ $tabs-holder-z-index: 250;
|
|||
background: linear-gradient(to bottom, rgba(#333, 0), rgba(#333, 1));
|
||||
}
|
||||
}
|
||||
|
||||
.attention-request-sidebar-popover {
|
||||
z-index: 999;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,6 @@ class DashboardController < Dashboard::ApplicationController
|
|||
|
||||
urgency :low, [:merge_requests]
|
||||
|
||||
before_action only: [:merge_requests] do
|
||||
push_frontend_feature_flag(:mr_attention_requests, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
def activity
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
|
@ -30,9 +30,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
before_action :set_issuables_index, only: [:index]
|
||||
before_action :authenticate_user!, only: [:assign_related_issues]
|
||||
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
|
||||
before_action only: [:index, :show] do
|
||||
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
before_action only: [:show] do
|
||||
push_frontend_feature_flag(:file_identifier_hash)
|
||||
|
|
|
@ -46,7 +46,9 @@ module Users
|
|||
storage_enforcement_banner_first_enforcement_threshold: 43,
|
||||
storage_enforcement_banner_second_enforcement_threshold: 44,
|
||||
storage_enforcement_banner_third_enforcement_threshold: 45,
|
||||
storage_enforcement_banner_fourth_enforcement_threshold: 46
|
||||
storage_enforcement_banner_fourth_enforcement_threshold: 46,
|
||||
attention_requests_top_nav: 47,
|
||||
attention_requests_side_nav: 48
|
||||
}
|
||||
|
||||
validates :feature_name,
|
||||
|
|
|
@ -149,7 +149,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
|||
)
|
||||
end
|
||||
|
||||
def assign_to_closing_issues_link
|
||||
def assign_to_closing_issues_path
|
||||
assign_related_issues_project_merge_request_path(project, merge_request)
|
||||
end
|
||||
|
||||
def assign_to_closing_issues_count
|
||||
# rubocop: disable CodeReuse/ServiceClass
|
||||
issues = MergeRequests::AssignIssuesService.new(project: project,
|
||||
current_user: current_user,
|
||||
|
@ -157,14 +161,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
|||
merge_request: merge_request,
|
||||
closes_issues: closing_issues
|
||||
}).assignable_issues
|
||||
path = assign_related_issues_project_merge_request_path(project, merge_request)
|
||||
if issues.present?
|
||||
if issues.count > 1
|
||||
link_to _('Assign yourself to these issues'), path, method: :post
|
||||
else
|
||||
link_to _('Assign yourself to this issue'), path, method: :post
|
||||
end
|
||||
end
|
||||
issues.count
|
||||
# rubocop: enable CodeReuse/ServiceClass
|
||||
end
|
||||
|
||||
|
|
|
@ -104,7 +104,11 @@ class MergeRequestWidgetEntity < Grape::Entity
|
|||
# include them if they are explicitly requested on first load.
|
||||
expose :issues_links, if: -> (_, opts) { opts[:issues_links] } do
|
||||
expose :assign_to_closing do |merge_request|
|
||||
presenter(merge_request).assign_to_closing_issues_link
|
||||
presenter(merge_request).assign_to_closing_issues_path
|
||||
end
|
||||
|
||||
expose :assign_to_closing_count do |merge_request|
|
||||
presenter(merge_request).assign_to_closing_issues_count
|
||||
end
|
||||
|
||||
expose :closing do |merge_request|
|
||||
|
|
|
@ -79,7 +79,8 @@
|
|||
%li.dropdown-header
|
||||
= _('Merge requests')
|
||||
- if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
|
||||
%li
|
||||
%li#js-need-attention-nav
|
||||
#js-need-attention-nav-onboarding
|
||||
= link_to attention_requested_mrs_dashboard_path, class: 'gl-display-flex! gl-align-items-center js-prefetch-document' do
|
||||
= _('Need your attention')
|
||||
= gl_badge_tag user_merge_requests_counts[:attention_requested_count], { size: :sm, variant: user_merge_requests_counts[:attention_requested_count] == 0 ? :neutral : :warning }, { class: 'merge-request-badge gl-ml-auto js-attention-count' }
|
||||
|
|
|
@ -92,5 +92,8 @@
|
|||
|
||||
#js-review-bar
|
||||
|
||||
- if Feature.enabled?(:mr_attention_requests, default_enabled: :yaml)
|
||||
#js-need-attention-sidebar-onboarding
|
||||
|
||||
= render 'projects/invite_members_modal', project: @project
|
||||
= render 'shared/web_ide_path'
|
||||
|
|
|
@ -21,7 +21,7 @@ If you use a cloud-managed service, or provide your own PostgreSQL instance:
|
|||
1. If you are using a cloud-managed service, you may need to grant additional
|
||||
roles to your `gitlab` user:
|
||||
- Amazon RDS requires the [`rds_superuser`](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.html#Appendix.PostgreSQL.CommonDBATasks.Roles) role.
|
||||
- Azure Database for PostgreSQL requires the [`azure_pg_admin`](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users#how-to-create-additional-admin-users-in-azure-database-for-postgresql) role.
|
||||
- Azure Database for PostgreSQL requires the [`azure_pg_admin`](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users#how-to-create-additional-admin-users-in-azure-database-for-postgresql) role. Azure Database for PostgreSQL - Flexible Server requires [allow-listing extensions before they can be installed](https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions#how-to-use-postgresql-extensions).
|
||||
- Google Cloud SQL requires the [`cloudsqlsuperuser`](https://cloud.google.com/sql/docs/postgres/users#default-users) role.
|
||||
|
||||
This is for the installation of extensions during installation and upgrades. As an alternative,
|
||||
|
|
|
@ -107,6 +107,10 @@ This section is for links to information elsewhere in the GitLab documentation.
|
|||
|
||||
To fix this, see [Backup and restore a non-packaged PostgreSQL database](https://docs.gitlab.com/omnibus/settings/database.html#backup-and-restore-a-non-packaged-postgresql-database).
|
||||
|
||||
- Deploying PostgreSQL on Azure Database for PostgreSQL - Flexible Server may result in an error stating `extension "btree_gist" is not allow-listed for "azure_pg_admin" users in Azure Database for PostgreSQL`
|
||||
|
||||
To resolve the above error, [allow-list the extension](https://docs.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-extensions#how-to-use-postgresql-extensions) prior to install.
|
||||
|
||||
## Support topics
|
||||
|
||||
### Database deadlocks
|
||||
|
|
|
@ -18994,6 +18994,8 @@ Name of the feature that the callout is for.
|
|||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="usercalloutfeaturenameenumactive_user_count_threshold"></a>`ACTIVE_USER_COUNT_THRESHOLD` | Callout feature name for active_user_count_threshold. |
|
||||
| <a id="usercalloutfeaturenameenumattention_requests_side_nav"></a>`ATTENTION_REQUESTS_SIDE_NAV` | Callout feature name for attention_requests_side_nav. |
|
||||
| <a id="usercalloutfeaturenameenumattention_requests_top_nav"></a>`ATTENTION_REQUESTS_TOP_NAV` | Callout feature name for attention_requests_top_nav. |
|
||||
| <a id="usercalloutfeaturenameenumbuy_pipeline_minutes_notification_dot"></a>`BUY_PIPELINE_MINUTES_NOTIFICATION_DOT` | Callout feature name for buy_pipeline_minutes_notification_dot. |
|
||||
| <a id="usercalloutfeaturenameenumcanary_deployment"></a>`CANARY_DEPLOYMENT` | Callout feature name for canary_deployment. |
|
||||
| <a id="usercalloutfeaturenameenumci_deprecation_warning_for_types_keyword"></a>`CI_DEPRECATION_WARNING_FOR_TYPES_KEYWORD` | Callout feature name for ci_deprecation_warning_for_types_keyword. |
|
||||
|
|
|
@ -4,16 +4,17 @@ group: Distribution
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Spamcheck anti-spam service **(PREMIUM SELF)**
|
||||
# Spamcheck anti-spam service **(FREE SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259) in GitLab 14.8.
|
||||
|
||||
WARNING:
|
||||
Spamcheck is available to all tiers, but only on instances using GitLab Enterprise Edition (EE). For [licensing reasons](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259#note_726605397), it is not included in the GitLab Community Edition (CE) package. You can [migrate from CE to EE](../../../update/package/convert_to_ee.md).
|
||||
|
||||
[Spamcheck](https://gitlab.com/gitlab-org/spamcheck) is an anti-spam engine
|
||||
developed by GitLab originally to combat rising amount of spam in GitLab.com,
|
||||
and later made public to be used in self-managed GitLab instances.
|
||||
|
||||
Spamcheck [contains obfuscated, non-free code](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/6259#note_726605397), which is only available in GitLab Enterprise Edition. You can [migrate from GitLab Community Edition](../../../update/package/convert_to_ee.md), and use the Free tier.
|
||||
|
||||
## Enable Spamcheck
|
||||
|
||||
Spamcheck is only available for package-based installations:
|
||||
|
|
|
@ -107,7 +107,7 @@ attached into the response headers.
|
|||
| `RateLimit-Limit` | `60` | The request quota for the client **each minute**. If the rate limit period set in the admin area is different from 1 minute, the value of this header is adjusted to approximately the nearest 60-minute period. |
|
||||
| `RateLimit-Name` | `throttle_authenticated_web` | Name of the throttle blocking the requests. |
|
||||
| `RateLimit-Observed` | `67` | Number of requests associated to the client in the time window. |
|
||||
| `RateLimit-Remaining` | `0` | Remaining quota in the time window. The result of `RateLimit-Limit` - `RateLimit-Remaining`. |
|
||||
| `RateLimit-Remaining` | `0` | Remaining quota in the time window. The result of `RateLimit-Limit` - `RateLimit-Observed`. |
|
||||
| `RateLimit-Reset` | `1609844400` | [Unix time](https://en.wikipedia.org/wiki/Unix_time)-formatted time when the request quota is reset. |
|
||||
| `RateLimit-ResetTime` | `Tue, 05 Jan 2021 11:00:00 GMT` | [RFC2616](https://tools.ietf.org/html/rfc2616#section-3.3.1)-formatted date and time when the request quota is reset. |
|
||||
| `Retry-After` | `30` | Remaining duration **in seconds** until the quota is reset. This is a [standard HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After). |
|
||||
|
|
|
@ -7,7 +7,7 @@ description: "Getting started with merge requests."
|
|||
|
||||
# Getting started with merge requests **(FREE)**
|
||||
|
||||
A merge request (**MR**) is the basis of GitLab as a code
|
||||
A merge request (**MR**) is the basis of GitLab as a tool for code
|
||||
collaboration and version control.
|
||||
|
||||
When working in a Git-based platform, you can use branching
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
|
@ -6,55 +6,66 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Signing commits with GPG **(FREE)**
|
||||
|
||||
You can use a GPG key to sign Git commits made in a GitLab repository. Signed
|
||||
commits are labeled **Verified** if the identity of the committer can be
|
||||
verified. To verify the identity of a committer, GitLab requires their public
|
||||
GPG key.
|
||||
You can sign the commits you make in a GitLab repository with a
|
||||
GPG ([GNU Privacy Guard](https://gnupg.org/)) key. When you add a cryptographic
|
||||
signature to your commit, you provide extra assurance that a commit
|
||||
originated from you, rather than an impersonator. If GitLab can verify a commit
|
||||
author's identity with a public GPG key, the commit is marked **Verified** in the
|
||||
GitLab UI. You can then configure [push rules](../push_rules.md#enabling-push-rules)
|
||||
for your project to reject individual commits not signed with GPG, or reject all
|
||||
commits from unverified users.
|
||||
|
||||
NOTE:
|
||||
The term GPG is used for all OpenPGP/PGP/GPG related material and
|
||||
GitLab uses the term GPG for all OpenPGP, PGP, and GPG-related material and
|
||||
implementations.
|
||||
|
||||
To view a user's public GPG key, you can:
|
||||
|
||||
- Go to `https://gitlab.example.com/<username>.gpg`.
|
||||
- Select **View public GPG keys** (**{key}**) in the top right of the user's profile.
|
||||
|
||||
GPG verified tags are not supported yet.
|
||||
|
||||
See the [further reading](#further-reading) section for more details on GPG.
|
||||
|
||||
## How GitLab handles GPG
|
||||
|
||||
GitLab uses its own keyring to verify the GPG signature. It does not access any
|
||||
public key server.
|
||||
|
||||
For a commit to be verified by GitLab:
|
||||
For GitLab to consider a commit verified:
|
||||
|
||||
- The committer must have a GPG public/private key pair.
|
||||
- The committer's public key must have been uploaded to their GitLab
|
||||
account.
|
||||
- One of the emails in the GPG key must match a **verified** email address
|
||||
used by the committer in GitLab. This address will be part of the public key.
|
||||
If you want to keep this address private, use the automatically generated
|
||||
- The committer's public key must be uploaded to their GitLab account.
|
||||
- One of the email addresses in the GPG public key must match a **verified** email address
|
||||
used by the committer in GitLab. To keep this address private, use the automatically generated
|
||||
[private commit email address](../../../profile/index.md#use-an-automatically-generated-private-commit-email)
|
||||
GitLab provides in your profile.
|
||||
- The committer's email address must match the verified email address from the
|
||||
GPG key.
|
||||
|
||||
## Generating a GPG key
|
||||
GitLab uses its own keyring to verify the GPG signature. It does not access any
|
||||
public key server.
|
||||
|
||||
If you don't already have a GPG key, the following steps can help you get
|
||||
started:
|
||||
GPG verified tags are not supported.
|
||||
|
||||
1. [Install GPG](https://www.gnupg.org/download/index.html) for your operating system.
|
||||
For more details about GPG, refer to the [related topics list](#related-topics).
|
||||
|
||||
## View a user's public GPG key
|
||||
|
||||
To view a user's public GPG key, you can either:
|
||||
|
||||
- Go to `https://gitlab.example.com/<USERNAME>.gpg`. GitLab displays the GPG key,
|
||||
if the user has configured one, or a blank page for users without a configured GPG key.
|
||||
- Go to the user's profile (such as `https://gitlab.example.com/<USERNAME>`). In the top right
|
||||
of the user's profile, select **View public GPG keys** (**{key}**).
|
||||
|
||||
## Configure commit signing
|
||||
|
||||
To sign commits, you must configure both your local machine and your GitLab account:
|
||||
|
||||
1. [Create a GPG key](#create-a-gpg-key).
|
||||
1. [Add a GPG key to your account](#add-a-gpg-key-to-your-account).
|
||||
1. [Associate your GPG key with Git](#associate-your-gpg-key-with-git).
|
||||
1. [Sign your Git commits](#sign-your-git-commits).
|
||||
|
||||
### Create a GPG key
|
||||
|
||||
If you don't already have a GPG key, create one:
|
||||
|
||||
1. [Install GPG](https://www.gnupg.org/download/) for your operating system.
|
||||
If your operating system has `gpg2` installed, replace `gpg` with `gpg2` in
|
||||
the following commands.
|
||||
1. Generate the private/public key pair with the command appropriate for your version
|
||||
of `gpg`. This command spawns a series of questions:
|
||||
the commands on this page.
|
||||
1. To generate your key pair, run the command appropriate for your version of `gpg`:
|
||||
|
||||
```shell
|
||||
# Use this command for the default version of gpg, including
|
||||
# Use this command for the default version of GPG, including
|
||||
# Gpg4win on Windows, and most macOS versions:
|
||||
gpg --gen-key
|
||||
|
||||
|
@ -62,73 +73,29 @@ started:
|
|||
gpg --full-gen-key
|
||||
```
|
||||
|
||||
1. The first question is which algorithm can be used. Select the kind you want
|
||||
or press <kbd>Enter</kbd> to choose the default (RSA and RSA):
|
||||
|
||||
```plaintext
|
||||
Please select what kind of key you want:
|
||||
(1) RSA and RSA (default)
|
||||
(2) DSA and Elgamal
|
||||
(3) DSA (sign only)
|
||||
(4) RSA (sign only)
|
||||
Your selection? 1
|
||||
```
|
||||
|
||||
1. The next question is key length. We recommend you choose `4096`:
|
||||
|
||||
```plaintext
|
||||
RSA keys may be between 1024 and 4096 bits long.
|
||||
What keysize do you want? (2048) 4096
|
||||
Requested keysize is 4096 bits
|
||||
```
|
||||
|
||||
1. Specify the validity period of your key. This is something
|
||||
subjective, and you can use the default value, which is to never expire:
|
||||
|
||||
```plaintext
|
||||
Please specify how long the key should be valid.
|
||||
0 = key does not expire
|
||||
<n> = key expires in n days
|
||||
<n>w = key expires in n weeks
|
||||
<n>m = key expires in n months
|
||||
<n>y = key expires in n years
|
||||
Key is valid for? (0) 0
|
||||
Key does not expire at all
|
||||
```
|
||||
|
||||
1. Confirm that the answers you gave were correct by typing `y`:
|
||||
|
||||
```plaintext
|
||||
Is this correct? (y/N) y
|
||||
```
|
||||
|
||||
1. Enter your real name, the email address to be associated with this key
|
||||
(should match a verified email address you use in GitLab) and an optional
|
||||
comment (press <kbd>Enter</kbd> to skip):
|
||||
|
||||
```plaintext
|
||||
GnuPG needs to construct a user ID to identify your key.
|
||||
|
||||
Real name: Mr. Robot
|
||||
Email address: <your_email>
|
||||
Comment:
|
||||
You selected this USER-ID:
|
||||
"Mr. Robot <your_email>"
|
||||
|
||||
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
|
||||
```
|
||||
|
||||
1. Pick a strong password when asked and type it twice to confirm.
|
||||
1. Use the following command to list the private GPG key you just created:
|
||||
1. Select the algorithm your key should use, or press <kbd>Enter</kbd> to select
|
||||
the default option, `RSA and RSA`.
|
||||
1. Select the key length, in bits. GitLab recommends 4096-bit keys.
|
||||
1. Specify the validity period of your key. This value is subjective, and the
|
||||
default value is no expiration.
|
||||
1. To confirm your answers, enter `y`.
|
||||
1. Enter your name.
|
||||
1. Enter your email address. It must match a
|
||||
[verified email address](../../../profile/index.md#change-the-email-displayed-on-your-commits)
|
||||
in your GitLab account.
|
||||
1. Optional. Enter a comment to display in parentheses after your name.
|
||||
1. GPG displays the information you've entered so far. Edit the information or press
|
||||
<kbd>O</kbd> (for `Okay`) to continue.
|
||||
1. Enter a strong password, then enter it again to confirm it.
|
||||
1. To list your private GPG key, run this command, replacing
|
||||
`<EMAIL>` with the email address you used when you generated the key:
|
||||
|
||||
```shell
|
||||
gpg --list-secret-keys --keyid-format LONG <your_email>
|
||||
gpg --list-secret-keys --keyid-format LONG <EMAIL>
|
||||
```
|
||||
|
||||
Replace `<your_email>` with the email address you entered above.
|
||||
|
||||
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
|
||||
`30F2B65B9246B6CA`:
|
||||
1. In the output, identify the `sec` line, and copy the GPG key ID. It begins after
|
||||
the `/` character. In this example, the key ID is `30F2B65B9246B6CA`:
|
||||
|
||||
```plaintext
|
||||
sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
|
||||
|
@ -137,49 +104,46 @@ started:
|
|||
ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
|
||||
```
|
||||
|
||||
1. Export the public key of that ID (replace your key ID from the previous step):
|
||||
1. To show the associated public key, run this command, replacing `<ID>` with the
|
||||
GPG key ID from the previous step:
|
||||
|
||||
```shell
|
||||
gpg --armor --export 30F2B65B9246B6CA
|
||||
gpg --armor --export <ID>
|
||||
```
|
||||
|
||||
1. Finally, copy the public key and [add it in your user settings](#adding-a-gpg-key-to-your-account)
|
||||
1. Copy the public key, including the `BEGIN PGP PUBLIC KEY BLOCK` and
|
||||
`END PGP PUBLIC KEY BLOCK` lines. You need this key in the next step.
|
||||
|
||||
## Adding a GPG key to your account
|
||||
### Add a GPG key to your account
|
||||
|
||||
NOTE:
|
||||
After you add a key, you cannot edit it, only remove it. In case the paste
|
||||
didn't work, you have to remove the offending key and re-add it.
|
||||
|
||||
You can add a GPG key in your user settings:
|
||||
To add a GPG key to your user settings:
|
||||
|
||||
1. Sign in to GitLab.
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. On the left sidebar, select **GPG Keys**.
|
||||
1. Paste your _public_ key in the **Key** text box.
|
||||
|
||||
![Paste GPG public key](img/profile_settings_gpg_keys_paste_pub.png)
|
||||
|
||||
1. Select **Add key** to add it to GitLab. You can see the key's fingerprint, the corresponding
|
||||
email address, and creation date.
|
||||
1. On the left sidebar, select **GPG Keys** (**{key}**).
|
||||
1. In **Key**, paste your _public_ key.
|
||||
1. To add the key to your account, select **Add key**. GitLab shows the key's
|
||||
fingerprint, email address, and creation date:
|
||||
|
||||
![GPG key single page](img/profile_settings_gpg_keys_single_key.png)
|
||||
|
||||
## Associating your GPG key with Git
|
||||
After you add a key, you cannot edit it. Instead, remove the offending key and re-add it.
|
||||
|
||||
After you have [created your GPG key](#generating-a-gpg-key) and [added it to
|
||||
your account](#adding-a-gpg-key-to-your-account), it's time to tell Git which
|
||||
key to use.
|
||||
### Associate your GPG key with Git
|
||||
|
||||
1. Use the following command to list the private GPG key you just created:
|
||||
After you [create your GPG key](#create-a-gpg-key) and
|
||||
[add it to your account](#add-a-gpg-key-to-your-account), you must configure Git
|
||||
to use this key:
|
||||
|
||||
1. Run this command to list the private GPG key you just created,
|
||||
replacing `<EMAIL>` with the email address for your key:
|
||||
|
||||
```shell
|
||||
gpg --list-secret-keys --keyid-format LONG <your_email>
|
||||
gpg --list-secret-keys --keyid-format LONG <EMAIL>
|
||||
```
|
||||
|
||||
Replace `<your_email>` with the email address you entered above.
|
||||
|
||||
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
|
||||
1. Copy the GPG private key ID that starts with `sec`. In this example, the private key ID is
|
||||
`30F2B65B9246B6CA`:
|
||||
|
||||
```plaintext
|
||||
|
@ -189,114 +153,103 @@ key to use.
|
|||
ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
|
||||
```
|
||||
|
||||
1. Tell Git to use that key to sign the commits:
|
||||
1. Run this command to configure Git to sign your commits with your key,
|
||||
replacing `<KEY ID>` with your GPG key ID:
|
||||
|
||||
```shell
|
||||
git config --global user.signingkey 30F2B65B9246B6CA
|
||||
git config --global user.signingkey <KEY ID>
|
||||
```
|
||||
|
||||
Replace `30F2B65B9246B6CA` with your GPG key ID.
|
||||
|
||||
1. Optional. If Git is using `gpg` and you get errors like `secret key not available`
|
||||
or `gpg: signing failed: secret key not available`, run the following command to
|
||||
change to `gpg2`:
|
||||
1. Optional. If Git uses `gpg` and you get errors like `secret key not available`
|
||||
or `gpg: signing failed: secret key not available`, run this command to
|
||||
use `gpg2` instead:
|
||||
|
||||
```shell
|
||||
git config --global gpg.program gpg2
|
||||
```
|
||||
|
||||
## Signing commits
|
||||
### Sign your Git commits
|
||||
|
||||
After you have [created your GPG key](#generating-a-gpg-key) and [added it to
|
||||
your account](#adding-a-gpg-key-to-your-account), you can start signing your
|
||||
commits:
|
||||
After you [add your public key to your account](#add-a-gpg-key-to-your-account),
|
||||
you can sign individual commits manually, or configure Git to default to signed commits:
|
||||
|
||||
1. Commit like you used to, the only difference is the addition of the `-S` flag:
|
||||
- Sign individual Git commits manually:
|
||||
1. Add `-S` flag to any commit you want to sign:
|
||||
|
||||
```shell
|
||||
git commit -S -m "My commit msg"
|
||||
```
|
||||
```shell
|
||||
git commit -S -m "My commit message"
|
||||
```
|
||||
|
||||
1. Enter the passphrase of your GPG key when asked.
|
||||
1. Push to GitLab and check that your commits [are verified](#verifying-commits).
|
||||
1. Enter the passphrase of your GPG key when asked.
|
||||
1. Push to GitLab and check that your commits [are verified](#verify-commits).
|
||||
- Sign all Git commits by default by running this command:
|
||||
|
||||
If you don't want to type the `-S` flag every time you commit, you can tell Git
|
||||
to sign your commits automatically:
|
||||
```shell
|
||||
git config --global commit.gpgsign true
|
||||
```
|
||||
|
||||
```shell
|
||||
git config --global commit.gpgsign true
|
||||
```
|
||||
## Verify commits
|
||||
|
||||
## Verifying commits
|
||||
You can review commits for a merge request, or for an entire project:
|
||||
|
||||
1. Within a project or [merge request](../../merge_requests/index.md), navigate to
|
||||
the **Commits** tab. Signed commits show a badge containing either
|
||||
**Verified** or **Unverified**, depending on the verification status of the GPG
|
||||
signature.
|
||||
1. To review commits for a project:
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Repository > Commits**.
|
||||
1. To review commits for a merge request:
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Merge requests**, then select your merge request.
|
||||
1. Select **Commits**.
|
||||
1. Identify the commit you want to review. Signed commits show either a **Verified**
|
||||
or **Unverified** badge, depending on the verification status of the GPG
|
||||
signature. Unsigned commits do not display a badge:
|
||||
|
||||
![Signed and unsigned commits](img/project_signed_and_unsigned_commits.png)
|
||||
|
||||
1. By clicking on the GPG badge, details of the signature are displayed.
|
||||
1. To display the signature details for a commit, select the GPG badge:
|
||||
|
||||
![Signed commit with verified signature](img/project_signed_commit_verified_signature.png)
|
||||
|
||||
![Signed commit with verified signature](img/project_signed_commit_unverified_signature.png)
|
||||
![Signed commit with unverified signature](img/project_signed_commit_unverified_signature.png)
|
||||
|
||||
## Revoking a GPG key
|
||||
## Revoke a GPG key
|
||||
|
||||
Revoking a key **unverifies** already signed commits. Commits that were
|
||||
verified by using this key changes to an unverified state. Future commits
|
||||
stay unverified after you revoke this key. This action should be used
|
||||
in case your key has been compromised.
|
||||
If a GPG key becomes compromised, revoke it. Revoking a key changes both future and past commits:
|
||||
|
||||
- Past commits signed by this key are marked as unverified.
|
||||
- Future commits signed by this key are marked as unverified.
|
||||
|
||||
To revoke a GPG key:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. On the left sidebar, select **GPG Keys**.
|
||||
1. On the left sidebar, select **GPG Keys** (**{key}**).
|
||||
1. Select **Revoke** next to the GPG key you want to delete.
|
||||
|
||||
## Removing a GPG key
|
||||
## Remove a GPG key
|
||||
|
||||
Removing a key **does not unverify** already signed commits. Commits that were
|
||||
verified by using this key stay verified. Only unpushed commits stay
|
||||
unverified after you remove this key. To unverify already signed commits, you need
|
||||
to [revoke the associated GPG key](#revoking-a-gpg-key) from your account.
|
||||
When you remove a GPG key from your GitLab account:
|
||||
|
||||
- Previous commits signed with this key remain verified.
|
||||
- Future commits (including any commits created but not yet pushed) that attempt
|
||||
to use this key are unverified.
|
||||
|
||||
To remove a GPG key from your account:
|
||||
|
||||
1. In the top-right corner, select your avatar.
|
||||
1. Select **Edit profile**.
|
||||
1. On the left sidebar, select **GPG Keys**.
|
||||
1. Select the trash icon (**{remove}**) next to the GPG key you want to delete.
|
||||
1. On the left sidebar, select **GPG Keys** (**{key}**).
|
||||
1. Select **Remove** (**{remove}**) next to the GPG key you want to delete.
|
||||
|
||||
## Rejecting commits that are not signed **(PREMIUM)**
|
||||
If you must unverify both future and past commits,
|
||||
[revoke the associated GPG key](#revoke-a-gpg-key) instead.
|
||||
|
||||
You can configure your project to reject commits that aren't GPG-signed
|
||||
via [push rules](../push_rules.md).
|
||||
## Related topics
|
||||
|
||||
## GPG signing API
|
||||
|
||||
Learn how to [get the GPG signature from a commit via API](../../../../api/commits.md#get-gpg-signature-of-a-commit).
|
||||
|
||||
## Further reading
|
||||
|
||||
For more details about GPG, see:
|
||||
|
||||
- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
|
||||
- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys)
|
||||
- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices)
|
||||
- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced)
|
||||
- [Review existing GPG keys in your instance](../../../admin_area/credentials_inventory.md#review-existing-gpg-keys)
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
This is important to minimize requests for support, and to avoid doc comments with
|
||||
questions that you know someone might ask.
|
||||
|
||||
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
|
||||
If you have none to add when creating a doc, leave this section in place
|
||||
but commented out to help encourage others to add to it in the future. -->
|
||||
- [Sign commits and tags with X.509 certificates](../x509_signed_commits/index.md)
|
||||
- [Commits API](../../../../api/commits.md)
|
||||
- GPG resources:
|
||||
- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
|
||||
- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys)
|
||||
- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices)
|
||||
- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced)
|
||||
- [Review existing GPG keys in your instance](../../../admin_area/credentials_inventory.md#review-existing-gpg-keys)
|
||||
|
|
|
@ -20,7 +20,7 @@ The main difference is the way GitLab determines whether or not the developer's
|
|||
(A trust store is a repository of trusted security certificates.) Combined with
|
||||
any required intermediate certificates in the signature, the developer's certificate
|
||||
can be chained back to a trusted root certificate.
|
||||
- For GPG, developers [add their GPG key](../gpg_signed_commits/index.md#adding-a-gpg-key-to-your-account)
|
||||
- For GPG, developers [add their GPG key](../gpg_signed_commits/index.md#add-a-gpg-key-to-your-account)
|
||||
to their account.
|
||||
|
||||
GitLab uses its own certificate store and therefore defines the
|
||||
|
|
|
@ -59,6 +59,7 @@ module Gitlab
|
|||
push_frontend_feature_flag(:sandboxed_mermaid, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:source_editor_toolbar, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:gl_avatar_for_all_user_avatars, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:mr_attention_requests, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
# Exposes the state of a feature flag to the frontend code.
|
||||
|
|
|
@ -984,6 +984,9 @@ msgstr ""
|
|||
msgid "%{strongOpen}Warning:%{strongClose} SAML group links can cause GitLab to automatically remove members from groups."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{strongStart}Need your attention%{strongEnd} are the merge requests that need your help to move forward, as an assignee or reviewer."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{strongStart}Tip:%{strongEnd} You can also check out merge requests locally. %{linkStart}Learn more.%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4939,12 +4942,6 @@ msgstr ""
|
|||
msgid "Assign to me"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assign yourself to these issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assign yourself to this issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assigned %{assignee_users_sentence}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -34428,6 +34425,9 @@ msgstr ""
|
|||
msgid "Solution"
|
||||
msgstr ""
|
||||
|
||||
msgid "Some actions remove attention requests, like a reviewer approving or anyone merging the merge request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Some changes are not shown"
|
||||
msgstr ""
|
||||
|
||||
|
@ -38468,6 +38468,9 @@ msgstr ""
|
|||
msgid "To add the entry manually, provide the following details to the application on your phone."
|
||||
msgstr ""
|
||||
|
||||
msgid "To ask someone to look at a merge request, select %{strongStart}Request attention%{strongEnd}. Select again to remove the request."
|
||||
msgstr ""
|
||||
|
||||
msgid "To confirm, type %{phrase_code}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -44005,6 +44008,12 @@ msgstr ""
|
|||
msgid "mrWidget|Approved by you and others"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Assign yourself to these issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Assign yourself to this issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "mrWidget|Cancel auto-merge"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -19,15 +19,22 @@ function getMD5HashFromFile(data) {
|
|||
try {
|
||||
const file = 'gl-dependency-scanning-report.json'
|
||||
const data = fs.readFileSync(file)
|
||||
|
||||
const [filename, fileext] = path.basename(file).split('.')
|
||||
const uniqueId = process.env['CI_PIPELINE_ID'] && process.env['CI_JOB_ID'] ?
|
||||
process.env['CI_PIPELINE_ID'] + '-' + process.env['CI_JOB_ID'] :
|
||||
Date.now()
|
||||
const key = path.join('package_hunter_test', filename + '-' + uniqueId + '.' + fileext)
|
||||
|
||||
const responseData = await s3Client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: 'gl-logs-for-panther-test',
|
||||
Key: path.join('package_hunter_test', path.basename(file)),
|
||||
Key: key,
|
||||
Body: data,
|
||||
ContentMD5: getMD5HashFromFile(data),
|
||||
}),
|
||||
)
|
||||
console.log('Successfully uploaded %s', file)
|
||||
console.log('Successfully uploaded %s to %s', file, key)
|
||||
} catch (err) {
|
||||
if (err.name === 'CredentialsProviderError' || err.name === 'AuthorizationHeaderMalformed')
|
||||
console.log('Could not upload the report. Are AWS credentials configured in ~/.aws/credentials?')
|
||||
|
|
|
@ -797,6 +797,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
|
|||
|
||||
context 'when 2FA is required for the user' do
|
||||
before do
|
||||
stub_feature_flags(mr_attention_requests: false)
|
||||
group = create(:group, require_two_factor_authentication: true)
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlPopover, GlButton, GlSprintf, GlIcon } from '@gitlab/ui';
|
||||
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
||||
import NavigationPopover from '~/attention_requests/components/navigation_popover.vue';
|
||||
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
|
||||
|
||||
let wrapper;
|
||||
let dismiss;
|
||||
|
||||
function createComponent(provideData = {}, shouldShowCallout = true) {
|
||||
wrapper = shallowMount(NavigationPopover, {
|
||||
provide: {
|
||||
message: ['Test'],
|
||||
observerElSelector: '.js-test',
|
||||
observerElToggledClass: 'show',
|
||||
featureName: 'attention_requests',
|
||||
popoverTarget: '.js-test-popover',
|
||||
...provideData,
|
||||
},
|
||||
stubs: {
|
||||
UserCalloutDismisser: makeMockUserCalloutDismisser({
|
||||
dismiss,
|
||||
shouldShowCallout,
|
||||
}),
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Attention requests navigation popover', () => {
|
||||
beforeEach(() => {
|
||||
setFixtures('<div><div class="js-test-popover"></div><div class="js-test"></div></div>');
|
||||
dismiss = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('hides popover if callout is disabled', () => {
|
||||
createComponent({}, false);
|
||||
|
||||
expect(wrapper.findComponent(GlPopover).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows popover if callout is enabled', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.findComponent(GlPopover).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it.each`
|
||||
isDesktop | device | expectedPlacement
|
||||
${true} | ${'desktop'} | ${'left'}
|
||||
${false} | ${'mobile'} | ${'bottom'}
|
||||
`(
|
||||
'sets popover position to $expectedPlacement on $device',
|
||||
({ isDesktop, expectedPlacement }) => {
|
||||
jest.spyOn(bp, 'isDesktop').mockReturnValue(isDesktop);
|
||||
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.findComponent(GlPopover).props('placement')).toBe(expectedPlacement);
|
||||
},
|
||||
);
|
||||
|
||||
it('calls dismiss when clicking action button', () => {
|
||||
createComponent();
|
||||
|
||||
wrapper
|
||||
.findComponent(GlButton)
|
||||
.vm.$emit('click', { preventDefault() {}, stopPropagation() {} });
|
||||
|
||||
expect(dismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows icon in text', () => {
|
||||
createComponent({ showAttentionIcon: true, message: ['%{strongStart}Test%{strongEnd}'] });
|
||||
|
||||
const icon = wrapper.findComponent(GlIcon);
|
||||
|
||||
expect(icon.exists()).toBe(true);
|
||||
expect(icon.props('name')).toBe('attention');
|
||||
});
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
import { GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import RelatedLinks from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
|
||||
|
||||
|
@ -85,13 +86,29 @@ describe('MRWidgetRelatedLinks', () => {
|
|||
expect(content).toContain('Mentions issues #23 and #42');
|
||||
});
|
||||
|
||||
it('should have assing issues link', () => {
|
||||
createComponent({
|
||||
relatedLinks: {
|
||||
assignToMe: '<a href="#">Assign yourself to these issues</a>',
|
||||
},
|
||||
describe('should have correct assign issues link', () => {
|
||||
it.each([
|
||||
[1, 'Assign yourself to this issue'],
|
||||
[2, 'Assign yourself to these issues'],
|
||||
])('when issue count is %s, link displays correct text', (unassignedCount, text) => {
|
||||
const assignToMe = '/assign';
|
||||
|
||||
createComponent({
|
||||
relatedLinks: { assignToMe, unassignedCount },
|
||||
});
|
||||
|
||||
const glLinkWrapper = wrapper.findComponent(GlLink);
|
||||
|
||||
expect(glLinkWrapper.attributes('href')).toBe(assignToMe);
|
||||
expect(glLinkWrapper.text()).toBe(text);
|
||||
});
|
||||
|
||||
expect(wrapper.text().trim()).toContain('Assign yourself to these issues');
|
||||
it('when no link is present', () => {
|
||||
createComponent({
|
||||
relatedLinks: { assignToMe: '#', unassignedCount: 0 },
|
||||
});
|
||||
|
||||
expect(wrapper.findComponent(GlLink).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -162,10 +162,19 @@ RSpec.describe MergeRequestPresenter do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#assign_to_closing_issues_link' do
|
||||
describe '#assign_to_closing_issues_path' do
|
||||
subject do
|
||||
described_class.new(resource, current_user: user)
|
||||
.assign_to_closing_issues_link
|
||||
.assign_to_closing_issues_path
|
||||
end
|
||||
|
||||
it { is_expected.to match("#{project.full_path}/-/merge_requests/#{resource.iid}/assign_related_issues") }
|
||||
end
|
||||
|
||||
describe '#assign_to_closing_issues_count' do
|
||||
subject do
|
||||
described_class.new(resource, current_user: user)
|
||||
.assign_to_closing_issues_count
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -178,33 +187,28 @@ RSpec.describe MergeRequestPresenter do
|
|||
let(:issue) { create(:issue) }
|
||||
let(:assignable_issues) { [issue] }
|
||||
|
||||
it 'returns correct link with correct text' do
|
||||
it 'returns correct count' do
|
||||
is_expected
|
||||
.to match("#{project.full_path}/-/merge_requests/#{resource.iid}/assign_related_issues")
|
||||
|
||||
is_expected
|
||||
.to match("Assign yourself to this issue")
|
||||
.to match(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple closing issues' do
|
||||
let(:issues) { create_list(:issue, 2) }
|
||||
let(:issues) { build_list(:issue, 2) }
|
||||
let(:assignable_issues) { issues }
|
||||
|
||||
it 'returns correct link with correct text' do
|
||||
it 'returns correct count' do
|
||||
is_expected
|
||||
.to match("#{project.full_path}/-/merge_requests/#{resource.iid}/assign_related_issues")
|
||||
|
||||
is_expected
|
||||
.to match("Assign yourself to these issues")
|
||||
.to match(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'no closing issue' do
|
||||
let(:assignable_issues) { [] }
|
||||
|
||||
it 'returns correct link with correct text' do
|
||||
is_expected.to be_nil
|
||||
it 'returns correct count' do
|
||||
is_expected
|
||||
.to match(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,7 +59,7 @@ RSpec.describe MergeRequestWidgetEntity do
|
|||
data = described_class.new(resource, request: request, issues_links: true).as_json
|
||||
|
||||
expect(data).to include(:issues_links)
|
||||
expect(data[:issues_links]).to include(:assign_to_closing, :closing, :mentioned_but_not_closing, :closing_count, :mentioned_count)
|
||||
expect(data[:issues_links]).to include(:assign_to_closing, :assign_to_closing_count, :closing, :mentioned_but_not_closing, :closing_count, :mentioned_count)
|
||||
end
|
||||
|
||||
it 'omits issue links by default' do
|
||||
|
|
Loading…
Reference in a new issue