Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-07 15:09:56 +00:00
parent de8e5077c3
commit 79f98200f8
73 changed files with 696 additions and 312 deletions

View File

@ -68,6 +68,7 @@ export default {
newUsersToInvite: [],
selectedDate: undefined,
groupToBeSharedWith: {},
source: 'unknown',
};
},
computed: {
@ -195,6 +196,7 @@ export default {
...this.basePostData,
email: usersToInviteByEmail,
access_level: this.selectedAccessLevel,
invite_source: this.source,
};
},
addByUserIdPostData(usersToAddById) {
@ -202,6 +204,7 @@ export default {
...this.basePostData,
user_id: usersToAddById,
access_level: this.selectedAccessLevel,
invite_source: this.source,
};
},
shareWithGroupPostData(groupToBeSharedWith) {

View File

@ -3,6 +3,8 @@ import Activities from '~/activities';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import BlobViewer from '~/blob/viewer/index';
import { initUploadForm } from '~/blob_edit/blob_bundle';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import leaveByUrl from '~/namespaces/leave_by_url';
import initVueNotificationsDropdown from '~/notifications';
import { initUploadFileTrigger } from '~/projects/upload_file_experiment';
@ -44,3 +46,5 @@ initVueNotificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
initUploadFileTrigger();
initInviteMembersModal();
initInviteMembersTrigger();

View File

@ -7,13 +7,17 @@ import {
GlTooltipDirective,
GlLoadingIcon,
GlIcon,
GlHoverLoadDirective,
} from '@gitlab/ui';
import { escapeRegExp } from 'lodash';
import filesQuery from 'shared_queries/repository/files.query.graphql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import { TREE_PAGE_SIZE } from '~/repository/constants';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getRefMixin from '../../mixins/get_ref';
import blobInfoQuery from '../../queries/blob_info.query.graphql';
import commitQuery from '../../queries/commit.query.graphql';
export default {
@ -28,6 +32,7 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
GlHoverLoad: GlHoverLoadDirective,
},
apollo: {
commit: {
@ -139,6 +144,33 @@ export default {
return this.commit && this.commit.lockLabel;
},
},
methods: {
handlePreload() {
return this.isFolder ? this.loadFolder() : this.loadBlob();
},
loadFolder() {
this.apolloQuery(filesQuery, {
projectPath: this.projectPath,
ref: this.ref,
path: this.path,
nextPageCursor: '',
pageSize: TREE_PAGE_SIZE,
});
},
loadBlob() {
if (!this.refactorBlobViewerEnabled) {
return;
}
this.apolloQuery(blobInfoQuery, {
projectPath: this.projectPath,
filePath: this.path,
});
},
apolloQuery(query, variables) {
this.$apollo.query({ query, variables });
},
},
};
</script>
@ -148,6 +180,7 @@ export default {
<component
:is="linkComponent"
ref="link"
v-gl-hover-load="handlePreload"
:to="routerLinkTo"
:href="url"
:class="{

View File

@ -1,6 +1,5 @@
query getBlobInfo($projectPath: ID!, $filePath: String!) {
project(fullPath: $projectPath) {
id
repository {
blobs(paths: [$filePath]) {
nodes {

View File

@ -457,9 +457,7 @@ class ApplicationController < ActionController::Base
def set_current_context(&block)
Gitlab::ApplicationContext.with_context(
# Avoid loading the auth_user again after the request. Otherwise calling
# `auth_user` again would also trigger the Warden callbacks again
user: -> { auth_user if strong_memoized?(:auth_user) },
user: -> { context_user },
project: -> { @project if @project&.persisted? },
namespace: -> { @group if @group&.persisted? },
caller_id: caller_id,
@ -542,6 +540,12 @@ class ApplicationController < ActionController::Base
end
end
# Avoid loading the auth_user again after the request. Otherwise calling
# `auth_user` again would also trigger the Warden callbacks again
def context_user
auth_user if strong_memoized?(:auth_user)
end
def caller_id
"#{self.class.name}##{action_name}"
end

View File

@ -34,6 +34,10 @@ class ConfirmationsController < Devise::ConfirmationsController
def after_sign_in(resource)
after_sign_in_path_for(resource)
end
def context_user
resource
end
end
ConfirmationsController.prepend_mod_with('ConfirmationsController')

View File

@ -287,6 +287,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def fail_admin_mode_invalid_credentials
redirect_to new_admin_session_path, alert: _('Invalid login or password')
end
def context_user
current_user
end
end
OmniauthCallbacksController.prepend_mod_with('OmniauthCallbacksController')

View File

@ -67,6 +67,10 @@ class PasswordsController < Devise::PasswordsController
redirect_to new_user_session_path,
notice: I18n.t('devise.passwords.send_paranoid_instructions')
end
def context_user
resource
end
end
PasswordsController.prepend_mod_with('PasswordsController')

View File

@ -202,6 +202,10 @@ class RegistrationsController < Devise::RegistrationsController
experiment(:invite_signup_page_interaction, actor: member).track(:form_submission)
experiment('members/invite_email', actor: member).track(:accepted)
end
def context_user
current_user
end
end
RegistrationsController.prepend_mod_with('RegistrationsController')

View File

@ -5,10 +5,10 @@ module Types
class RunnerTypeEnum < BaseEnum
graphql_name 'CiRunnerType'
::Ci::Runner.runner_types.keys.each do |type|
value type.upcase,
description: "A runner that is #{type.tr('_', ' ')}.",
value: type
::Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
value runner_type.upcase,
description: "A runner that is #{runner_type.tr('_', ' ')}.",
value: runner_type
end
end
end

View File

@ -22,7 +22,7 @@ module Types
field :expires_at, Types::TimeType, null: true,
description: 'Date and time the membership expires.'
field :user, Types::UserType, null: false,
field :user, Types::UserType, null: true,
description: 'User that is associated with the member object.'
definition_methods do

View File

@ -27,7 +27,7 @@ module CacheMarkdownField
# Returns the default Banzai render context for the cached markdown field.
def banzai_render_context(field)
raise ArgumentError, "Unknown field: #{field.inspect}" unless
cached_markdown_fields.markdown_fields.include?(field)
cached_markdown_fields.key?(field)
# Always include a project key, or Banzai complains
project = self.project if self.respond_to?(:project)
@ -100,7 +100,7 @@ module CacheMarkdownField
def cached_html_for(markdown_field)
raise ArgumentError, "Unknown field: #{markdown_field}" unless
cached_markdown_fields.markdown_fields.include?(markdown_field)
cached_markdown_fields.key?(markdown_field)
__send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end
@ -108,7 +108,7 @@ module CacheMarkdownField
# Updates the markdown cache if necessary, then returns the field
# Unlike `cached_html_for` it returns `nil` if the field does not exist
def updated_cached_html_for(markdown_field)
return unless cached_markdown_fields.markdown_fields.include?(markdown_field)
return unless cached_markdown_fields.key?(markdown_field)
if attribute_invalidated?(cached_markdown_fields.html_field(markdown_field))
# Invalidated due to Markdown content change

View File

@ -1,16 +0,0 @@
# frozen_string_literal: true
# This class is to be removed with 9.1
# We should also by then remove BuildsEmailService from database
# https://gitlab.com/gitlab-org/gitlab/-/issues/331064
module Integrations
class BuildsEmail < Integration
def self.to_param
'builds_email'
end
def self.supported_events
%w[]
end
end
end

View File

@ -108,10 +108,14 @@ class Member < ApplicationRecord
scope :active_without_invites_and_requests, -> do
left_join_users
.where(users: { state: 'active' })
.non_request
.without_invites_and_requests
.reorder(nil)
end
scope :without_invites_and_requests, -> do
non_request
.non_invite
.non_minimal_access
.reorder(nil)
end
scope :invite, -> { where.not(invite_token: nil) }

View File

@ -47,11 +47,11 @@ module Issues
attr_reader :issue
def track_meeting_added_event
::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'add_zoom_meeting', label: 'Issue ID', value: issue.id)
::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'add_zoom_meeting', label: 'Issue ID', value: issue.id, user: current_user, project: @project, namespace: @project.namespace)
end
def track_meeting_removed_event
::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'remove_zoom_meeting', label: 'Issue ID', value: issue.id)
::Gitlab::Tracking.event('IncidentManagement::ZoomIntegration', 'remove_zoom_meeting', label: 'Issue ID', value: issue.id, user: current_user, project: @project, namespace: @project.namespace)
end
def add_zoom_meeting(link)

View File

@ -75,7 +75,7 @@ module Members
def after_execute(member:)
super
Gitlab::Tracking.event(self.class.name, 'create_member', label: invite_source, property: tracking_property(member))
Gitlab::Tracking.event(self.class.name, 'create_member', label: invite_source, property: tracking_property(member), user: current_user)
end
def invite_source

View File

@ -3,6 +3,10 @@
track_event: 'render' } }
= s_('InviteMember|Invite your team')
%p= s_('InviteMember|Add members to this project and start collaborating with your team.')
= link_to s_('InviteMember|Invite members'), project_project_members_path(@project, sort: :access_level_desc),
class: 'gl-button btn btn-confirm gl-mb-8 gl-xs-w-full',
data: { track_event: 'click_button', track_label: 'invite_members_empty_project' }
.js-invite-members-trigger{ data: { variant: 'confirm',
classes: 'gl-mb-8 gl-xs-w-full',
display_text: s_('InviteMember|Invite members'),
event: 'click_button',
label: 'invite_members_empty_project' } }
= render 'shared/issuable/invite_members_trigger', project: @project

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class RemoveBuildsEmailServiceFromServices < ActiveRecord::Migration[6.1]
def up
execute("DELETE from services WHERE type = 'BuildsEmailService'")
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
fb02e0fee2760dad203b54d81c342dbf1461b3010503cab05da1eb14ab5d33da

View File

@ -9501,7 +9501,7 @@ Represents a Group Membership.
| <a id="groupmembergroup"></a>`group` | [`Group`](#group) | Group that a User is a member of. |
| <a id="groupmemberid"></a>`id` | [`ID!`](#id) | ID of the member. |
| <a id="groupmemberupdatedat"></a>`updatedAt` | [`Time`](#time) | Date and time the membership was last updated. |
| <a id="groupmemberuser"></a>`user` | [`UserCore!`](#usercore) | User that is associated with the member object. |
| <a id="groupmemberuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. |
| <a id="groupmemberuserpermissions"></a>`userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. |
### `GroupPermissions`
@ -11916,7 +11916,7 @@ Represents a Project Membership.
| <a id="projectmemberid"></a>`id` | [`ID!`](#id) | ID of the member. |
| <a id="projectmemberproject"></a>`project` | [`Project`](#project) | Project that User is a member of. |
| <a id="projectmemberupdatedat"></a>`updatedAt` | [`Time`](#time) | Date and time the membership was last updated. |
| <a id="projectmemberuser"></a>`user` | [`UserCore!`](#usercore) | User that is associated with the member object. |
| <a id="projectmemberuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. |
| <a id="projectmemberuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. |
### `ProjectPermissions`
@ -15687,7 +15687,7 @@ Implementations:
| <a id="memberinterfaceexpiresat"></a>`expiresAt` | [`Time`](#time) | Date and time the membership expires. |
| <a id="memberinterfaceid"></a>`id` | [`ID!`](#id) | ID of the member. |
| <a id="memberinterfaceupdatedat"></a>`updatedAt` | [`Time`](#time) | Date and time the membership was last updated. |
| <a id="memberinterfaceuser"></a>`user` | [`UserCore!`](#usercore) | User that is associated with the member object. |
| <a id="memberinterfaceuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member object. |
#### `Noteable`

View File

@ -752,7 +752,7 @@ Parameters:
| `name` | string | yes | The name of the group. |
| `path` | string | yes | The path of the group. |
| `description` | string | no | The group's description. |
| `membership_lock` | boolean | no | **(STARTER)** Prevent adding new members to project membership within this group. |
| `membership_lock` | boolean | no | **(PREMIUM)** Prevent adding new members to project membership within this group. |
| `visibility` | string | no | The group's visibility. Can be `private`, `internal`, or `public`. |
| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. |
| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
@ -828,7 +828,7 @@ PUT /groups/:id
| `name` | string | no | The name of the group. |
| `path` | string | no | The path of the group. |
| `description` | string | no | The description of the group. |
| `membership_lock` | boolean | no | **(STARTER)** Prevent adding new members to project membership within this group. |
| `membership_lock` | boolean | no | **(PREMIUM)** Prevent adding new members to project membership within this group. |
| `share_with_group_lock` | boolean | no | Prevent sharing a project with another group within this group. |
| `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. |
| `require_two_factor_authentication` | boolean | no | Require all users in this group to setup Two-factor authentication. |
@ -1146,7 +1146,7 @@ DELETE /groups/:id/hooks/:hook_id
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `hook_id` | integer | yes | The ID of the group hook. |
## Group Audit Events **(STARTER)**
## Group Audit Events **(PREMIUM)**
Group audit events can be accessed via the [Group Audit Events API](audit_events.md#group-audit-events)
@ -1302,11 +1302,11 @@ DELETE /groups/:id/share/:group_id
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `group_id` | integer | yes | The ID of the group to share with |
## Push Rules **(STARTER)**
## Push Rules **(PREMIUM)**
> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 13.4.
> Introduced in [GitLab](https://about.gitlab.com/pricing/) 13.4.
### Get group push rules **(STARTER)**
### Get group push rules **(PREMIUM)**
Get the [push rules](../user/group/index.md#group-push-rules) of a group.
@ -1349,7 +1349,7 @@ the `commit_committer_check` and `reject_unsigned_commits` parameters:
}
```
### Add group push rule **(STARTER)**
### Add group push rule **(PREMIUM)**
Adds [push rules](../user/group/index.md#group-push-rules) to the specified group.
@ -1362,17 +1362,17 @@ POST /groups/:id/push_rule
| Attribute | Type | Required | Description |
| --------------------------------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
| `member_check` **(STARTER)** | boolean | no | Allows only GitLab users to author commits |
| `prevent_secrets` **(STARTER)** | boolean | no | [Files that are likely to contain secrets](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) are rejected |
| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match the regular expression provided in this attribute, e.g. `Fixed \d+\..*` |
| `commit_message_negative_regex` **(STARTER)** | string | no | Commit messages matching the regular expression provided in this attribute aren't allowed, e.g. `ssh\:\/\/` |
| `branch_name_regex` **(STARTER)** | string | no | All branch names must match the regular expression provided in this attribute, e.g. `(feature|hotfix)\/*` |
| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match the regular expression provided in this attribute, e.g. `@my-company.com$` |
| `file_name_regex` **(STARTER)** | string | no | Filenames matching the regular expression provided in this attribute are **not** allowed, e.g. `(jar|exe)$` |
| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) allowed |
| `commit_committer_check` **(PREMIUM)** | boolean | no | Only commits pushed using verified emails are allowed |
| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Only commits signed through GPG are allowed |
| `deny_delete_tag` | boolean | no | Deny deleting a tag |
| `member_check` | boolean | no | Allows only GitLab users to author commits |
| `prevent_secrets` | boolean | no | [Files that are likely to contain secrets](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) are rejected |
| `commit_message_regex` | string | no | All commit messages must match the regular expression provided in this attribute, e.g. `Fixed \d+\..*` |
| `commit_message_negative_regex` | string | no | Commit messages matching the regular expression provided in this attribute aren't allowed, e.g. `ssh\:\/\/` |
| `branch_name_regex` | string | no | All branch names must match the regular expression provided in this attribute, e.g. `(feature|hotfix)\/*` |
| `author_email_regex` | string | no | All commit author emails must match the regular expression provided in this attribute, e.g. `@my-company.com$` |
| `file_name_regex` | string | no | Filenames matching the regular expression provided in this attribute are **not** allowed, e.g. `(jar|exe)$` |
| `max_file_size` | integer | no | Maximum file size (MB) allowed |
| `commit_committer_check` | boolean | no | Only commits pushed using verified emails are allowed |
| `reject_unsigned_commits` | boolean | no | Only commits signed through GPG are allowed |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/19/push_rule"
@ -1396,7 +1396,7 @@ Response:
}
```
### Edit group push rule **(STARTER)**
### Edit group push rule **(PREMIUM)**
Edit push rules for a specified group.
@ -1409,17 +1409,17 @@ PUT /groups/:id/push_rule
| Attribute | Type | Required | Description |
| --------------------------------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
| `member_check` **(STARTER)** | boolean | no | Restricts commits to be authored by existing GitLab users only |
| `prevent_secrets` **(STARTER)** | boolean | no | [Files that are likely to contain secrets](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) are rejected |
| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match the regular expression provided in this attribute, e.g. `Fixed \d+\..*` |
| `commit_message_negative_regex` **(STARTER)** | string | no | Commit messages matching the regular expression provided in this attribute aren't allowed, e.g. `ssh\:\/\/` |
| `branch_name_regex` **(STARTER)** | string | no | All branch names must match the regular expression provided in this attribute, e.g. `(feature|hotfix)\/*` |
| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match the regular expression provided in this attribute, e.g. `@my-company.com$` |
| `file_name_regex` **(STARTER)** | string | no | Filenames matching the regular expression provided in this attribute are **not** allowed, e.g. `(jar|exe)$` |
| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) allowed |
| `commit_committer_check` **(PREMIUM)** | boolean | no | Only commits pushed using verified emails are allowed |
| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Only commits signed through GPG are allowed |
| `deny_delete_tag` | boolean | no | Deny deleting a tag |
| `member_check` | boolean | no | Restricts commits to be authored by existing GitLab users only |
| `prevent_secrets` | boolean | no | [Files that are likely to contain secrets](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) are rejected |
| `commit_message_regex` | string | no | All commit messages must match the regular expression provided in this attribute, e.g. `Fixed \d+\..*` |
| `commit_message_negative_regex` | string | no | Commit messages matching the regular expression provided in this attribute aren't allowed, e.g. `ssh\:\/\/` |
| `branch_name_regex` | string | no | All branch names must match the regular expression provided in this attribute, e.g. `(feature|hotfix)\/*` |
| `author_email_regex` | string | no | All commit author emails must match the regular expression provided in this attribute, e.g. `@my-company.com$` |
| `file_name_regex` | string | no | Filenames matching the regular expression provided in this attribute are **not** allowed, e.g. `(jar|exe)$` |
| `max_file_size` | integer | no | Maximum file size (MB) allowed |
| `commit_committer_check` | boolean | no | Only commits pushed using verified emails are allowed |
| `reject_unsigned_commits` | boolean | no | Only commits signed through GPG are allowed |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/19/push_rule"
@ -1443,7 +1443,7 @@ Response:
}
```
### Delete group push rule **(STARTER)**
### Delete group push rule **(PREMIUM)**
Deletes the [push rules](../user/group/index.md#group-push-rules) of a group.

View File

@ -62,8 +62,9 @@ Example response:
"active": true,
"description": "test-1-20150125",
"id": 6,
"is_shared": false,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "project_type",
"name": null,
"online": true,
"status": "online"
@ -74,6 +75,7 @@ Example response:
"id": 8,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "group_type",
"name": null,
"online": false,
"status": "offline"
@ -115,6 +117,7 @@ Example response:
"id": 1,
"ip_address": "127.0.0.1",
"is_shared": true,
"runner_type": "instance_type",
"name": null,
"online": true,
"status": "online"
@ -125,6 +128,7 @@ Example response:
"id": 3,
"ip_address": "127.0.0.1",
"is_shared": true,
"runner_type": "instance_type",
"name": null,
"online": false,
"status": "offline"
@ -135,6 +139,7 @@ Example response:
"id": 6,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "project_type",
"name": null,
"online": true,
"status": "paused"
@ -145,6 +150,7 @@ Example response:
"id": 8,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "group_type",
"name": null,
"online": false,
"status": "offline"
@ -187,6 +193,7 @@ Example response:
"id": 6,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "project_type",
"contacted_at": "2016-01-25T16:39:48.066Z",
"name": null,
"online": true,
@ -250,6 +257,7 @@ Example response:
"id": 6,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "group_type",
"contacted_at": "2016-01-25T16:39:48.066Z",
"name": null,
"online": true,
@ -420,6 +428,7 @@ Example response:
"id": 8,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "project_type",
"name": null,
"online": false,
"status": "offline"
@ -430,6 +439,7 @@ Example response:
"id": 5,
"ip_address": "127.0.0.1",
"is_shared": true,
"runner_type": "instance_type",
"name": null,
"online": true,
"status": "paused"
@ -464,6 +474,7 @@ Example response:
"id": 9,
"ip_address": "127.0.0.1",
"is_shared": false,
"runner_type": "project_type",
"name": null,
"online": true,
"status": "online"
@ -522,6 +533,7 @@ Example response:
"ip_address": "127.0.0.1",
"active": true,
"is_shared": true,
"runner_type": "instance_type",
"name": "gitlab-runner",
"online": null,
"status": "not_connected"
@ -532,6 +544,7 @@ Example response:
"ip_address": "127.0.0.1",
"active": true,
"is_shared": true,
"runner_type": "instance_type",
"name": "gitlab-runner",
"online": false,
"status": "offline"
@ -542,6 +555,7 @@ Example response:
"ip_address": "127.0.0.1",
"active": true,
"is_shared": false,
"runner_type": "group_type",
"name": "gitlab-runner",
"online": null,
"status": "not_connected"

View File

@ -203,6 +203,10 @@ read_secrets:
- export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"
```
NOTE:
If you're using a Vault instance provided by HashiCorp Cloud Platform,
you need to export the `VAULT_NAMESPACE` variable. Its default value is `admin`.
![read_secrets staging](img/vault-read-secrets-staging.png)
The following job is able to authenticate using the `myproject-production` role and read secrets under `/secret/myproject/production/`:

View File

@ -3306,7 +3306,7 @@ job:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49775) in GitLab 13.8
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - It's enabled on GitLab.com.
> - It's disabled on GitLab.com.
> - It's recommended for production use.
Use `artifacts:public` to determine whether the job artifacts should be
@ -3524,12 +3524,13 @@ as artifacts.
The collected Metrics report uploads to GitLab as an artifact and displays in merge requests.
##### `artifacts:reports:performance` **(PREMIUM)**
##### `artifacts:reports:browser_performance` **(PREMIUM)**
> - Introduced in GitLab 11.5.
> - Requires GitLab Runner 11.5 and above.
> - [Name changed](https://gitlab.com/gitlab-org/gitlab/-/issues/225914) from `artifacts:reports:performance` in GitLab 14.0.
The `performance` report collects [Browser Performance Testing metrics](../../user/project/merge_requests/browser_performance_testing.md)
The `browser_performance` report collects [Browser Performance Testing metrics](../../user/project/merge_requests/browser_performance_testing.md)
as artifacts.
The collected Browser Performance report uploads to GitLab as an artifact and displays in merge requests.

View File

@ -431,7 +431,8 @@ The following table lists variables used to disable jobs.
| `license_scanning` | `LICENSE_MANAGEMENT_DISABLED` | [From GitLab 12.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22773) | If the variable is present, the job isn't created. |
| `load_performance` | `LOAD_PERFORMANCE_DISABLED` | From GitLab 13.2 | If the variable is present, the job isn't created. |
| `nodejs-scan-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `performance` | `PERFORMANCE_DISABLED` | From GitLab 11.0 | Browser performance. If the variable is present, the job isn't created. |
| `performance` | `PERFORMANCE_DISABLED` | GitLab 11.0 to GitLab 13.12 | Browser performance. If the variable is present, the job isn't created. Replaced by `browser_peformance`. |
| `browser_performance` | `BROWSER_PERFORMANCE_DISABLED` | From GitLab 14.0 | Browser performance. If the variable is present, the job isn't created. Replaces `performance`. |
| `phpcs-security-audit-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `pmd-apex-sast` | `SAST_DISABLED` | | If the variable is present, the job isn't created. |
| `retire-js-dependency_scanning` | `DEPENDENCY_SCANNING_DISABLED` | | If the variable is present, the job isn't created. |

View File

@ -1161,6 +1161,7 @@ X-Gitlab-Event: Pipeline Hook
"id": 380987,
"description": "shared-runners-manager-6.gitlab.com",
"active": true,
"runner_type": "instance_type",
"is_shared": true,
"tags": [
"linux",
@ -1196,7 +1197,8 @@ X-Gitlab-Event: Pipeline Hook
"id":380987,
"description":"shared-runners-manager-6.gitlab.com",
"active":true,
"is_shared":true,
"runner_type": "instance_type",
"is_shared": true,
"tags": [
"linux",
"docker"
@ -1230,6 +1232,7 @@ X-Gitlab-Event: Pipeline Hook
"id": 380987,
"description": "shared-runners-manager-6.gitlab.com",
"active": true,
"runner_type": "instance_type",
"is_shared": true,
"tags": [
"linux",
@ -1333,6 +1336,7 @@ X-Gitlab-Event: Job Hook
},
"runner": {
"active": true,
"runner_type": "project_type",
"is_shared": false,
"id": 380987,
"description": "shared-runners-manager-6.gitlab.com",

View File

@ -40,11 +40,11 @@ Consider the following workflow:
## How browser performance testing works
First, define a job in your `.gitlab-ci.yml` file that generates the
[Browser Performance report artifact](../../../ci/yaml/README.md#artifactsreportsperformance).
[Browser Performance report artifact](../../../ci/yaml/README.md#artifactsreportsbrowser_performance).
GitLab then checks this report, compares key performance metrics for each page
between the source and target branches, and shows the information in the merge request.
For an example Performance job, see
For an example Browser Performance job, see
[Configuring Browser Performance Testing](#configuring-browser-performance-testing).
NOTE:
@ -70,18 +70,17 @@ using Docker-in-Docker.
include:
template: Verify/Browser-Performance.gitlab-ci.yml
performance:
browser_performance:
variables:
URL: https://example.com
```
WARNING:
In GitLab 14.0 and later, the job [is scheduled to be renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/225914)
from `performance` to `browser_performance`.
In GitLab 13.12 and earlier, the job [was named](https://gitlab.com/gitlab-org/gitlab/-/issues/225914) `performance`.
The above example:
- Creates a `performance` job in your CI/CD pipeline and runs sitespeed.io against the webpage you
- Creates a `browser_performance` job in your CI/CD pipeline and runs sitespeed.io against the webpage you
defined in `URL` to gather key metrics.
- Uses a template that doesn't work with Kubernetes clusters. If you are using a Kubernetes cluster,
use [`template: Jobs/Browser-Performance-Testing.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml)
@ -90,7 +89,7 @@ The above example:
GitLab 12.3 or earlier, you must [add the configuration manually](#gitlab-versions-132-and-earlier).
The template uses the [GitLab plugin for sitespeed.io](https://gitlab.com/gitlab-org/gl-performance),
and it saves the full HTML sitespeed.io report as a [Browser Performance report artifact](../../../ci/yaml/README.md#artifactsreportsperformance)
and it saves the full HTML sitespeed.io report as a [Browser Performance report artifact](../../../ci/yaml/README.md#artifactsreportsbrowser_performance)
that you can later download and analyze. This implementation always takes the latest
Browser Performance artifact available. If [GitLab Pages](../pages/index.md) is enabled,
you can view the report directly in your browser.
@ -108,7 +107,7 @@ makes on the given URL, and change the version:
include:
template: Verify/Browser-Performance.gitlab-ci.yml
performance:
browser_performance:
variables:
URL: https://www.sitespeed.io/
SITESPEED_VERSION: 13.2.0
@ -127,7 +126,7 @@ if the `Total Score` metric degrades by 5 points or more:
include:
template: Verify/Browser-Performance.gitlab-ci.yml
performance:
browser_performance:
variables:
URL: https://example.com
DEGRADATION_THRESHOLD: 5
@ -140,13 +139,13 @@ The `Total Score` metric is based on sitespeed.io's [coach performance score](ht
The above CI YAML configuration is great for testing against static environments, and it can
be extended for dynamic environments, but a few extra steps are required:
1. The `performance` job should run after the dynamic environment has started.
1. The `browser_performance` job should run after the dynamic environment has started.
1. In the `review` job:
1. Generate a URL list file with the dynamic URL.
1. Save the file as an artifact, for example with `echo $CI_ENVIRONMENT_URL > environment_url.txt`
in your job's `script`.
1. Pass the list as the URL environment variable (which can be a URL or a file containing URLs)
to the `performance` job.
to the `browser_performance` job.
1. You can now run the sitespeed.io container against the desired hostname and
paths.
@ -176,7 +175,7 @@ review:
except:
- master
performance:
browser_performance:
dependencies:
- review
variables:

View File

@ -8,6 +8,7 @@ module API
expose :ip_address
expose :active
expose :instance_type?, as: :is_shared
expose :runner_type
expose :name
expose :online?, as: :online
expose :status

View File

@ -117,7 +117,7 @@ module API
not_allowed! # This currently can only be reached in EE
elsif member.valid? && member.persisted?
present_members(member)
Gitlab::Tracking.event(::Members::CreateService.name, 'create_member', label: params[:invite_source], property: 'existing_user')
Gitlab::Tracking.event(::Members::CreateService.name, 'create_member', label: params[:invite_source], property: 'existing_user', user: current_user)
else
render_validation_error!(member)
end

View File

@ -11,7 +11,7 @@
# * test: TEST_DISABLED
# * code_quality: CODE_QUALITY_DISABLED
# * license_management: LICENSE_MANAGEMENT_DISABLED
# * performance: PERFORMANCE_DISABLED
# * browser_performance: PERFORMANCE_DISABLED
# * load_performance: LOAD_PERFORMANCE_DISABLED
# * sast: SAST_DISABLED
# * secret_detection: SECRET_DETECTION_DISABLED

View File

@ -1,6 +1,6 @@
# Read more about the feature here: https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html
performance:
browser_performance:
stage: performance
image: docker:19.03.12
allow_failure: true
@ -72,6 +72,6 @@ performance:
rules:
- if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""'
when: never
- if: '$PERFORMANCE_DISABLED'
- if: '$BROWSER_PERFORMANCE_DISABLED'
when: never
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'

View File

@ -72,6 +72,6 @@ browser_performance:
rules:
- if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""'
when: never
- if: '$PERFORMANCE_DISABLED'
- if: '$BROWSER_PERFORMANCE_DISABLED'
when: never
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'

View File

@ -7,7 +7,7 @@ code_quality:
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.23"
CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.24"
needs: []
script:
- export SOURCE_CODE=$PWD

View File

@ -1,6 +1,6 @@
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/ruby/tags/
image: "ruby:2.5"
image: ruby:latest
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.

View File

@ -6,7 +6,7 @@ stages:
- deploy
- performance
performance:
browser_performance:
stage: performance
image: docker:git
variables:

View File

@ -112,7 +112,9 @@ module Gitlab
end
def yaml_variables_for(job_name)
job = jobs.fetch(job_name)
job = jobs[job_name]
return [] unless job
Gitlab::Ci::Variables::Helpers.inherit_yaml_variables(
from: root_variables,
@ -122,9 +124,7 @@ module Gitlab
end
def stage_for(job_name)
job = jobs.fetch(job_name)
job[:stage]
jobs.dig(job_name, :stage)
end
private

View File

@ -83,7 +83,9 @@ module Gitlab
{
id: runner.id,
description: runner.description,
runner_type: runner.runner_type,
active: runner.active?,
is_shared: runner.instance_type?,
tags: runner.tags&.map(&:name)
}
end

View File

@ -79,7 +79,9 @@ module Gitlab
{
id: runner.id,
description: runner.description,
runner_type: runner.runner_type,
active: runner.active?,
is_shared: runner.instance_type?,
tags: runner.tags&.map(&:name)
}
end

View File

@ -9,7 +9,7 @@ module Gitlab
@data = {}
end
delegate :[], :[]=, to: :@data
delegate :[], :[]=, :key?, to: :@data
def markdown_fields
@data.keys

View File

@ -240,9 +240,7 @@ namespace :gitlab do
desc 'Run migrations with instrumentation'
task migration_testing: :environment do
result_dir = Gitlab::Database::Migrations::Instrumentation::RESULT_DIR
raise "Directory exists already, won't overwrite: #{result_dir}" if File.exist?(result_dir)
Dir.mkdir(result_dir)
FileUtils.mkdir_p(result_dir)
verbose_was = ActiveRecord::Migration.verbose
ActiveRecord::Migration.verbose = true

View File

@ -8319,42 +8319,9 @@ msgstr ""
msgid "ComplianceFramework|Edit Compliance Framework"
msgstr ""
msgid "ComplianceFramework|GDPR"
msgstr ""
msgid "ComplianceFramework|GDPR - General Data Protection Regulation"
msgstr ""
msgid "ComplianceFramework|HIPAA"
msgstr ""
msgid "ComplianceFramework|HIPAA - Health Insurance Portability and Accountability Act"
msgstr ""
msgid "ComplianceFramework|New Compliance Framework"
msgstr ""
msgid "ComplianceFramework|PCI-DSS"
msgstr ""
msgid "ComplianceFramework|PCI-DSS - Payment Card Industry-Data Security Standard"
msgstr ""
msgid "ComplianceFramework|SOC 2"
msgstr ""
msgid "ComplianceFramework|SOC 2 - Service Organization Control 2"
msgstr ""
msgid "ComplianceFramework|SOX"
msgstr ""
msgid "ComplianceFramework|SOX - Sarbanes-Oxley"
msgstr ""
msgid "ComplianceFramework|This project is regulated by %{framework}."
msgstr ""
msgid "Component"
msgstr ""
@ -39282,9 +39249,6 @@ msgstr ""
msgid "must be greater than start date"
msgstr ""
msgid "must contain only valid frameworks"
msgstr ""
msgid "my-awesome-group"
msgstr ""

View File

@ -17,7 +17,7 @@ module QA
%w[
CODE_QUALITY_DISABLED TEST_DISABLED LICENSE_MANAGEMENT_DISABLED
SAST_DISABLED DAST_DISABLED DEPENDENCY_SCANNING_DISABLED
CONTAINER_SCANNING_DISABLED PERFORMANCE_DISABLED SECRET_DETECTION_DISABLED
CONTAINER_SCANNING_DISABLED BROWSER_PERFORMANCE_DISABLED SECRET_DETECTION_DISABLED
].each do |key|
Resource::CiVariable.fabricate_via_api! do |resource|
resource.project = @project

View File

@ -23,7 +23,7 @@ regenerating the Startup CSS files.
**What should I do now?**
IMPORTANT: Please make sure to update your MR title with `[RUN AS-IF-FOSS]` and start a new MR pipeline
IMPORTANT: Please make sure to update your MR title with "[RUN AS-IF-FOSS]" and start a new MR pipeline
To fix this job, consider one of the following options:

View File

@ -12,7 +12,9 @@ RSpec.describe ConfirmationsController do
describe '#show' do
render_views
subject { get :show, params: { confirmation_token: confirmation_token } }
def perform_request
get :show, params: { confirmation_token: confirmation_token }
end
context 'user is already confirmed' do
let_it_be_with_reload(:user) { create(:user, :unconfirmed) }
@ -20,20 +22,37 @@ RSpec.describe ConfirmationsController do
before do
user.confirm
subject
end
it 'renders `new`' do
perform_request
expect(response).to render_template(:new)
end
it 'displays an error message' do
perform_request
expect(response.body).to include('Email was already confirmed, please try signing in')
end
it 'does not display the email of the user' do
perform_request
expect(response.body).not_to include(user.email)
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:show).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => user.username,
'meta.caller_id' => 'ConfirmationsController#show')
end
perform_request
end
end
context 'user accesses the link after the expiry of confirmation token has passed' do
@ -42,39 +61,64 @@ RSpec.describe ConfirmationsController do
before do
allow(Devise).to receive(:confirm_within).and_return(1.day)
travel_to(3.days.from_now) do
subject
end
end
it 'renders `new`' do
travel_to(3.days.from_now) { perform_request }
expect(response).to render_template(:new)
end
it 'displays an error message' do
travel_to(3.days.from_now) { perform_request }
expect(response.body).to include('Email needs to be confirmed within 1 day, please request a new one below')
end
it 'does not display the email of the user' do
travel_to(3.days.from_now) { perform_request }
expect(response.body).not_to include(user.email)
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:show).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => user.username,
'meta.caller_id' => 'ConfirmationsController#show')
end
travel_to(3.days.from_now) { perform_request }
end
end
context 'with an invalid confirmation token' do
let(:confirmation_token) { 'invalid_confirmation_token' }
before do
subject
end
it 'renders `new`' do
perform_request
expect(response).to render_template(:new)
end
it 'displays an error message' do
perform_request
expect(response.body).to include('Confirmation token is invalid')
end
it 'sets the the caller_id in the context' do
expect(controller).to receive(:show).and_wrap_original do |m, *args|
expect(Gitlab::ApplicationContext.current)
.to include('meta.caller_id' => 'ConfirmationsController#show')
m.call(*args)
end
perform_request
end
end
end
end

View File

@ -293,6 +293,18 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
expect(request.env['warden']).to be_authenticated
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:atlassian_oauth2).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => user.username,
'meta.caller_id' => 'OmniauthCallbacksController#atlassian_oauth2')
end
post :atlassian_oauth2
end
end
context 'for a new user' do
@ -454,6 +466,18 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
it 'doesn\'t link a new identity to the user' do
expect { post :saml, params: { SAMLResponse: mock_saml_response } }.not_to change { user.identities.count }
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:saml).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => user.username,
'meta.caller_id' => 'OmniauthCallbacksController#saml')
end
post :saml, params: { SAMLResponse: mock_saml_response }
end
end
end

View File

@ -77,6 +77,18 @@ RSpec.describe PasswordsController do
expect(user.password_expires_at).not_to be_nil
end
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:update).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => user.username,
'meta.caller_id' => 'PasswordsController#update')
end
subject
end
end
end
end

View File

@ -434,6 +434,18 @@ RSpec.describe RegistrationsController do
expect(User.last.last_name).to eq(base_user_params[:last_name])
expect(User.last.name).to eq("#{base_user_params[:first_name]} #{base_user_params[:last_name]}")
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:create).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => base_user_params[:username],
'meta.caller_id' => 'RegistrationsController#create')
end
subject
end
end
describe '#destroy' do
@ -525,5 +537,17 @@ RSpec.describe RegistrationsController do
end
end
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:destroy).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
.to include('meta.user' => user.username,
'meta.caller_id' => 'RegistrationsController#destroy')
end
post :destroy
end
end
end

View File

@ -3,20 +3,19 @@
require 'spec_helper'
RSpec.describe 'Groups > Members > Manage members' do
include Select2Helper
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let(:user1) { create(:user, name: 'John Doe') }
let(:user2) { create(:user, name: 'Mary Jane') }
let(:group) { create(:group) }
let_it_be(:user1) { create(:user, name: 'John Doe') }
let_it_be(:user2) { create(:user, name: 'Mary Jane') }
let_it_be(:group) { create(:group) }
before do
sign_in(user1)
end
shared_examples 'includes the correct Invite link' do |should_include, should_not_include|
it 'includes either the form or the modal trigger' do
it 'includes either the form or the modal trigger', :aggregate_failures do
group.add_owner(user1)
visit group_group_members_path(group)
@ -27,12 +26,12 @@ RSpec.describe 'Groups > Members > Manage members' do
end
shared_examples 'does not include either invite modal or either invite form' do
it 'does not include either of the invite members or invite group modal buttons' do
it 'does not include either of the invite members or invite group modal buttons', :aggregate_failures do
expect(page).not_to have_selector '.js-invite-members-modal'
expect(page).not_to have_selector '.js-invite-group-modal'
end
it 'does not include either of the invite users or invite group forms' do
it 'does not include either of the invite users or invite group forms', :aggregate_failures do
expect(page).not_to have_selector '.invite-users-form'
expect(page).not_to have_selector '.invite-group-form'
end
@ -66,7 +65,7 @@ RSpec.describe 'Groups > Members > Manage members' do
end
end
it 'add user to group', :js do
it 'add user to group', :js, :snowplow, :aggregate_failures do
group.add_owner(user1)
visit group_group_members_path(group)
@ -77,6 +76,13 @@ RSpec.describe 'Groups > Members > Manage members' do
expect(page).to have_content(user2.name)
expect(page).to have_button('Reporter')
end
expect_snowplow_event(
category: 'Members::CreateService',
action: 'create_member',
label: 'unknown',
property: 'existing_user'
)
end
it 'do not disclose email addresses', :js do
@ -143,11 +149,13 @@ RSpec.describe 'Groups > Members > Manage members' do
wait_for_requests
expect(page).not_to have_content(user2.name)
expect(group.users).not_to include(user2)
aggregate_failures do
expect(page).not_to have_content(user2.name)
expect(group.users).not_to include(user2)
end
end
it 'add yourself to group when already an owner', :js do
it 'add yourself to group when already an owner', :js, :aggregate_failures do
group.add_owner(user1)
visit group_group_members_path(group)
@ -160,7 +168,7 @@ RSpec.describe 'Groups > Members > Manage members' do
end
end
it 'invite user to group', :js do
it 'invite user to group', :js, :snowplow do
group.add_owner(user1)
visit group_group_members_path(group)
@ -170,14 +178,23 @@ RSpec.describe 'Groups > Members > Manage members' do
expect(page).to have_link 'Invited'
click_link 'Invited'
page.within(members_table) do
expect(page).to have_content('test@example.com')
expect(page).to have_content('Invited')
expect(page).to have_button('Reporter')
aggregate_failures do
page.within(members_table) do
expect(page).to have_content('test@example.com')
expect(page).to have_content('Invited')
expect(page).to have_button('Reporter')
end
expect_snowplow_event(
category: 'Members::InviteService',
action: 'create_member',
label: 'unknown',
property: 'net_new_user'
)
end
end
context 'as a guest', :js do
context 'when user is a guest' do
before do
group.add_guest(user1)
group.add_developer(user2)
@ -187,7 +204,7 @@ RSpec.describe 'Groups > Members > Manage members' do
it_behaves_like 'does not include either invite modal or either invite form'
it 'does not include a button on the members page list to manage or remove the existing member', :js do
it 'does not include a button on the members page list to manage or remove the existing member', :js, :aggregate_failures do
page.within(second_row) do
# Can not modify user2 role
expect(page).not_to have_button 'Developer'
@ -198,7 +215,7 @@ RSpec.describe 'Groups > Members > Manage members' do
end
end
context 'As a guest when the :invite_members_group_modal feature flag is disabled', :js do
context 'when user is a guest and the :invite_members_group_modal feature flag is disabled' do
before do
stub_feature_flags(invite_members_group_modal: false)
group.add_guest(user1)
@ -209,7 +226,7 @@ RSpec.describe 'Groups > Members > Manage members' do
it_behaves_like 'does not include either invite modal or either invite form'
it 'does not include a button on the members page list to manage or remove the existing member', :js do
it 'does not include a button on the members page list to manage or remove the existing member', :js, :aggregate_failures do
page.within(second_row) do
# Can not modify user2 role
expect(page).not_to have_button 'Developer'

View File

@ -6,17 +6,17 @@ RSpec.describe 'Project members list', :js do
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let(:user1) { create(:user, name: 'John Doe') }
let(:user2) { create(:user, name: 'Mary Jane') }
let(:group) { create(:group) }
let(:project) { create(:project, :internal, namespace: group) }
let_it_be(:user1) { create(:user, name: 'John Doe') }
let_it_be(:user2) { create(:user, name: 'Mary Jane') }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :internal, namespace: group) }
before do
sign_in(user1)
group.add_owner(user1)
end
it 'show members from project and group' do
it 'show members from project and group', :aggregate_failures do
project.add_developer(user2)
visit_members_page
@ -25,7 +25,7 @@ RSpec.describe 'Project members list', :js do
expect(second_row).to have_content(user2.name)
end
it 'show user once if member of both group and project' do
it 'show user once if member of both group and project', :aggregate_failures do
project.add_developer(user1)
visit_members_page
@ -47,7 +47,7 @@ RSpec.describe 'Project members list', :js do
end
end
it 'add user to project' do
it 'add user to project', :snowplow, :aggregate_failures do
visit_members_page
invite_member(user2.name, role: 'Reporter')
@ -55,9 +55,16 @@ RSpec.describe 'Project members list', :js do
page.within find_member_row(user2) do
expect(page).to have_button('Reporter')
end
expect_snowplow_event(
category: 'Members::CreateService',
action: 'create_member',
label: 'unknown',
property: 'existing_user'
)
end
it 'uses ProjectMember access_level_roles for the invite members modal access option' do
it 'uses ProjectMember access_level_roles for the invite members modal access option', :aggregate_failures do
visit_members_page
click_on 'Invite members'
@ -95,7 +102,7 @@ RSpec.describe 'Project members list', :js do
expect(members_table).not_to have_content(other_user.name)
end
it 'invite user to project' do
it 'invite user to project', :snowplow, :aggregate_failures do
visit_members_page
invite_member('test@example.com', role: 'Reporter')
@ -105,6 +112,13 @@ RSpec.describe 'Project members list', :js do
page.within find_invited_member_row('test@example.com') do
expect(page).to have_button('Reporter')
end
expect_snowplow_event(
category: 'Members::InviteService',
action: 'create_member',
label: 'unknown',
property: 'net_new_user'
)
end
context 'as a signed out visitor viewing a public project' do
@ -128,7 +142,7 @@ RSpec.describe 'Project members list', :js do
project.add_maintainer(project_bot)
end
it 'does not show form used to change roles and "Expiration date" or the remove user button' do
it 'does not show form used to change roles and "Expiration date" or the remove user button', :aggregate_failures do
visit_members_page
page.within find_member_row(project_bot) do

View File

@ -3,8 +3,8 @@
require 'spec_helper'
RSpec.describe 'User views an empty project' do
let(:project) { create(:project, :empty_repo) }
let(:user) { create(:user) }
let_it_be(:project) { create(:project, :empty_repo) }
let_it_be(:user) { create(:user) }
shared_examples 'allowing push to default branch' do
it 'shows push-to-master instructions' do
@ -14,17 +14,25 @@ RSpec.describe 'User views an empty project' do
end
end
describe 'as a maintainer' do
context 'when user is a maintainer' do
before do
project.add_maintainer(user)
sign_in(user)
end
it_behaves_like 'allowing push to default branch'
it 'shows a link for inviting members and launches invite modal', :js do
visit project_path(project)
click_button 'Invite members'
expect(page).to have_content("You're inviting members to the")
end
end
describe 'as an admin' do
let(:user) { create(:user, :admin) }
context 'when user is an admin' do
let_it_be(:user) { create(:user, :admin) }
context 'when admin mode is enabled' do
before do
@ -44,16 +52,17 @@ RSpec.describe 'User views an empty project' do
end
end
describe 'as a developer' do
context 'when user is a developer' do
before do
project.add_developer(user)
sign_in(user)
end
it 'does not show push-to-master instructions' do
it 'does not show push-to-master instructions nor invite members link', :aggregate_failures, :js do
visit project_path(project)
expect(page).not_to have_content('git push -u origin master')
expect(page).not_to have_button(text: 'Invite members')
end
end
end

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Runner (JavaScript fixtures)' do
include AdminModeHelper
include ApiHelpers
include JavaScriptFixturesHelpers
include GraphqlHelpers
let_it_be(:admin) { create(:admin) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, version: '1.0.0', revision: '123', description: 'Instance runner', ip_address: '127.0.0.1') }
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner', ip_address: '127.0.0.1') }
let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project], active: false, version: '2.0.0', revision: '456', description: 'Project runner', ip_address: '127.0.0.1') }
query_path = 'runner/graphql/'
fixtures_path = 'graphql/runner/'
before(:all) do
clean_frontend_fixtures(fixtures_path)
end
after(:all) do
remove_repository(project)
end
before do
sign_in(admin)
enable_admin_mode!(admin)
end
describe GraphQL::Query, type: :request do
get_runners_query_name = 'get_runners.query.graphql'
let_it_be(:query) do
get_graphql_query_as_string("#{query_path}#{get_runners_query_name}", [
'runner/graphql/runner_node.fragment.graphql',
'graphql_shared/fragments/pageInfo.fragment.graphql'
])
end
it "#{fixtures_path}#{get_runners_query_name}.json" do
post_graphql(query, current_user: admin, variables: {})
expect_graphql_errors_to_be_empty
end
it "#{fixtures_path}#{get_runners_query_name}.paginated.json" do
post_graphql(query, current_user: admin, variables: { first: 2 })
expect_graphql_errors_to_be_empty
end
end
describe GraphQL::Query, type: :request do
get_runner_query_name = 'get_runner.query.graphql'
let_it_be(:query) { get_graphql_query_as_string("#{query_path}#{get_runner_query_name}") }
it "#{fixtures_path}#{get_runner_query_name}.json" do
post_graphql(query, current_user: admin, variables: {
id: instance_runner.to_global_id.to_s
})
expect_graphql_errors_to_be_empty
end
end
end

View File

@ -15,6 +15,7 @@ const isProject = false;
const inviteeType = 'members';
const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 };
const defaultAccessLevel = 10;
const inviteSource = 'unknown';
const helpLink = 'https://example.com';
const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' };
@ -173,6 +174,7 @@ describe('InviteMembersModal', () => {
user_id: '1',
access_level: defaultAccessLevel,
expires_at: undefined,
invite_source: inviteSource,
format: 'json',
};
@ -245,6 +247,7 @@ describe('InviteMembersModal', () => {
access_level: defaultAccessLevel,
expires_at: undefined,
email: 'email@example.com',
invite_source: inviteSource,
format: 'json',
};
@ -293,6 +296,7 @@ describe('InviteMembersModal', () => {
const postData = {
access_level: defaultAccessLevel,
expires_at: undefined,
invite_source: inviteSource,
format: 'json',
};
@ -308,20 +312,39 @@ describe('InviteMembersModal', () => {
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'showToastMessageSuccess');
jest.spyOn(wrapper.vm, 'trackInvite');
});
describe('when triggered from regular mounting', () => {
beforeEach(() => {
clickInviteButton();
});
it('calls Api inviteGroupMembersByEmail with the correct params', () => {
expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, emailPostData);
});
it('calls Api addGroupMembersByUserId with the correct params', () => {
expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, idPostData);
});
it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
});
});
it('calls Apis with the invite source passed through to openModal', () => {
wrapper.vm.openModal({ inviteeType: 'members', source: '_invite_source_' });
clickInviteButton();
});
it('calls Api inviteGroupMembersByEmail with the correct params', () => {
expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, emailPostData);
});
it('calls Api addGroupMembersByUserId with the correct params', () => {
expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, idPostData);
});
it('displays the successful toastMessage', () => {
expect(wrapper.vm.showToastMessageSuccess).toHaveBeenCalled();
expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, {
...emailPostData,
invite_source: '_invite_source_',
});
expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, {
...idPostData,
invite_source: '_invite_source_',
});
});
});
@ -403,18 +426,11 @@ describe('InviteMembersModal', () => {
});
describe('tracking', () => {
const postData = {
user_id: '1',
access_level: defaultAccessLevel,
expires_at: undefined,
format: 'json',
};
beforeEach(() => {
wrapper = createComponent({ newUsersToInvite: [user3] });
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({});
});
it('tracks the invite', () => {

View File

@ -1,5 +1,6 @@
import { GlBadge, GlLink, GlIcon } from '@gitlab/ui';
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import TableRow from '~/repository/components/table/row.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
@ -19,6 +20,9 @@ function factory(propsData = {}) {
projectPath: 'gitlab-org/gitlab-ce',
url: `https://test.com`,
},
directives: {
GlHoverLoad: createMockDirective(),
},
provide: {
glFeatures: { refactorBlobViewer: true },
},
@ -34,6 +38,8 @@ function factory(propsData = {}) {
}
describe('Repository table row component', () => {
const findRouterLink = () => vm.find(RouterLinkStub);
afterEach(() => {
vm.destroy();
});
@ -81,6 +87,21 @@ describe('Repository table row component', () => {
});
});
it('renders a gl-hover-load directive', () => {
factory({
id: '1',
sha: '123',
path: 'test',
type: 'blob',
currentPath: '/',
});
const hoverLoadDirective = getBinding(findRouterLink().element, 'gl-hover-load');
expect(hoverLoadDirective).not.toBeUndefined();
expect(hoverLoadDirective.value).toBeInstanceOf(Function);
});
it.each`
type | component | componentName
${'tree'} | ${RouterLinkStub} | ${'RouterLink'}

View File

@ -68,7 +68,7 @@ describe('RunnerList', () => {
});
it('Displays a list of runners', () => {
expect(findRows()).toHaveLength(2);
expect(findRows()).toHaveLength(3);
expect(findSkeletonLoader().exists()).toBe(false);
});
@ -77,7 +77,7 @@ describe('RunnerList', () => {
const { id, description, version, ipAddress, shortSha } = mockRunners[0];
// Badges
expect(findCell({ fieldKey: 'type' }).text()).toMatchInterpolatedText('shared locked');
expect(findCell({ fieldKey: 'type' }).text()).toMatchInterpolatedText('specific paused');
// Runner identifier
expect(findCell({ fieldKey: 'name' }).text()).toContain(

View File

@ -1,44 +1,6 @@
export const runnersData = {
data: {
runners: {
nodes: [
{
id: 'gid://gitlab/Ci::Runner/1',
description: 'runner-1',
runnerType: 'INSTANCE_TYPE',
shortSha: '2P6oDVDm',
version: '13.12.0',
revision: '11223344',
ipAddress: '127.0.0.1',
active: true,
locked: true,
tagList: [],
contactedAt: '2021-05-14T11:44:03Z',
__typename: 'CiRunner',
},
{
id: 'gid://gitlab/Ci::Runner/2',
description: 'runner-2',
runnerType: 'GROUP_TYPE',
shortSha: 'dpSCAC31',
version: '13.12.0',
revision: '11223344',
ipAddress: '127.0.0.1',
active: true,
locked: true,
tagList: [],
contactedAt: '2021-05-14T11:44:02Z',
__typename: 'CiRunner',
},
],
pageInfo: {
endCursor: 'GRAPHQL_END_CURSOR',
startCursor: 'GRAPHQL_START_CURSOR',
hasNextPage: true,
hasPreviousPage: false,
__typename: 'PageInfo',
},
__typename: 'CiRunnerConnection',
},
},
};
// Fixtures generated by: spec/frontend/fixtures/runner.rb
export const runnersData = getJSONFixture('graphql/runner/get_runners.query.graphql.json');
export const runnersDataPaginated = getJSONFixture(
'graphql/runner/get_runners.query.graphql.paginated.json',
);
export const runnerData = getJSONFixture('graphql/runner/get_runner.query.graphql.json');

View File

@ -3,12 +3,15 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerTypeBadge from '~/runner/components/runner_type_badge.vue';
import { INSTANCE_TYPE } from '~/runner/constants';
import getRunnerQuery from '~/runner/graphql/get_runner.query.graphql';
import RunnerDetailsApp from '~/runner/runner_details/runner_details_app.vue';
const mockRunnerId = '55';
import { runnerData } from '../mock_data';
const mockRunnerGraphqlId = runnerData.data.runner.id;
const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
const localVue = createLocalVue();
localVue.use(VueApollo);
@ -35,15 +38,7 @@ describe('RunnerDetailsApp', () => {
};
beforeEach(async () => {
mockRunnerQuery = jest.fn().mockResolvedValue({
data: {
runner: {
id: `gid://gitlab/Ci::Runner/${mockRunnerId}`,
runnerType: INSTANCE_TYPE,
__typename: 'CiRunner',
},
},
});
mockRunnerQuery = jest.fn().mockResolvedValue(runnerData);
});
afterEach(() => {
@ -54,13 +49,13 @@ describe('RunnerDetailsApp', () => {
it('expect GraphQL ID to be requested', async () => {
await createComponentWithApollo();
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: `gid://gitlab/Ci::Runner/${mockRunnerId}` });
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
});
it('displays the runner id', async () => {
await createComponentWithApollo();
expect(wrapper.text()).toContain('Runner #55');
expect(wrapper.text()).toContain(`Runner #${mockRunnerId}`);
});
it('displays the runner type', async () => {

View File

@ -24,12 +24,10 @@ import {
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
import RunnerListApp from '~/runner/runner_list/runner_list_app.vue';
import { runnersData } from '../mock_data';
import { runnersData, runnersDataPaginated } from '../mock_data';
const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
const mockActiveRunnersCount = 2;
const mocKRunners = runnersData.data.runners.nodes;
const mockPageInfo = runnersData.data.runners.pageInfo;
jest.mock('@sentry/browser');
jest.mock('~/lib/utils/url_utility', () => ({
@ -98,7 +96,7 @@ describe('RunnerListApp', () => {
});
it('shows the runners list', () => {
expect(mocKRunners).toMatchObject(findRunnerList().props('runners'));
expect(runnersData.data.runners.nodes).toMatchObject(findRunnerList().props('runners'));
});
it('requests the runners with no filters', () => {
@ -205,6 +203,8 @@ describe('RunnerListApp', () => {
describe('Pagination', () => {
beforeEach(() => {
mockRunnersQuery = jest.fn().mockResolvedValue(runnersDataPaginated);
createComponentWithApollo({ mountFn: mount });
});
@ -225,13 +225,7 @@ describe('RunnerListApp', () => {
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
sort: CREATED_DESC,
first: RUNNER_PAGE_SIZE,
after: expect.any(String),
});
expect(mockRunnersQuery).toHaveBeenLastCalledWith({
sort: CREATED_DESC,
first: RUNNER_PAGE_SIZE,
after: mockPageInfo.endCursor,
after: runnersDataPaginated.data.runners.pageInfo.endCursor,
});
});
});

View File

@ -54,14 +54,22 @@ module Gitlab
YAML
end
subject(:yaml_variables_for) { result.yaml_variables_for(:job) }
let(:job_name) { :job }
it do
subject(:yaml_variables_for) { result.yaml_variables_for(job_name) }
it 'returns calculated variables with root and job variables' do
is_expected.to match_array([
{ key: 'VAR1', value: 'value 11', public: true },
{ key: 'VAR2', value: 'value 2', public: true }
])
end
context 'when an absent job is sent' do
let(:job_name) { :invalid_job }
it { is_expected.to eq([]) }
end
end
describe '#stage_for' do
@ -72,10 +80,16 @@ module Gitlab
YAML
end
subject(:stage_for) { result.stage_for(:job) }
let(:job_name) { :job }
it do
is_expected.to eq('test')
subject(:stage_for) { result.stage_for(job_name) }
it { is_expected.to eq('test') }
context 'when an absent job is sent' do
let(:job_name) { :invalid_job }
it { is_expected.to be_nil }
end
end
end

View File

@ -47,6 +47,8 @@ RSpec.describe Gitlab::DataBuilder::Build do
it { expect(data[:runner][:id]).to eq(build.runner.id) }
it { expect(data[:runner][:tags]).to match_array(tag_names) }
it { expect(data[:runner][:description]).to eq(build.runner.description) }
it { expect(data[:runner][:runner_type]).to eq(build.runner.runner_type) }
it { expect(data[:runner][:is_shared]).to eq(build.runner.instance_type?) }
it { expect(data[:environment]).to be_nil }
context 'commit author_url' do

View File

@ -58,8 +58,10 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
it 'has runner attributes', :aggregate_failures do
expect(runner_data[:id]).to eq(ci_runner.id)
expect(runner_data[:description]).to eq(ci_runner.description)
expect(runner_data[:runner_type]).to eq(ci_runner.runner_type)
expect(runner_data[:active]).to eq(ci_runner.active)
expect(runner_data[:tags]).to match_array(tag_names)
expect(runner_data[:is_shared]).to eq(ci_runner.instance_type?)
end
end

View File

@ -12,4 +12,11 @@ RSpec.describe Gitlab::MarkdownCache::FieldData do
it 'translates a markdown field name into a html field name' do
expect(field_data.html_field(:description)).to eq("description_html")
end
describe '#key?' do
specify do
expect(field_data.key?(:description)).to be_truthy
expect(field_data.key?(:something_else)).to be_falsy
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!('remove_builds_email_service_from_services')
RSpec.describe RemoveBuildsEmailServiceFromServices do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:services) { table(:services) }
let(:namespace) { namespaces.create!(name: 'foo', path: 'bar') }
let(:project) { projects.create!(namespace_id: namespace.id) }
it 'correctly deletes `BuildsEmailService` services' do
services.create!(project_id: project.id, type: 'BuildsEmailService')
services.create!(project_id: project.id, type: 'OtherService')
expect(services.all.pluck(:type)).to match_array %w[BuildsEmailService OtherService]
migrate!
expect(services.all.pluck(:type)).to eq %w[OtherService]
end
end

View File

@ -408,6 +408,20 @@ RSpec.describe Member do
it { is_expected.not_to include @member_with_minimal_access }
end
describe '.without_invites_and_requests' do
subject { described_class.without_invites_and_requests.to_a }
it { is_expected.to include @owner }
it { is_expected.to include @maintainer }
it { is_expected.not_to include @invited_member }
it { is_expected.to include @accepted_invite_member }
it { is_expected.not_to include @requested_member }
it { is_expected.to include @accepted_request_member }
it { is_expected.to include @blocked_maintainer }
it { is_expected.to include @blocked_developer }
it { is_expected.not_to include @member_with_minimal_access }
end
describe '.connected_to_user' do
subject { described_class.connected_to_user.to_a }

View File

@ -137,11 +137,11 @@ RSpec.describe API::Ci::Runners do
get api('/runners/all', admin)
expect(json_response).to match_array [
a_hash_including('description' => 'Project runner'),
a_hash_including('description' => 'Two projects runner'),
a_hash_including('description' => 'Group runner A'),
a_hash_including('description' => 'Group runner B'),
a_hash_including('description' => 'Shared runner')
a_hash_including('description' => 'Project runner', 'is_shared' => false, 'runner_type' => 'project_type'),
a_hash_including('description' => 'Two projects runner', 'is_shared' => false, 'runner_type' => 'project_type'),
a_hash_including('description' => 'Group runner A', 'is_shared' => false, 'runner_type' => 'group_type'),
a_hash_including('description' => 'Group runner B', 'is_shared' => false, 'runner_type' => 'group_type'),
a_hash_including('description' => 'Shared runner', 'is_shared' => true, 'runner_type' => 'instance_type')
]
end

View File

@ -14,6 +14,23 @@ RSpec.describe 'getting group members information' do
[user_1, user_2].each { |user| parent_group.add_guest(user) }
end
context 'when a member is invited only via email' do
before do
create(:group_member, :invited, source: parent_group)
end
it 'returns null in the user field' do
fetch_members(group: parent_group, args: { relations: [:DIRECT] })
expect(graphql_errors).to be_nil
expect(graphql_data_at(:group, :group_members, :edges, :node)).to contain_exactly(
{ 'user' => { 'id' => global_id_of(user_1) } },
{ 'user' => { 'id' => global_id_of(user_2) } },
'user' => nil
)
end
end
context 'when the request is correct' do
it_behaves_like 'a working graphql query' do
before do

View File

@ -50,6 +50,20 @@ RSpec.describe 'getting project members information' do
invited_group.add_guest(invited_user)
end
context 'when a member is invited only via email and current_user is a maintainer' do
before do
parent_project.add_maintainer(user)
create(:project_member, :invited, source: parent_project)
end
it 'returns null in the user field' do
fetch_members(project: parent_project, args: { relations: [:DIRECT] })
expect(graphql_errors).to be_nil
expect(graphql_data_at(:project, :project_members, :edges, :node)).to contain_exactly({ 'user' => { 'id' => global_id_of(user) } }, 'user' => nil)
end
end
it 'returns direct members' do
fetch_members(project: child_project, args: { relations: [:DIRECT] })

View File

@ -162,7 +162,8 @@ RSpec.describe API::Invitations do
category: 'Members::InviteService',
action: 'create_member',
label: 'api',
property: 'net_new_user'
property: 'net_new_user',
user: maintainer
)
end
@ -173,7 +174,8 @@ RSpec.describe API::Invitations do
category: 'Members::InviteService',
action: 'create_member',
label: '_invite_source_',
property: 'net_new_user'
property: 'net_new_user',
user: maintainer
)
end
end

View File

@ -266,7 +266,8 @@ RSpec.describe API::Members do
category: 'Members::CreateService',
action: 'create_member',
label: 'api',
property: 'existing_user'
property: 'existing_user',
user: maintainer
)
end
@ -278,7 +279,8 @@ RSpec.describe API::Members do
category: 'Members::CreateService',
action: 'create_member',
label: '_invite_source_',
property: 'existing_user'
property: 'existing_user',
user: maintainer
)
end
end
@ -321,7 +323,8 @@ RSpec.describe API::Members do
category: 'Members::CreateService',
action: 'create_member',
label: 'api',
property: 'existing_user'
property: 'existing_user',
user: maintainer
)
end
@ -333,7 +336,8 @@ RSpec.describe API::Members do
category: 'Members::CreateService',
action: 'create_member',
label: '_invite_source_',
property: 'existing_user'
property: 'existing_user',
user: maintainer
)
end
end

View File

@ -53,7 +53,10 @@ RSpec.describe Issues::ZoomLinkService do
category: 'IncidentManagement::ZoomIntegration',
action: 'add_zoom_meeting',
label: 'Issue ID',
value: issue.id
value: issue.id,
user: user,
project: project,
namespace: project.namespace
)
end
@ -192,7 +195,10 @@ RSpec.describe Issues::ZoomLinkService do
category: 'IncidentManagement::ZoomIntegration',
action: 'remove_zoom_meeting',
label: 'Issue ID',
value: issue.id
value: issue.id,
user: user,
project: project,
namespace: project.namespace
)
end
end

View File

@ -93,7 +93,8 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
category: described_class.name,
action: 'create_member',
label: 'unknown',
property: 'existing_user'
property: 'existing_user',
user: user
)
end
end
@ -108,7 +109,8 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
category: described_class.name,
action: 'create_member',
label: '_invite_source_',
property: 'existing_user'
property: 'existing_user',
user: user
)
end
end
@ -123,7 +125,8 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
category: described_class.name,
action: 'create_member',
label: 'unknown',
property: 'net_new_user'
property: 'net_new_user',
user: user
)
end
end

View File

@ -67,6 +67,23 @@ RSpec.describe Security::CiConfiguration::SastParserService do
expect(sast_brakeman_level['value']).to eql('1')
end
end
context 'when .gitlab-ci.yml does not include the sast job' do
before do
allow(project.repository).to receive(:blob_data_at).and_return(
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
)
end
it 'populates the current values with the default values' do
expect(secure_analyzers_prefix['value']).to eql('registry.gitlab.com/gitlab-org/security-products/analyzers')
expect(sast_excluded_paths['value']).to eql('spec, test, tests, tmp')
expect(sast_pipeline_stage['value']).to eql('test')
expect(sast_search_max_depth['value']).to eql('4')
expect(brakeman['enabled']).to be(true)
expect(sast_brakeman_level['value']).to eql('1')
end
end
end
end
end

View File

@ -306,7 +306,7 @@ RSpec.describe 'gitlab:db namespace rake task' do
let(:all_migrations) { [double('migration1', version: 1), pending_migration] }
let(:pending_migration) { double('migration2', version: 2) }
let(:filename) { Gitlab::Database::Migrations::Instrumentation::STATS_FILENAME }
let!(:directory) { Dir.mktmpdir }
let(:result_dir) { Dir.mktmpdir }
let(:observations) { %w[some data] }
before do
@ -316,19 +316,17 @@ RSpec.describe 'gitlab:db namespace rake task' do
allow(instrumentation).to receive(:observe).and_yield
allow(Dir).to receive(:mkdir)
allow(File).to receive(:exist?).with(directory).and_return(false)
stub_const('Gitlab::Database::Migrations::Instrumentation::RESULT_DIR', directory)
stub_const('Gitlab::Database::Migrations::Instrumentation::RESULT_DIR', result_dir)
end
after do
FileUtils.rm_rf([directory])
FileUtils.rm_rf(result_dir)
end
it 'fails when the directory already exists' do
expect(File).to receive(:exist?).with(directory).and_return(true)
it 'creates result directory when one does not exist' do
FileUtils.rm_rf(result_dir)
expect { subject }.to raise_error(/Directory exists/)
expect { subject }.to change { Dir.exist?(result_dir) }.from(false).to(true)
end
it 'instruments the pending migration' do
@ -346,7 +344,7 @@ RSpec.describe 'gitlab:db namespace rake task' do
it 'writes observations out to JSON file' do
subject
expect(File.read(File.join(directory, filename))).to eq(observations.to_json)
expect(File.read(File.join(result_dir, filename))).to eq(observations.to_json)
end
end

View File

@ -57,20 +57,24 @@ RSpec.describe 'projects/empty' do
render
expect(rendered).to have_selector('[data-track-event=render]')
expect(rendered).to have_selector('[data-track-label=invite_members_empty_project]', count: 2)
expect(rendered).to have_selector('[data-track-label=invite_members_empty_project]')
expect(rendered).to have_content('Invite your team')
expect(rendered).to have_content('Add members to this project and start collaborating with your team.')
expect(rendered).to have_link('Invite members', href: project_project_members_path(project, sort: :access_level_desc))
expect(rendered).to have_selector('[data-track-event=click_button]')
expect(rendered).to have_selector('.js-invite-members-trigger')
expect(rendered).to have_selector('.js-invite-members-modal')
expect(rendered).to have_selector('[data-label=invite_members_empty_project]')
expect(rendered).to have_selector('[data-event=click_button]')
end
context 'when user does not have permissions to invite members' do
let(:can_import_members) { false }
it 'does not show invite member info' do
it 'does not show invite member info', :aggregate_failures do
render
expect(rendered).not_to have_content('Invite your team')
expect(rendered).not_to have_selector('.js-invite-members-trigger')
expect(rendered).not_to have_selector('.js-invite-members-modal')
end
end
end