Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-25 06:09:42 +00:00
parent c916c6f79b
commit 654281e682
48 changed files with 1201 additions and 61 deletions

View file

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/browser';
import { sanitize } from '~/lib/dompurify'; import { sanitize } from '~/lib/dompurify';
// We currently load + parse the data from the issue app and related merge request // We currently load + parse the data from the issue app and related merge request
@ -7,10 +8,9 @@ export const parseIssuableData = () => {
try { try {
if (cachedParsedData) return cachedParsedData; if (cachedParsedData) return cachedParsedData;
const initialDataEl = document.getElementById('js-issuable-app-initial-data'); const initialDataEl = document.getElementById('js-issuable-app');
const parsedData = JSON.parse(initialDataEl.textContent.replace(/"/g, '"'));
const parsedData = JSON.parse(initialDataEl.dataset.initial);
parsedData.initialTitleHtml = sanitize(parsedData.initialTitleHtml); parsedData.initialTitleHtml = sanitize(parsedData.initialTitleHtml);
parsedData.initialDescriptionHtml = sanitize(parsedData.initialDescriptionHtml); parsedData.initialDescriptionHtml = sanitize(parsedData.initialDescriptionHtml);
@ -18,7 +18,7 @@ export const parseIssuableData = () => {
return parsedData; return parsedData;
} catch (e) { } catch (e) {
console.error(e); // eslint-disable-line no-console Sentry.captureException(e);
return {}; return {};
} }

View file

@ -1,7 +1,7 @@
<script> <script>
/* eslint-disable vue/no-v-html */ /* eslint-disable vue/no-v-html */
import Vue from 'vue'; import Vue from 'vue';
import { GlFormGroup, GlDeprecatedButton, GlModal, GlToast, GlToggle } from '@gitlab/ui'; import { GlFormGroup, GlButton, GlModal, GlToast, GlToggle } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import { visitUrl, getBaseURL } from '~/lib/utils/url_utility'; import { visitUrl, getBaseURL } from '~/lib/utils/url_utility';
@ -11,7 +11,7 @@ Vue.use(GlToast);
export default { export default {
components: { components: {
GlFormGroup, GlFormGroup,
GlDeprecatedButton, GlButton,
GlModal, GlModal,
GlToggle, GlToggle,
}, },
@ -123,7 +123,7 @@ export default {
<h4 class="js-section-header"> <h4 class="js-section-header">
{{ s__('SelfMonitoring|Self monitoring') }} {{ s__('SelfMonitoring|Self monitoring') }}
</h4> </h4>
<gl-deprecated-button class="js-settings-toggle">{{ __('Expand') }}</gl-deprecated-button> <gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button>
<p class="js-section-sub-header"> <p class="js-section-sub-header">
{{ s__('SelfMonitoring|Enable or disable instance self monitoring') }} {{ s__('SelfMonitoring|Enable or disable instance self monitoring') }}
</p> </p>
@ -146,6 +146,7 @@ export default {
:ok-title="__('Delete project')" :ok-title="__('Delete project')"
:cancel-title="__('Cancel')" :cancel-title="__('Cancel')"
ok-variant="danger" ok-variant="danger"
category="primary"
@ok="deleteProject" @ok="deleteProject"
@cancel="hideSelfMonitorModal" @cancel="hideSelfMonitorModal"
> >

View file

@ -0,0 +1,34 @@
<script>
import { GlAvatarLink, GlAvatarLabeled } from '@gitlab/ui';
import { AVATAR_SIZE } from '../constants';
export default {
name: 'GroupAvatar',
avatarSize: AVATAR_SIZE,
components: { GlAvatarLink, GlAvatarLabeled },
props: {
member: {
type: Object,
required: true,
},
},
computed: {
group() {
return this.member.sharedWithGroup;
},
},
};
</script>
<template>
<gl-avatar-link :href="group.webUrl">
<gl-avatar-labeled
:label="group.fullName"
:src="group.avatarUrl"
:alt="group.fullName"
:size="$options.avatarSize"
:entity-name="group.name"
:entity-id="group.id"
/>
</gl-avatar-link>
</template>

View file

@ -0,0 +1,32 @@
<script>
import { GlAvatarLabeled } from '@gitlab/ui';
import { AVATAR_SIZE } from '../constants';
export default {
name: 'InviteAvatar',
avatarSize: AVATAR_SIZE,
components: { GlAvatarLabeled },
props: {
member: {
type: Object,
required: true,
},
},
computed: {
invite() {
return this.member.invite;
},
},
};
</script>
<template>
<gl-avatar-labeled
:label="invite.email"
:src="invite.avatarUrl"
:alt="invite.email"
:size="$options.avatarSize"
:entity-name="invite.email"
:entity-id="member.id"
/>
</template>

View file

@ -0,0 +1,55 @@
<script>
import { GlAvatarLink, GlAvatarLabeled, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { __ } from '~/locale';
import { AVATAR_SIZE } from '../constants';
export default {
name: 'UserAvatar',
avatarSize: AVATAR_SIZE,
orphanedUserLabel: __('Orphaned member'),
components: { GlAvatarLink, GlAvatarLabeled },
directives: {
SafeHtml,
},
props: {
member: {
type: Object,
required: true,
},
},
computed: {
user() {
return this.member.user;
},
},
};
</script>
<template>
<gl-avatar-link
v-if="user"
class="js-user-link"
:href="user.webUrl"
:data-user-id="user.id"
:data-username="user.username"
>
<gl-avatar-labeled
:label="user.name"
:sub-label="`@${user.username}`"
:src="user.avatarUrl"
:alt="user.name"
:size="$options.avatarSize"
:entity-name="user.name"
:entity-id="user.id"
/>
</gl-avatar-link>
<gl-avatar-labeled
v-else
:label="$options.orphanedUserLabel"
:alt="$options.orphanedUserLabel"
:size="$options.avatarSize"
:entity-name="$options.orphanedUserLabel"
:entity-id="member.id"
/>
</template>

View file

@ -53,3 +53,12 @@ export const FIELDS = [
tdClass: 'col-actions', tdClass: 'col-actions',
}, },
]; ];
export const AVATAR_SIZE = 48;
export const MEMBER_TYPES = {
user: 'user',
group: 'group',
invite: 'invite',
accessRequest: 'accessRequest',
};

View file

@ -0,0 +1,31 @@
<script>
import { kebabCase } from 'lodash';
import UserAvatar from '../avatars/user_avatar.vue';
import InviteAvatar from '../avatars/invite_avatar.vue';
import GroupAvatar from '../avatars/group_avatar.vue';
export default {
name: 'MemberAvatar',
components: { UserAvatar, InviteAvatar, GroupAvatar, AccessRequestAvatar: UserAvatar },
props: {
memberType: {
type: String,
required: true,
},
member: {
type: Object,
required: true,
},
},
computed: {
avatarComponent() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `${kebabCase(this.memberType)}-avatar`;
},
},
};
</script>
<template>
<component :is="avatarComponent" :member="member" />
</template>

View file

@ -3,11 +3,15 @@ import { mapState } from 'vuex';
import { GlTable } from '@gitlab/ui'; import { GlTable } from '@gitlab/ui';
import { FIELDS } from '../constants'; import { FIELDS } from '../constants';
import initUserPopovers from '~/user_popovers'; import initUserPopovers from '~/user_popovers';
import MemberAvatar from './member_avatar.vue';
import MembersTableCell from './members_table_cell.vue';
export default { export default {
name: 'MembersTable', name: 'MembersTable',
components: { components: {
GlTable, GlTable,
MemberAvatar,
MembersTableCell,
}, },
computed: { computed: {
...mapState(['members', 'tableFields']), ...mapState(['members', 'tableFields']),
@ -33,6 +37,12 @@ export default {
:empty-text="__('No members found')" :empty-text="__('No members found')"
show-empty show-empty
> >
<template #cell(account)="{ item: member }">
<members-table-cell #default="{ memberType }" :member="member">
<member-avatar :member-type="memberType" :member="member" />
</members-table-cell>
</template>
<template #cell(source)> <template #cell(source)>
<!-- Temporarily empty --> <!-- Temporarily empty -->
</template> </template>

View file

@ -0,0 +1,40 @@
<script>
import { MEMBER_TYPES } from '../constants';
export default {
name: 'MembersTableCell',
props: {
member: {
type: Object,
required: true,
},
},
computed: {
isGroup() {
return Boolean(this.member.sharedWithGroup);
},
isInvite() {
return Boolean(this.member.invite);
},
isAccessRequest() {
return Boolean(this.member.requestedAt);
},
memberType() {
if (this.isGroup) {
return MEMBER_TYPES.group;
} else if (this.isInvite) {
return MEMBER_TYPES.invite;
} else if (this.isAccessRequest) {
return MEMBER_TYPES.accessRequest;
}
return MEMBER_TYPES.user;
},
},
render() {
return this.$scopedSlots.default({
memberType: this.memberType,
});
},
};
</script>

View file

@ -112,6 +112,7 @@ export default {
this.currentHighlightItem += 1; this.currentHighlightItem += 1;
} else if (e.keyCode === ENTER_KEY_CODE && this.currentHighlightItem > -1) { } else if (e.keyCode === ENTER_KEY_CODE && this.currentHighlightItem > -1) {
this.updateSelectedLabels([this.visibleLabels[this.currentHighlightItem]]); this.updateSelectedLabels([this.visibleLabels[this.currentHighlightItem]]);
this.searchKey = '';
} else if (e.keyCode === ESC_KEY_CODE) { } else if (e.keyCode === ESC_KEY_CODE) {
this.toggleDropdownContents(); this.toggleDropdownContents();
} }

View file

@ -62,9 +62,7 @@
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.content-block .detail-page-description.content-block
-# haml-lint:disable InlineJavaScript #js-issuable-app{ data: { initial: issuable_initial_data(@issue).to_json} }
%script#js-issuable-app-initial-data{ type: "application/json" }= issuable_initial_data(@issue).to_json
#js-issuable-app
.title-container .title-container
%h2.title= markdown_field(@issue, :title) %h2.title= markdown_field(@issue, :title)
- if @issue.description.present? - if @issue.description.present?

View file

@ -0,0 +1,5 @@
---
title: Add empty dependencies value to ECS Deploy job
merge_request: 36862
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Reset labels select search text on Enter
merge_request: 43285
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Move job token specs to core
merge_request: 42374
author: Mathieu Parent
type: changed

View file

@ -1,7 +1,7 @@
--- ---
name: container_registry_fast_tag_delete name: container_registry_fast_tag_delete
introduced_by_url: introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23325
rollout_issue_url: rollout_issue_url:
group: group: group::package
type: development type: development
default_enabled: true default_enabled: true

View file

@ -1,7 +1,7 @@
--- ---
name: invisible_captcha name: invisible_captcha
introduced_by_url: introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31625
rollout_issue_url: rollout_issue_url:
group: group: group::acquisition
type: development type: development
default_enabled: false default_enabled: false

View file

@ -0,0 +1,14 @@
---
# Suggestion: gitlab.InclusionAbleism
#
# Suggests alternatives for words that foster ableism.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use inclusive language. Consider "%s" instead of "%s".'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#inclusive-language
level: suggestion
ignorecase: true
swap:
sanity (?:check|test): check for completeness
dummy: placeholder

View file

@ -0,0 +1,16 @@
---
# Warning: gitlab.InclusionCultural
#
# Suggests alternatives for words that are culturally inappropriate.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use inclusive language. Consider "%s" instead of "%s".'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#inclusive-language
level: warning
ignorecase: true
swap:
blacklist(?:ed|ing|s)?: denylist
whitelist(?:ed|ing|s)?: allowlist
master: primary
slave: secondary

View file

@ -0,0 +1,18 @@
---
# Suggestion: gitlab.InclusionGender
#
# Suggests alternatives for words that are gender-specific.
#
# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles
extends: substitution
message: 'Use inclusive language. Consider "%s" instead of "%s".'
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#inclusive-language
level: suggestion
ignorecase: true
swap:
mankind: humanity, people
manpower: GitLab team members
he: they
his: their
she: they
hers: their

View file

@ -12,7 +12,6 @@ level: warning
ignorecase: true ignorecase: true
swap: swap:
admin: administrator admin: administrator
blacklist(ed|ing)?: denylist
code base: codebase code base: codebase
config: configuration config: configuration
distro: distribution distro: distribution
@ -20,4 +19,3 @@ swap:
filesystem: file system filesystem: file system
info: information info: information
repo: repository repo: repository
whitelist(ed|ing)?: allowlist

View file

@ -424,17 +424,17 @@ server (with `gitaly_address`) unless you setup with special
storages: storages:
default: default:
gitaly_address: tcp://gitaly1.internal:8075 gitaly_address: tcp://gitaly1.internal:8075
path: /some/dummy/path path: /some/local/path
storage1: storage1:
gitaly_address: tcp://gitaly1.internal:8075 gitaly_address: tcp://gitaly1.internal:8075
path: /some/dummy/path path: /some/local/path
storage2: storage2:
gitaly_address: tcp://gitaly2.internal:8075 gitaly_address: tcp://gitaly2.internal:8075
path: /some/dummy/path path: /some/local/path
``` ```
NOTE: **Note:** NOTE: **Note:**
`/some/dummy/path` should be set to a local folder that exists, however no data will be stored in `/some/local/path` should be set to a local folder that exists, however no data will be stored in
this folder. This will no longer be necessary after this folder. This will no longer be necessary after
[this issue](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved. [this issue](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved.
@ -627,17 +627,17 @@ To configure Gitaly with TLS:
storages: storages:
default: default:
gitaly_address: tls://gitaly1.internal:9999 gitaly_address: tls://gitaly1.internal:9999
path: /some/dummy/path path: /some/local/path
storage1: storage1:
gitaly_address: tls://gitaly1.internal:9999 gitaly_address: tls://gitaly1.internal:9999
path: /some/dummy/path path: /some/local/path
storage2: storage2:
gitaly_address: tls://gitaly2.internal:9999 gitaly_address: tls://gitaly2.internal:9999
path: /some/dummy/path path: /some/local/path
``` ```
NOTE: **Note:** NOTE: **Note:**
`/some/dummy/path` should be set to a local folder that exists, however no data will be stored `/some/local/path` should be set to a local folder that exists, however no data will be stored
in this folder. This will no longer be necessary after in this folder. This will no longer be necessary after
[Gitaly issue #1282](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved. [Gitaly issue #1282](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved.

View file

@ -547,14 +547,14 @@ To configure Praefect with TLS:
storages: storages:
default: default:
gitaly_address: tls://praefect1.internal:3305 gitaly_address: tls://praefect1.internal:3305
path: /some/dummy/path path: /some/local/path
storage1: storage1:
gitaly_address: tls://praefect2.internal:3305 gitaly_address: tls://praefect2.internal:3305
path: /some/dummy/path path: /some/local/path
``` ```
NOTE: **Note:** NOTE: **Note:**
`/some/dummy/path` should be set to a local folder that exists, however no `/some/local/path` should be set to a local folder that exists, however no
data will be stored in this folder. This will no longer be necessary after data will be stored in this folder. This will no longer be necessary after
[this issue](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved. [this issue](https://gitlab.com/gitlab-org/gitaly/-/issues/1282) is resolved.

View file

@ -71,7 +71,7 @@ The instructions make the assumption that you will be using the email address `i
sudo postfix start sudo postfix start
``` ```
1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: 1. Send the new `incoming` user an email to test SMTP, by entering the following into the SMTP prompt:
```plaintext ```plaintext
ehlo localhost ehlo localhost
@ -251,7 +251,7 @@ Courier, which we will install later to add IMAP authentication, requires mailbo
If you get a `Connection refused` error instead, make sure your firewall is set up to allow inbound traffic on port 25. If you get a `Connection refused` error instead, make sure your firewall is set up to allow inbound traffic on port 25.
1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: 1. Send the `incoming` user an email to test SMTP, by entering the following into the SMTP prompt:
```plaintext ```plaintext
ehlo gitlab.example.com ehlo gitlab.example.com

View file

@ -6398,7 +6398,7 @@ type GeoNode {
name: String name: String
""" """
Package file registries of the GeoNode. Available only when feature flag `geo_package_file_replication` is enabled Package file registries of the GeoNode
""" """
packageFileRegistries( packageFileRegistries(
""" """
@ -6508,6 +6508,37 @@ type GeoNode {
last: Int last: Int
): TerraformStateRegistryConnection ): TerraformStateRegistryConnection
"""
Find terraform state version registries on this Geo node. Available only when
feature flag `geo_terraform_state_version_replication` is enabled
"""
terraformStateVersionRegistries(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Filters registries by their ID
"""
ids: [ID!]
"""
Returns the last _n_ elements from the list.
"""
last: Int
): TerraformStateVersionRegistryConnection
""" """
The user-facing URL for this Geo node The user-facing URL for this Geo node
""" """
@ -16923,6 +16954,86 @@ type TerraformStateRegistryEdge {
node: TerraformStateRegistry node: TerraformStateRegistry
} }
"""
Represents the Geo sync and verification state of a terraform state version
"""
type TerraformStateVersionRegistry {
"""
Timestamp when the TerraformStateVersionRegistry was created
"""
createdAt: Time
"""
ID of the TerraformStateVersionRegistry
"""
id: ID!
"""
Error message during sync of the TerraformStateVersionRegistry
"""
lastSyncFailure: String
"""
Timestamp of the most recent successful sync of the TerraformStateVersionRegistry
"""
lastSyncedAt: Time
"""
Timestamp after which the TerraformStateVersionRegistry should be resynced
"""
retryAt: Time
"""
Number of consecutive failed sync attempts of the TerraformStateVersionRegistry
"""
retryCount: Int
"""
Sync state of the TerraformStateVersionRegistry
"""
state: RegistryState
"""
ID of the terraform state version
"""
terraformStateVersionId: ID!
}
"""
The connection type for TerraformStateVersionRegistry.
"""
type TerraformStateVersionRegistryConnection {
"""
A list of edges.
"""
edges: [TerraformStateVersionRegistryEdge]
"""
A list of nodes.
"""
nodes: [TerraformStateVersionRegistry]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type TerraformStateVersionRegistryEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: TerraformStateVersionRegistry
}
""" """
Represents a requirement test report Represents a requirement test report
""" """

View file

@ -17863,7 +17863,7 @@
}, },
{ {
"name": "packageFileRegistries", "name": "packageFileRegistries",
"description": "Package file registries of the GeoNode. Available only when feature flag `geo_package_file_replication` is enabled", "description": "Package file registries of the GeoNode",
"args": [ "args": [
{ {
"name": "ids", "name": "ids",
@ -18134,6 +18134,77 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "terraformStateVersionRegistries",
"description": "Find terraform state version registries on this Geo node. Available only when feature flag `geo_terraform_state_version_replication` is enabled",
"args": [
{
"name": "ids",
"description": "Filters registries by their ID",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "TerraformStateVersionRegistryConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "url", "name": "url",
"description": "The user-facing URL for this Geo node", "description": "The user-facing URL for this Geo node",
@ -49587,6 +49658,251 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "TerraformStateVersionRegistry",
"description": "Represents the Geo sync and verification state of a terraform state version",
"fields": [
{
"name": "createdAt",
"description": "Timestamp when the TerraformStateVersionRegistry was created",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the TerraformStateVersionRegistry",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lastSyncFailure",
"description": "Error message during sync of the TerraformStateVersionRegistry",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lastSyncedAt",
"description": "Timestamp of the most recent successful sync of the TerraformStateVersionRegistry",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "retryAt",
"description": "Timestamp after which the TerraformStateVersionRegistry should be resynced",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "retryCount",
"description": "Number of consecutive failed sync attempts of the TerraformStateVersionRegistry",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "state",
"description": "Sync state of the TerraformStateVersionRegistry",
"args": [
],
"type": {
"kind": "ENUM",
"name": "RegistryState",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "terraformStateVersionId",
"description": "ID of the terraform state version",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TerraformStateVersionRegistryConnection",
"description": "The connection type for TerraformStateVersionRegistry.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "TerraformStateVersionRegistryEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "TerraformStateVersionRegistry",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TerraformStateVersionRegistryEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "TerraformStateVersionRegistry",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "TestReport", "name": "TestReport",

View file

@ -2409,6 +2409,21 @@ Represents the sync and verification state of a terraform state.
| `state` | RegistryState | Sync state of the TerraformStateRegistry | | `state` | RegistryState | Sync state of the TerraformStateRegistry |
| `terraformStateId` | ID! | ID of the TerraformState | | `terraformStateId` | ID! | ID of the TerraformState |
### TerraformStateVersionRegistry
Represents the Geo sync and verification state of a terraform state version.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `createdAt` | Time | Timestamp when the TerraformStateVersionRegistry was created |
| `id` | ID! | ID of the TerraformStateVersionRegistry |
| `lastSyncFailure` | String | Error message during sync of the TerraformStateVersionRegistry |
| `lastSyncedAt` | Time | Timestamp of the most recent successful sync of the TerraformStateVersionRegistry |
| `retryAt` | Time | Timestamp after which the TerraformStateVersionRegistry should be resynced |
| `retryCount` | Int | Number of consecutive failed sync attempts of the TerraformStateVersionRegistry |
| `state` | RegistryState | Sync state of the TerraformStateVersionRegistry |
| `terraformStateVersionId` | ID! | ID of the terraform state version |
### TestReport ### TestReport
Represents a requirement test report. Represents a requirement test report.

View file

@ -663,10 +663,10 @@ Example response:
"weight": null, "weight": null,
"has_tasks": false, "has_tasks": false,
"_links": { "_links": {
"self": "http://gitlab.dummy:3000/api/v4/projects/1/issues/1", "self": "http://gitlab.example:3000/api/v4/projects/1/issues/1",
"notes": "http://gitlab.dummy:3000/api/v4/projects/1/issues/1/notes", "notes": "http://gitlab.example:3000/api/v4/projects/1/issues/1/notes",
"award_emoji": "http://gitlab.dummy:3000/api/v4/projects/1/issues/1/award_emoji", "award_emoji": "http://gitlab.example:3000/api/v4/projects/1/issues/1/award_emoji",
"project": "http://gitlab.dummy:3000/api/v4/projects/1" "project": "http://gitlab.example:3000/api/v4/projects/1"
}, },
"references": { "references": {
"short": "#1", "short": "#1",

View file

@ -19,7 +19,7 @@ If you just want to delete everything and start over with an empty DB (approxima
bundle exec rake db:reset RAILS_ENV=development bundle exec rake db:reset RAILS_ENV=development
``` ```
If you just want to delete everything and start over with dummy data (approximately 4 minutes). This If you just want to delete everything and start over with sample data (approximately 4 minutes). This
also does `db:reset` and runs DB-specific migrations: also does `db:reset` and runs DB-specific migrations:
```shell ```shell

View file

@ -418,8 +418,11 @@ We strive to create documentation that is inclusive. This section includes
guidance and examples in the following categories: guidance and examples in the following categories:
- [Gender-specific wording](#avoid-gender-specific-wording). - [Gender-specific wording](#avoid-gender-specific-wording).
(Tested in [`InclusionGender.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionGender.yml).)
- [Ableist language](#avoid-ableist-language). - [Ableist language](#avoid-ableist-language).
(Tested in [`InclusionAbleism.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionAbleism.yml).)
- [Cultural sensitivity](#culturally-sensitive-language). - [Cultural sensitivity](#culturally-sensitive-language).
(Tested in [`InclusionCultural.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/InclusionCultural.yml).)
We write our developer documentation with inclusivity and diversity in mind. This We write our developer documentation with inclusivity and diversity in mind. This
page is not an exhaustive reference, but describes some general guidelines and page is not an exhaustive reference, but describes some general guidelines and
@ -433,11 +436,13 @@ a gender-neutral pronoun.
Avoid the use of gender-specific pronouns, unless referring to a specific person. Avoid the use of gender-specific pronouns, unless referring to a specific person.
<!-- vale gitlab.InclusionGender = NO -->
| Use | Avoid | | Use | Avoid |
|-----------------------------------|---------------------------------| |-----------------------------------|---------------------------------|
| People, humanity | Mankind | | People, humanity | Mankind |
| GitLab Team Members | Manpower | | GitLab Team Members | Manpower |
| You can install; They can install | He can install; She can install | | You can install; They can install | He can install; She can install |
<!-- vale gitlab.InclusionGender = YES -->
If you need to set up [Fake user information](#fake-user-information), use If you need to set up [Fake user information](#fake-user-information), use
diverse or non-gendered names with common surnames. diverse or non-gendered names with common surnames.
@ -446,6 +451,7 @@ diverse or non-gendered names with common surnames.
Avoid terms that are also used in negative stereotypes for different groups. Avoid terms that are also used in negative stereotypes for different groups.
<!-- vale gitlab.InclusionAbleism = NO -->
| Use | Avoid | | Use | Avoid |
|------------------------|----------------------| |------------------------|----------------------|
| Check for completeness | Sanity check | | Check for completeness | Sanity check |
@ -454,6 +460,7 @@ Avoid terms that are also used in negative stereotypes for different groups.
| Placeholder variable | Dummy variable | | Placeholder variable | Dummy variable |
| Active/Inactive | Enabled/Disabled | | Active/Inactive | Enabled/Disabled |
| On/Off | Enabled/Disabled | | On/Off | Enabled/Disabled |
<!-- vale gitlab.InclusionAbleism = YES -->
Credit: [Avoid ableist language](https://developers.google.com/style/inclusive-documentation#ableist-language) Credit: [Avoid ableist language](https://developers.google.com/style/inclusive-documentation#ableist-language)
in the Google Developer Style Guide. in the Google Developer Style Guide.
@ -464,10 +471,12 @@ Avoid terms that reflect negative cultural stereotypes and history. In most
cases, you can replace terms such as `master` and `slave` with terms that are cases, you can replace terms such as `master` and `slave` with terms that are
more precise and functional, such as `primary` and `secondary`. more precise and functional, such as `primary` and `secondary`.
<!-- vale gitlab.InclusionCultural = NO -->
| Use | Avoid | | Use | Avoid |
|----------------------|-----------------------| |----------------------|-----------------------|
| Primary / secondary | Master / slave | | Primary / secondary | Master / slave |
| Allowlist / denylist | Blacklist / whitelist | | Allowlist / denylist | Blacklist / whitelist |
<!-- vale gitlab.InclusionCultural = YES -->
For more information see the following [Internet Draft specification](https://tools.ietf.org/html/draft-knodel-terminology-02). For more information see the following [Internet Draft specification](https://tools.ietf.org/html/draft-knodel-terminology-02).

View file

@ -27,7 +27,7 @@ Please note that [S/MIME signed](../administration/smime_signing_email.md) email
## Mailer previews ## Mailer previews
Rails provides a way to preview our mailer templates in HTML and plaintext using Rails provides a way to preview our mailer templates in HTML and plaintext using
dummy data. sample data.
The previews live in [`app/mailers/previews`](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/mailers/previews) and can be viewed at The previews live in [`app/mailers/previews`](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/mailers/previews) and can be viewed at
[`/rails/mailers`](http://localhost:3000/rails/mailers). [`/rails/mailers`](http://localhost:3000/rails/mailers).

View file

@ -97,7 +97,7 @@ The following options are available.
| Restrict by commit message (negative match)| **Starter** 11.1 | Only commit messages that do not match this regular expression are allowed to be pushed. Leave empty to allow any commit message. Uses multiline mode, which can be disabled using `(?-m)`. | | Restrict by commit message (negative match)| **Starter** 11.1 | Only commit messages that do not match this regular expression are allowed to be pushed. Leave empty to allow any commit message. Uses multiline mode, which can be disabled using `(?-m)`. |
| Restrict by branch name | **Starter** 9.3 | Only branch names that match this regular expression are allowed to be pushed. Leave empty to allow any branch name. | | Restrict by branch name | **Starter** 9.3 | Only branch names that match this regular expression are allowed to be pushed. Leave empty to allow any branch name. |
| Restrict by commit author's email | **Starter** 7.10 | Only commit author's email that match this regular expression are allowed to be pushed. Leave empty to allow any email. | | Restrict by commit author's email | **Starter** 7.10 | Only commit author's email that match this regular expression are allowed to be pushed. Leave empty to allow any email. |
| Prohibited file names | **Starter** 7.10 | Any committed filenames that match this regular expression are not allowed to be pushed. Leave empty to allow any filenames. | | Prohibited file names | **Starter** 7.10 | Any committed filenames that match this regular expression and do not already exist in the repository are not allowed to be pushed. Leave empty to allow any filenames. See [common examples](#prohibited-file-names). |
| Maximum file size | **Starter** 7.12 | Pushes that contain added or updated files that exceed this file size (in MB) are rejected. Set to 0 to allow files of any size. Files tracked by Git LFS are exempted. | | Maximum file size | **Starter** 7.12 | Pushes that contain added or updated files that exceed this file size (in MB) are rejected. Set to 0 to allow files of any size. Files tracked by Git LFS are exempted. |
TIP: **Tip:** TIP: **Tip:**
@ -178,6 +178,44 @@ pry.history
bash_history bash_history
``` ```
## Prohibited file names
> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 7.10.
Each file name contained in a Git push is compared to the regular expression in this field. Filenames in Git consist of both the file's name and any directory that may precede it. A singular regular expression can contain multiple independent matches used as exclusions. File names can be broadly matched to any location in the repository, or restricted to specific locations. Filenames can also be partial matches used to exclude file types by extension.
The following examples make use of regex string boundary characters which match the beginning of a string (`^`), and the end (`$`). They also include instances where either the directory path or the filename can include `.` or `/`. Both of these special regex characters have to be escaped with a backslash `\` to be used as normal characters in a match condition.
Example: prevent pushing any `.exe` files to any location in the repository. This is an example of a partial match, which can match any filename that contains `.exe` at the end:
```plaintext
\.exe$
```
Example: prevent a specific configuration file in the repository root from being pushed:
```plaintext
^config\.yml$
```
Example: prevent a specific configuration file in a known directory from being pushed:
```plaintext
^directory-name\/config\.yml$
```
Example: prevent the specific file named `install.exe` from being pushed to any location in the repository. Note that the parenthesized expression `(^|\/)` will match either a file following a directory separator or a file in the root directory of the repository:
```plaintext
(^|\/)install\.exe$
```
Example: combining all of the above in a single expression. Note that all of the preceding expressions rely on the end of string character `$`, so we can move that part of each expression to the end of the grouped collection of match conditions where it will be appended to all matches:
```plaintext
(\.exe|^config\.yml|^directory-name\/config\.yml|(^|\/)install\.exe)$
```
<!-- ## Troubleshooting <!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View file

@ -12,7 +12,7 @@ more repositories, by importing an SSH public key to your GitLab instance.
This is useful for cloning repositories to your Continuous This is useful for cloning repositories to your Continuous
Integration (CI) server. By using deploy keys, you don't have to set up a Integration (CI) server. By using deploy keys, you don't have to set up a
dummy user account. fake user account.
There are two types of deploy keys: There are two types of deploy keys:

View file

@ -10,6 +10,7 @@
.deploy_to_ecs: .deploy_to_ecs:
image: 'registry.gitlab.com/gitlab-org/cloud-deploy/aws-ecs:latest' image: 'registry.gitlab.com/gitlab-org/cloud-deploy/aws-ecs:latest'
dependencies: []
script: script:
- ecs update-task-definition - ecs update-task-definition

View file

@ -9066,10 +9066,10 @@ msgstr ""
msgid "Dismiss Merge Request promotion" msgid "Dismiss Merge Request promotion"
msgstr "" msgstr ""
msgid "Dismiss Selected" msgid "Dismiss Value Stream Analytics introduction box"
msgstr "" msgstr ""
msgid "Dismiss Value Stream Analytics introduction box" msgid "Dismiss selected"
msgstr "" msgstr ""
msgid "Dismiss trial promotion" msgid "Dismiss trial promotion"
@ -17931,6 +17931,9 @@ msgstr ""
msgid "Origin" msgid "Origin"
msgstr "" msgstr ""
msgid "Orphaned member"
msgstr ""
msgid "Other Labels" msgid "Other Labels"
msgstr "" msgstr ""

View file

@ -103,6 +103,8 @@ module QA
end end
def click_commit(commit_msg) def click_commit(commit_msg)
wait_for_requests
within_element(:file_tree_table) do within_element(:file_tree_table) do
click_on commit_msg click_on commit_msg
end end

View file

@ -14,12 +14,8 @@ useMockIntersectionObserver();
jest.mock('~/lib/utils/poll'); jest.mock('~/lib/utils/poll');
const setupHTML = initialData => { const setupHTML = initialData => {
document.body.innerHTML = ` document.body.innerHTML = `<div id="js-issuable-app"></div>`;
<div id="js-issuable-app"></div> document.getElementById('js-issuable-app').dataset.initial = JSON.stringify(initialData);
<script id="js-issuable-app-initial-data" type="application/json">
${JSON.stringify(initialData)}
</script>
`;
}; };
describe('Issue show index', () => { describe('Issue show index', () => {

View file

@ -19,9 +19,8 @@ describe('RelatedMergeRequests', () => {
mockData = getJSONFixture(FIXTURE_PATH); mockData = getJSONFixture(FIXTURE_PATH);
// put the fixture in DOM as the component expects // put the fixture in DOM as the component expects
document.body.innerHTML = `<div id="js-issuable-app-initial-data">${JSON.stringify( document.body.innerHTML = `<div id="js-issuable-app"></div>`;
mockData, document.getElementById('js-issuable-app').dataset.initial = JSON.stringify(mockData);
)}</div>`;
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 }); mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 });

View file

@ -15,13 +15,16 @@ exports[`self monitor component When the self monitor project has not been creat
</h4> </h4>
<gl-deprecated-button-stub <gl-button-stub
buttontextclasses=""
category="primary"
class="js-settings-toggle" class="js-settings-toggle"
size="md" icon=""
variant="secondary" size="medium"
variant="default"
> >
Expand Expand
</gl-deprecated-button-stub> </gl-button-stub>
<p <p
class="js-section-sub-header" class="js-section-sub-header"
@ -56,6 +59,7 @@ exports[`self monitor component When the self monitor project has not been creat
<gl-modal-stub <gl-modal-stub
cancel-title="Cancel" cancel-title="Cancel"
category="primary"
modalclass="" modalclass=""
modalid="delete-self-monitor-modal" modalid="delete-self-monitor-modal"
ok-title="Delete project" ok-title="Delete project"

View file

@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlDeprecatedButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import SelfMonitor from '~/self_monitor/components/self_monitor_form.vue'; import SelfMonitor from '~/self_monitor/components/self_monitor_form.vue';
import { createStore } from '~/self_monitor/store'; import { createStore } from '~/self_monitor/store';
@ -42,7 +42,7 @@ describe('self monitor component', () => {
it('renders as an expand button by default', () => { it('renders as an expand button by default', () => {
wrapper = shallowMount(SelfMonitor, { store }); wrapper = shallowMount(SelfMonitor, { store });
const button = wrapper.find(GlDeprecatedButton); const button = wrapper.find(GlButton);
expect(button.text()).toBe('Expand'); expect(button.text()).toBe('Expand');
}); });

View file

@ -0,0 +1,46 @@
import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { GlAvatarLink } from '@gitlab/ui';
import { group as member } from '../mock_data';
import GroupAvatar from '~/vue_shared/components/members/avatars/group_avatar.vue';
describe('MemberList', () => {
let wrapper;
const group = member.sharedWithGroup;
const createComponent = (propsData = {}) => {
wrapper = mount(GroupAvatar, {
propsData: {
member,
...propsData,
},
});
};
const getByText = (text, options) =>
createWrapper(getByTextHelper(wrapper.element, text, options));
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders link to group', () => {
const link = wrapper.find(GlAvatarLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(group.webUrl);
});
it("renders group's full name", () => {
expect(getByText(group.fullName).exists()).toBe(true);
});
it("renders group's avatar", () => {
expect(wrapper.find('img').attributes('src')).toBe(group.avatarUrl);
});
});

View file

@ -0,0 +1,38 @@
import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { invite as member } from '../mock_data';
import InviteAvatar from '~/vue_shared/components/members/avatars/invite_avatar.vue';
describe('MemberList', () => {
let wrapper;
const { invite } = member;
const createComponent = (propsData = {}) => {
wrapper = mount(InviteAvatar, {
propsData: {
member,
...propsData,
},
});
};
const getByText = (text, options) =>
createWrapper(getByTextHelper(wrapper.element, text, options));
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders email as name', () => {
expect(getByText(invite.email).exists()).toBe(true);
});
it('renders avatar', () => {
expect(wrapper.find('img').attributes('src')).toBe(invite.avatarUrl);
});
});

View file

@ -0,0 +1,66 @@
import { mount, createWrapper } from '@vue/test-utils';
import { getByText as getByTextHelper } from '@testing-library/dom';
import { GlAvatarLink } from '@gitlab/ui';
import { member, orphanedMember } from '../mock_data';
import UserAvatar from '~/vue_shared/components/members/avatars/user_avatar.vue';
describe('MemberList', () => {
let wrapper;
const { user } = member;
const createComponent = (propsData = {}) => {
wrapper = mount(UserAvatar, {
propsData: {
member,
...propsData,
},
});
};
const getByText = (text, options) =>
createWrapper(getByTextHelper(wrapper.element, text, options));
afterEach(() => {
wrapper.destroy();
});
it("renders link to user's profile", () => {
createComponent();
const link = wrapper.find(GlAvatarLink);
expect(link.exists()).toBe(true);
expect(link.attributes()).toMatchObject({
href: user.webUrl,
'data-user-id': `${user.id}`,
'data-username': user.username,
});
});
it("renders user's name", () => {
createComponent();
expect(getByText(user.name).exists()).toBe(true);
});
it("renders user's username", () => {
createComponent();
expect(getByText(`@${user.username}`).exists()).toBe(true);
});
it("renders user's avatar", () => {
createComponent();
expect(wrapper.find('img').attributes('src')).toBe(user.avatarUrl);
});
describe('when user property does not exist', () => {
it('displays an orphaned user', () => {
createComponent({ member: orphanedMember });
expect(getByText('Orphaned member').exists()).toBe(true);
});
});
});

View file

@ -0,0 +1,61 @@
export const member = {
requestedAt: null,
canUpdate: false,
canRemove: false,
canOverride: false,
accessLevel: { integerValue: 50, stringValue: 'Owner' },
source: {
id: 178,
name: 'Foo Bar',
webUrl: 'https://gitlab.com/groups/foo-bar',
},
user: {
id: 123,
name: 'Administrator',
username: 'root',
webUrl: 'https://gitlab.com/root',
avatarUrl: 'https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80&d=identicon',
blocked: false,
twoFactorEnabled: false,
},
id: 238,
createdAt: '2020-07-17T16:22:46.923Z',
expiresAt: null,
usingLicense: false,
groupSso: false,
groupManagedAccount: false,
};
export const group = {
accessLevel: { integerValue: 10, stringValue: 'Guest' },
sharedWithGroup: {
id: 24,
name: 'Commit451',
avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png?width=40',
fullPath: 'parent-group/commit451',
fullName: 'Parent group / Commit451',
webUrl: 'https://gitlab.com/groups/parent-group/commit451',
},
id: 3,
createdAt: '2020-08-06T15:31:07.662Z',
expiresAt: null,
};
const { user, ...memberNoUser } = member;
export const invite = {
...memberNoUser,
invite: {
email: 'jewel@hudsonwalter.biz',
avatarUrl: 'https://www.gravatar.com/avatar/cbab7510da7eec2f60f638261b05436d?s=80&d=identicon',
canResend: true,
},
};
export const orphanedMember = memberNoUser;
export const accessRequest = {
...member,
requestedAt: '2020-07-17T16:22:46.923Z',
};
export const members = [member];

View file

@ -0,0 +1,36 @@
import { shallowMount } from '@vue/test-utils';
import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
import { member as memberMock, group, invite, accessRequest } from '../mock_data';
import MemberAvatar from '~/vue_shared/components/members/table/member_avatar.vue';
import UserAvatar from '~/vue_shared/components/members/avatars/user_avatar.vue';
import GroupAvatar from '~/vue_shared/components/members/avatars/group_avatar.vue';
import InviteAvatar from '~/vue_shared/components/members/avatars/invite_avatar.vue';
describe('MemberList', () => {
let wrapper;
const createComponent = propsData => {
wrapper = shallowMount(MemberAvatar, {
propsData,
});
};
afterEach(() => {
wrapper.destroy();
});
test.each`
memberType | member | expectedComponent | expectedComponentName
${MEMBER_TYPES.user} | ${memberMock} | ${UserAvatar} | ${'UserAvatar'}
${MEMBER_TYPES.group} | ${group} | ${GroupAvatar} | ${'GroupAvatar'}
${MEMBER_TYPES.invite} | ${invite} | ${InviteAvatar} | ${'InviteAvatar'}
${MEMBER_TYPES.accessRequest} | ${accessRequest} | ${UserAvatar} | ${'UserAvatar'}
`(
'renders $expectedComponentName when `memberType` is $memberType',
({ memberType, member, expectedComponent }) => {
createComponent({ memberType, member });
expect(wrapper.find(expectedComponent).exists()).toBe(true);
},
);
});

View file

@ -0,0 +1,53 @@
import { mount, createLocalVue } from '@vue/test-utils';
import { MEMBER_TYPES } from '~/vue_shared/components/members/constants';
import { member as memberMock, group, invite, accessRequest } from '../mock_data';
import MembersTableCell from '~/vue_shared/components/members/table/members_table_cell.vue';
describe('MemberList', () => {
const WrappedComponent = {
props: {
memberType: {
type: String,
required: true,
},
},
render(createElement) {
return createElement('div', this.memberType);
},
};
const localVue = createLocalVue();
localVue.component('wrapped-component', WrappedComponent);
let wrapper;
const createComponent = propsData => {
wrapper = mount(MembersTableCell, {
localVue,
propsData,
scopedSlots: {
default: '<wrapped-component :member-type="props.memberType" />',
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
test.each`
member | expectedMemberType
${memberMock} | ${MEMBER_TYPES.user}
${group} | ${MEMBER_TYPES.group}
${invite} | ${MEMBER_TYPES.invite}
${accessRequest} | ${MEMBER_TYPES.accessRequest}
`(
'sets scoped slot prop `memberType` to $expectedMemberType',
({ member, expectedMemberType }) => {
createComponent({ member });
expect(wrapper.find(WrappedComponent).props('memberType')).toBe(expectedMemberType);
},
);
});

View file

@ -133,6 +133,19 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.vm.currentHighlightItem).toBe(2); expect(wrapper.vm.currentHighlightItem).toBe(2);
}); });
it('resets the search text when the Enter key is pressed', () => {
wrapper.setData({
currentHighlightItem: 1,
searchKey: 'bug',
});
wrapper.vm.handleKeyDown({
keyCode: ENTER_KEY_CODE,
});
expect(wrapper.vm.searchKey).toBe('');
});
it('calls action `updateSelectedLabels` with currently highlighted label when Enter key is pressed', () => { it('calls action `updateSelectedLabels` with currently highlighted label when Enter key is pressed', () => {
jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation(); jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation();
wrapper.setData({ wrapper.setData({

View file

@ -9,7 +9,7 @@ RSpec.describe API::Helpers do
include described_class include described_class
include TermsHelper include TermsHelper
let(:user) { create(:user) } let_it_be(:user, reload: true) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:key) { create(:key, user: user) } let(:key) { create(:key, user: user) }
@ -243,6 +243,67 @@ RSpec.describe API::Helpers do
end end
end end
end end
describe "when authenticating using a job token" do
let_it_be(:job, reload: true) do
create(:ci_build, user: user, status: :running)
end
let(:route_authentication_setting) { {} }
before do
allow_any_instance_of(self.class).to receive(:route_authentication_setting)
.and_return(route_authentication_setting)
end
context 'when route is allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: true } }
it "returns a 401 response for an invalid token" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = 'invalid token'
expect { current_user }.to raise_error /401/
end
it "returns a 401 response for a job that's not running" do
job.update!(status: :success)
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
expect { current_user }.to raise_error /401/
end
it "returns a 403 response for a user without access" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect { current_user }.to raise_error /403/
end
it 'returns a 403 response for a user who is blocked' do
user.block!
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
expect { current_user }.to raise_error /403/
end
it "sets current_user" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
expect(current_user).to eq(user)
end
end
context 'when route is not allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: false } }
it "sets current_user to nil" do
env[Gitlab::Auth::AuthFinders::JOB_TOKEN_HEADER] = job.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
expect(current_user).to be_nil
end
end
end
end end
describe '.handle_api_exception' do describe '.handle_api_exception' do

View file

@ -604,7 +604,7 @@ module KubernetesHelpers
} }
end end
def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil) def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil, replicas: 3)
{ {
"metadata" => { "metadata" => {
"name" => name, "name" => name,
@ -617,7 +617,7 @@ module KubernetesHelpers
"track" => track "track" => track
}.compact }.compact
}, },
"spec" => { "replicas" => 3 }, "spec" => { "replicas" => replicas },
"status" => { "status" => {
"observedGeneration" => 4 "observedGeneration" => 4
} }