Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-21 06:09:03 +00:00
parent e1e342d79b
commit 25c07d7230
39 changed files with 367 additions and 88 deletions

View file

@ -66,6 +66,7 @@ docs-lint links:
- bundle exec nanoc
# Check the internal links
- bundle exec nanoc check internal_links
- bundle exec nanoc check internal_anchors
# Delete the redirect files, rebuild, and check internal links again, to see if we are linking to redirects.
# Don't delete the documentation/index.md, which is a false positive for the simple grep.
- grep -rl "redirect_to:" /tmp/gitlab-docs/content/ee/ | grep -v "development/documentation/index.md" | xargs rm -f
@ -74,7 +75,7 @@ docs-lint links:
- echo -e "\e[1;96mMake sure all links point to the correct page."
- bundle exec nanoc check internal_links
# Check the internal anchor links
- bundle exec nanoc check internal_anchors
ui-docs-links lint:
extends:

View file

@ -140,7 +140,7 @@ function renderMermaids($els) {
'Warning: Displaying this diagram might cause performance issues on this page.',
)}</div>
<div class="gl-alert-actions">
<button class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md new-gl-button">Display</button>
<button class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md gl-button">Display</button>
</div>
</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">

View file

@ -213,6 +213,8 @@ export default {
ref="header"
:class="{ 'gl-z-dropdown-menu!': moreActionsShown }"
class="js-file-title file-title file-title-flex-parent"
data-qa-selector="file_title_container"
:data-qa-file-name="filePath"
@click.self="handleToggleFile"
>
<div class="file-header-content">
@ -307,6 +309,7 @@ export default {
right
toggle-class="btn-icon js-diff-more-actions"
class="gl-pt-0!"
data-qa-selector="dropdown_button"
@show="setMoreActionsShown(true)"
@hidden="setMoreActionsShown(false)"
>
@ -340,6 +343,7 @@ export default {
ref="ideEditButton"
:href="diffFile.ide_edit_path"
class="js-ide-edit-blob"
data-qa-selector="edit_in_ide_button"
>
{{ __('Edit in Web IDE') }}
</gl-dropdown-item>

View file

@ -11,7 +11,7 @@ export default {
GlTooltip: GlTooltipDirective,
},
computed: {
...mapState(['currentActivityView']),
...mapState(['currentActivityView', 'stagedFiles']),
},
methods: {
...mapActions(['updateActivityBarView']),
@ -81,6 +81,9 @@ export default {
@click.prevent="changedActivityView($event, $options.leftSidebarViews.commit.name)"
>
<gl-icon name="commit" />
<div v-if="stagedFiles.length > 0" class="ide-commit-badge badge badge-pill">
{{ stagedFiles.length }}
</div>
</button>
</li>
</ul>

View file

@ -108,6 +108,7 @@ export default {
class="d-flex"
icon="remove"
icon-classes="mr-2"
data-qa-selector="delete_button"
@click="deleteEntry(path)"
/>
</li>

View file

@ -1,6 +1,6 @@
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import { GlButton, GlModalDirective } from '@gitlab/ui';
import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../event_hub';
import { integrationLevels } from '../constants';
@ -28,9 +28,17 @@ export default {
GlButton,
},
directives: {
'gl-modal': GlModalDirective,
GlModal: GlModalDirective,
SafeHtml,
},
mixins: [glFeatureFlagsMixin()],
props: {
helpHtml: {
type: String,
required: false,
default: '',
},
},
computed: {
...mapGetters(['currentKey', 'propsSource', 'isDisabled']),
...mapState([
@ -80,11 +88,17 @@ export default {
this.fetchResetIntegration();
},
},
helpHtmlConfig: {
ADD_TAGS: ['use'], // to support icon SVGs
},
};
</script>
<template>
<div>
<!-- helpHtml is trusted input -->
<div v-if="helpHtml" v-safe-html:[$options.helpHtmlConfig]="helpHtml"></div>
<override-dropdown
v-if="defaultState !== null"
:inherit-from-id="defaultState.id"
@ -92,6 +106,7 @@ export default {
:learn-more-path="propsSource.learnMorePath"
@change="setOverride"
/>
<active-checkbox v-if="propsSource.showActive" :key="`${currentKey}-active-checkbox`" />
<jira-trigger-fields
v-if="isJira"

View file

@ -80,21 +80,29 @@ export default (el, defaultEl) => {
}
const props = parseDatasetToProps(el.dataset);
const initialState = {
defaultState: null,
customState: props,
};
if (defaultEl) {
initialState.defaultState = Object.freeze(parseDatasetToProps(defaultEl.dataset));
}
// Here, we capture the "helpHtml", so we can pass it to the Vue component
// to position it where ever it wants.
// Because this node is a _child_ of `el`, it will be removed when the Vue component is mounted,
// so we don't need to manually remove it.
const helpHtml = el.querySelector('.js-integration-help-html')?.innerHTML;
return new Vue({
el,
store: createStore(initialState),
render(createElement) {
return createElement(IntegrationForm);
return createElement(IntegrationForm, {
props: {
helpHtml,
},
});
},
});
};

View file

@ -147,6 +147,7 @@ export default {
:style="levelIndentation"
class="file-row-name"
data-qa-selector="file_name_content"
:data-qa-file-name="file.name"
data-testid="file-row-name-container"
:class="[fileClasses, { 'str-truncated': !truncateMiddle, 'gl-min-w-0': truncateMiddle }]"
>

View file

@ -605,6 +605,17 @@ $ide-commit-header-height: 48px;
left: -1px;
}
}
.ide-commit-badge {
background-color: var(--ide-highlight-accent, $almost-black) !important;
color: var(--ide-highlight-background, $white) !important;
position: absolute;
left: 38px;
top: $gl-padding-8;
font-size: $gl-font-size-12;
padding: 2px $gl-padding-4;
font-weight: $gl-font-weight-bold !important;
}
}
.ide-activity-bar {

View file

@ -11,7 +11,7 @@
%h4.gl-alert-title= s_('AdminSettings|Some settings have moved')
= html_escape_once(s_('AdminSettings|Elasticsearch, PlantUML, Slack application, Third party offers, Snowplow, Amazon EKS have moved to Settings &gt; General.')).html_safe
.gl-alert-actions
= link_to s_('AdminSettings|Go to General Settings'), general_admin_application_settings_path, class: 'btn gl-alert-action btn-info new-gl-button'
= link_to s_('AdminSettings|Go to General Settings'), general_admin_application_settings_path, class: 'btn gl-alert-action btn-info gl-button'
%h4= s_('AdminSettings|Apply integration settings to all Projects')
%p

View file

@ -8,7 +8,7 @@
%p
- link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
= s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), class: 'btn btn-md new-gl-button js-close-callout'
= link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'), class: 'btn btn-md btn-default gl-button js-close-callout'
%button.gl-banner-close.close.js-close-callout{ type: 'button',
'aria-label' => s_('AutoDevOps|Dismiss Auto DevOps box') }

View file

@ -6,5 +6,5 @@
.gl-alert-body
= s_("MissingSSHKeyWarningLink|You won't be able to pull or push repositories via SSH until you add an SSH key to your profile")
.gl-alert-actions
= link_to s_('MissingSSHKeyWarningLink|Add SSH key'), profile_keys_path, class: "btn gl-alert-action btn-warning btn-md new-gl-button"
= link_to s_('MissingSSHKeyWarningLink|Add SSH key'), profile_keys_path, class: "btn gl-alert-action btn-warning btn-md gl-button"
= link_to s_("MissingSSHKeyWarningLink|Don't show again"), profile_path(user: {hide_no_ssh_key: true}), method: :put, role: 'button', class: 'btn gl-alert-action btn-md btn-warning gl-button btn-warning-secondary'

View file

@ -1,13 +1,14 @@
= form_errors(integration)
- if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true)
= render "projects/services/#{integration.to_param}/help", subject: integration
- elsif integration.help.present?
.info-well
.well-segment
= markdown integration.help
.service-settings
- if @default_integration
.js-vue-default-integration-settings{ data: integration_form_data(@default_integration, group: @group) }
.js-vue-integration-settings{ data: integration_form_data(integration, group: @group) }
.js-integration-help-html
-# All content below will be repositioned in Vue
- if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true)
= render "projects/services/#{integration.to_param}/help", subject: integration
- elsif integration.help.present?
.info-well
.well-segment
= markdown integration.help

View file

@ -0,0 +1,5 @@
---
title: Move "number of changed files" into Web IDE sidebar badge
merge_request: 51166
author: Kev @KevSlashNull
type: added

View file

@ -0,0 +1,5 @@
---
title: Add a /request_review alias for /assign_reviewer
merge_request: 51751
author:
type: added

View file

@ -0,0 +1,6 @@
---
title: Update button style for consistency in Settings > Integrations, Mermaid Diagram
warning, and No SSH warning
merge_request: 51864
author:
type: other

View file

@ -1358,7 +1358,7 @@ Get JetBrains TeamCity CI service settings for a project.
GET /projects/:id/services/teamcity
```
## Jenkins CI **(STARTER)**
## Jenkins CI
A continuous integration and build server

View file

@ -299,6 +299,28 @@ through web requests. See the
[follow-up work](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/68)
for more information.
### Logging context metadata (through workers)
Additional metadata can be attached to a worker through the use of the [`ApplicationWorker#log_extra_metadata_on_done`](https://gitlab.com/gitlab-org/gitlab/-/blob/16ecc33341a3f6b6bebdf78d863c5bce76b040d3/app/workers/concerns/application_worker.rb#L31-34)
method. Using this method adds metadata that is later logged to Kibana with the done job payload.
```ruby
class MyExampleWorker
include ApplicationWorker
def perform(*args)
# Worker performs work
# ...
# The contents of value will appear in Kibana under `json.extra.my_example_worker.my_key`
log_extra_metadata_on_done(:my_key, value)
end
end
```
Please see [this example](https://gitlab.com/gitlab-org/gitlab/-/blob/16ecc33341a3f6b6bebdf78d863c5bce76b040d3/app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb#L20-21)
which logs a count of how many artifacts are destroyed per run of the `ExpireArtifactsWorker`.
## Exception Handling
It often happens that you catch the exception and want to track it.

View file

@ -142,7 +142,7 @@ new PostgreSQL one:
sudo -u gitlab-psql pgloader commands.load
```
1. Once the migration finishes, you should see a summary table that looks like
1. After the migration finishes, you should see a summary table that looks like
the following:
```plaintext
@ -243,7 +243,7 @@ new PostgreSQL one:
sudo -u postgres pgloader commands.load
```
1. Once the migration finishes, you should see a summary table that looks like
1. After the migration finishes, you should see a summary table that looks like
the following:
```plaintext

View file

@ -40,7 +40,7 @@ If you're not using the Omnibus GitLab package you may have to adjust the paths
`pg_dump` and the PostgreSQL installation directory to match the paths of your
configuration.
Once the structure dump is generated we also need to generate a dump for the
After the structure dump is generated we also need to generate a dump for the
`schema_migrations` table. This table doesn't have any primary keys and as such
can't be replicated easily by Slony. To generate this dump run the following
command on your active database server:
@ -210,7 +210,7 @@ this output, don't just append it below it. The result looks like this:
]
```
Once you have the configuration file generated you must install it on both the
After you have the configuration file generated you must install it on both the
old and new database. To do so, place it in
`/var/opt/gitlab/postgresql/slony/slon_tools.conf` (for which we created the
directory earlier on).
@ -218,7 +218,7 @@ directory earlier on).
Now that the configuration file is in place we can _finally_ start replicating
our database. First we must set up the schema in our new database. To do so make
sure that the SQL files we generated earlier can be found in the `/tmp`
directory of the new server. Once these files are in place start a `psql`
directory of the new server. After these files are in place start a `psql`
session on this server:
```shell

View file

@ -20,7 +20,7 @@ used (less than 1MB) and it is automatically resized.
![Navigation bar header logo screenshot](img/appearance_header_logo_v12_3.png)
Once you select and upload an image, click **Update appearance settings** at the bottom
After you select and upload an image, click **Update appearance settings** at the bottom
of the page to activate it in the GitLab instance.
NOTE:

View file

@ -300,7 +300,9 @@ The Sidekiq dashboard consists of the following elements:
### Logs
The **Logs** page provides access to the following log files:
Since GitLab 13.0, **Log** view has been removed from the admin dashboard since the logging does not work in multi-node setups and could cause confusion for administrators by displaying partial information.
For multi-node systems we recommend ingesting the logs into services like Elasticsearch and Splunk.
| Log file | Contents |
| :---------------------- | :------- |
@ -312,7 +314,7 @@ The **Logs** page provides access to the following log files:
| `integrations_json.log` | Activity between GitLab and integrated systems |
| `kubernetes.log` | Kubernetes activity |
The contents of these log files can be useful when troubleshooting a problem. Access is available to GitLab admins, without requiring direct access to the log files.
The contents of these log files can be useful when troubleshooting a problem.
For details of these log files and their contents, see [Log system](../../administration/logs.md).

View file

@ -40,7 +40,7 @@ In order to change this option:
1. Select **Save changes**.
NOTE:
Once the hostname gets configured, every private commit email using the previous hostname, will not get
After the hostname gets configured, every private commit email using the previous hostname is not
recognized by GitLab. This can directly conflict with certain [Push rules](../../../push_rules/push_rules.md) such as
`Check whether author is a GitLab user` and `Check whether committer is the current authenticated user`.

View file

@ -27,7 +27,7 @@ You can restrict the password authentication for web interface and Git over HTTP
When this feature enabled, all users must use the [two-factor authentication](../../profile/account/two_factor_authentication.md).
Once the two-factor authentication is configured as mandatory, the users are allowed
After the two-factor authentication is configured as mandatory, users are allowed
to skip forced configuration of two-factor authentication for the configurable grace
period in hours.

View file

@ -267,7 +267,7 @@ You can dismiss multiple vulnerabilities at once, providing an optional reason.
Selecting the checkboxes on the side of each vulnerability in the list selects that individual vulnerability.
Alternatively, you can select all the vulnerabilities in the list by selecting the checkbox in the table header.
Deselecting the checkbox in the header deselects all the vulnerabilities in the list.
Once you have selected some vulnerabilities, a menu appears at the top of the table that allows you to select a dismissal reason.
After you have selected some vulnerabilities, a menu appears at the top of the table that allows you to select a dismissal reason.
Pressing the "Dismiss Selected" button dismisses all the selected vulnerabilities at once, with the reason you chose.
![Multiple vulnerability dismissal](img/multi_select_v12_9.png)
@ -281,7 +281,7 @@ You can create an issue for a vulnerability by visiting the vulnerability's page
This creates a [confidential issue](../project/issues/confidential_issues.md) in the project the
vulnerability came from, and pre-populates it with some useful information taken from the vulnerability
report. Once the issue is created, you are redirected to it so you can edit, assign, or comment on
report. After the issue is created, you are redirected to it so you can edit, assign, or comment on
it.
Upon returning to the group security dashboard, the vulnerability now has an associated issue next

View file

@ -267,7 +267,7 @@ To create an issue associated with the vulnerability, click the **Create Issue**
![Create an issue for the vulnerability](img/vulnerability_details_create_issue_v13_7.png)
Once you create the issue, the linked issue icon in the vulnerability list:
After you create the issue, the linked issue icon in the vulnerability list:
- Indicates that an issue has been created for that vulnerability.
- Shows a tooltip that contains a link to the issue.

View file

@ -38,7 +38,7 @@ In order to:
- Show pod usage correctly, you must
[enable Deploy Boards](../project/deploy_boards.md#enabling-deploy-boards).
Once you have successful deployments to your group-level or instance-level cluster:
After you have successful deployments to your group-level or instance-level cluster:
1. Navigate to your group's **Kubernetes** page.
1. Click on the **Environments** tab.

View file

@ -370,7 +370,7 @@ From a merge request's **Discussion** tab, or from an epic/issue overview, find
![Notes filters dropdown options](img/index_notes_filters.png)
Once you select one of the filters in a given issue or MR, GitLab will save
After you select one of the filters in a given issue or MR, GitLab will save
your preference, so that it will persist when you visit the same page again
from any device you're logged into.
@ -401,7 +401,7 @@ the merge request authored by the user that applied them.
![Apply suggestions](img/apply_suggestion_v12_7.png)
Once the author applies a Suggestion, it will be marked with the **Applied** label,
After the author applies a Suggestion, it will be marked with the **Applied** label,
the thread will be automatically resolved, and GitLab will create a new commit
and push the suggested change directly into the codebase in the merge request's
branch. [Developer permission](../permissions.md) is required to do so.
@ -537,7 +537,7 @@ Clicking on the **Reply to comment** button will bring the reply area into focus
![Reply to comment feature](img/reply_to_comment.gif)
Replying to a non-thread comment will convert the non-thread comment to a
thread once the reply is submitted. This conversion is considered an edit
thread after the reply is submitted. This conversion is considered an edit
to the original comment, so a note about when it was last edited will appear underneath it.
This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff threads are

View file

@ -69,7 +69,7 @@ For more details on the specific data persisted in a group export, see the
![Export group panel](img/export_panel_v13_0.png)
1. Once the export is generated, you should receive an e-mail with a link to the [exported contents](#exported-contents)
1. After the export is generated, you should receive an e-mail with a link to the [exported contents](#exported-contents)
in a compressed tar archive, with contents in JSON format.
1. Alternatively, you can come back to the project settings and download the

View file

@ -384,7 +384,7 @@ codes. If you saved these codes, you can use one of them to sign in.
To use a recovery code, enter your username/email and password on the GitLab
sign-in page. When prompted for a two-factor code, enter the recovery code.
Once you use a recovery code, you cannot re-use it. You can still use the other
After you use a recovery code, you cannot re-use it. You can still use the other
recovery codes you saved.
### Generate new recovery codes using SSH

View file

@ -62,7 +62,7 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/promote` | ✓ | | | Promote issue to epic. **(PREMIUM)** |
| `/publish` | ✓ | | | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906)) **(ULTIMATE)** |
| `/reassign @user1 @user2` | ✓ | ✓ | | Replace current assignees with those specified. **(STARTER)** |
| `/rebase` | | ✓ | | Rebase source branch. This will schedule a background task that attempt to rebase the changes in the source branch on the latest commit of the target branch. If `/rebase` is used, `/merge` will be ignored to avoid a race condition where the source branch is merged or deleted before it is rebased. If there are merge conflicts, GitLab will display a message that a rebase cannot be scheduled. Rebase failures will be displayed with the merge request status. |
| `/rebase` | | ✓ | | Rebase source branch. This schedules a background task that attempts to rebase the changes in the source branch on the latest commit of the target branch. If `/rebase` is used, `/merge` is ignored to avoid a race condition where the source branch is merged or deleted before it is rebased. If there are merge conflicts, GitLab displays a message that a rebase cannot be scheduled. Rebase failures are displayed with the merge request status. |
| `/reassign_reviewer @user1 @user2` | | ✓ | | Replace current reviewers with those specified. **(STARTER)** |
| `/relabel ~label1 ~label2` | ✓ | ✓ | ✓ | Replace current labels with those specified. |
| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related. **(STARTER)** |

View file

@ -187,7 +187,7 @@ module Gitlab
parse_params do |reviewer_param|
extract_users(reviewer_param)
end
command :assign_reviewer, :reviewer do |users|
command :assign_reviewer, :reviewer, :request_review do |users|
next if users.empty?
if quick_action_target.allows_multiple_reviewers?

View file

@ -58,6 +58,9 @@ module QA
view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do
element :file_name_content
element :file_title_container
element :dropdown_button
element :edit_in_ide_button
end
view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do
@ -296,6 +299,13 @@ module QA
click_element(:open_in_web_ide_button)
wait_for_requests
end
def edit_file_in_web_ide(file_name)
within_element(:file_title_container, file_name: file_name) do
click_element(:dropdown_button)
click_element(:edit_in_ide_button)
end
end
end
end
end

View file

@ -63,6 +63,7 @@ module QA
view 'app/assets/javascripts/ide/components/new_dropdown/index.vue' do
element :dropdown_button
element :rename_move_button
element :delete_button
end
view 'app/views/shared/_confirm_fork_modal.html.haml' do
@ -128,6 +129,13 @@ module QA
end
end
def has_file_content?(file_name, file_content)
click_element(:file_row_container, file_name: file_name)
within_element(:editor_container) do
has_text?(file_content)
end
end
def go_to_project
click_element(:project_path_content, Page::Project::Show)
end
@ -236,7 +244,7 @@ module QA
end
def rename_file(file_name, new_file_name)
click_element(:file_name_content, text: file_name)
click_element(:file_name_content, file_name: file_name)
click_element(:dropdown_button)
click_element(:rename_move_button, Page::Component::WebIDE::Modal::CreateNewFile)
fill_element(:file_name_field, new_file_name)
@ -259,6 +267,12 @@ module QA
find_element(:file_upload_field, visible: false).send_keys(file_path)
end
end
def delete_file(file_name)
click_element(:file_name_content, file_name: file_name)
click_element(:dropdown_button)
click_element(:delete_button)
end
end
end
end

View file

@ -0,0 +1,80 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Create' do
describe 'Open Web IDE from Diff Tab' do
files = [
{
file_path: 'file1',
content: 'test1'
},
{
file_path: 'file2',
content: 'test2'
},
{
file_path: 'file3',
content: 'test3'
}
]
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.initialize_with_readme = true
end
end
let(:source) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.branch = 'new-mr'
commit.start_branch = project.default_branch
commit.commit_message = 'Add new files'
commit.add_files(files)
end
end
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |mr|
mr.source = source
mr.project = project
mr.source_branch = 'new-mr'
mr.target_new_branch = false
end
end
before do
Flow::Login.sign_in
merge_request.visit!
end
it 'opens and edits a multi-file merge request in Web IDE from Diff Tab', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/997' do
Page::MergeRequest::Show.perform do |show|
show.click_diffs_tab
show.edit_file_in_web_ide('file1')
end
Page::Project::WebIDE::Edit.perform do |ide|
files.each do |files|
expect(ide).to have_file(files[:file_path])
expect(ide).to have_file_content(files[:file_path], files[:content])
end
ide.delete_file('file1')
ide.commit_changes
end
merge_request.visit!
Page::MergeRequest::Show.perform do |show|
show.click_diffs_tab
expect(show).not_to have_file('file1')
expect(show).to have_file('file2')
expect(show).to have_file('file3')
end
end
end
end
end

View file

@ -40,4 +40,8 @@ RSpec.describe 'Slack slash commands', :js do
value = find_field('url').value
expect(value).to match("api/v4/projects/#{project.id}/services/slack_slash_commands/trigger")
end
it 'shows help content' do
expect(page).to have_content('This service allows users to perform common operations on this project by entering slash commands in Slack.')
end
end

View file

@ -9,6 +9,8 @@ describe('IDE activity bar', () => {
let vm;
let store;
const findChangesBadge = () => vm.$el.querySelector('.badge');
beforeEach(() => {
store = createStore();
@ -69,4 +71,19 @@ describe('IDE activity bar', () => {
});
});
});
describe('changes badge', () => {
it('is rendered when files are staged', () => {
store.state.stagedFiles = [{ path: '/path/to/file' }];
vm.$mount();
expect(findChangesBadge()).toBeTruthy();
expect(findChangesBadge().textContent.trim()).toBe('1');
});
it('is not rendered when no changes are present', () => {
vm.$mount();
expect(findChangesBadge()).toBeFalsy();
});
});
});

View file

@ -1,5 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { mockIntegrationProps } from 'jest/integrations/edit/mock_data';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { setHTMLFixture } from 'helpers/fixtures';
import { createStore } from '~/integrations/edit/store';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
@ -15,24 +17,31 @@ import { integrationLevels } from '~/integrations/edit/constants';
describe('IntegrationForm', () => {
let wrapper;
const createComponent = (customStateProps = {}, featureFlags = {}, initialState = {}) => {
wrapper = shallowMount(IntegrationForm, {
propsData: {},
store: createStore({
customState: { ...mockIntegrationProps, ...customStateProps },
...initialState,
const createComponent = ({
customStateProps = {},
featureFlags = {},
initialState = {},
props = {},
} = {}) => {
wrapper = extendedWrapper(
shallowMount(IntegrationForm, {
propsData: { ...props },
store: createStore({
customState: { ...mockIntegrationProps, ...customStateProps },
...initialState,
}),
stubs: {
OverrideDropdown,
ActiveCheckbox,
ConfirmationModal,
JiraTriggerFields,
TriggerFields,
},
provide: {
glFeatures: featureFlags,
},
}),
stubs: {
OverrideDropdown,
ActiveCheckbox,
ConfirmationModal,
JiraTriggerFields,
TriggerFields,
},
provide: {
glFeatures: featureFlags,
},
});
);
};
afterEach(() => {
@ -63,7 +72,9 @@ describe('IntegrationForm', () => {
describe('showActive is false', () => {
it('does not render ActiveCheckbox', () => {
createComponent({
showActive: false,
customStateProps: {
showActive: false,
},
});
expect(findActiveCheckbox().exists()).toBe(false);
@ -73,7 +84,9 @@ describe('IntegrationForm', () => {
describe('integrationLevel is instance', () => {
it('renders ConfirmationModal', () => {
createComponent({
integrationLevel: integrationLevels.INSTANCE,
customStateProps: {
integrationLevel: integrationLevels.INSTANCE,
},
});
expect(findConfirmationModal().exists()).toBe(true);
@ -82,7 +95,9 @@ describe('IntegrationForm', () => {
describe('resetPath is empty', () => {
it('does not render ResetConfirmationModal and button', () => {
createComponent({
integrationLevel: integrationLevels.INSTANCE,
customStateProps: {
integrationLevel: integrationLevels.INSTANCE,
},
});
expect(findResetButton().exists()).toBe(false);
@ -93,8 +108,10 @@ describe('IntegrationForm', () => {
describe('resetPath is present', () => {
it('renders ResetConfirmationModal and button', () => {
createComponent({
integrationLevel: integrationLevels.INSTANCE,
resetPath: 'resetPath',
customStateProps: {
integrationLevel: integrationLevels.INSTANCE,
resetPath: 'resetPath',
},
});
expect(findResetButton().exists()).toBe(true);
@ -106,7 +123,9 @@ describe('IntegrationForm', () => {
describe('integrationLevel is group', () => {
it('renders ConfirmationModal', () => {
createComponent({
integrationLevel: integrationLevels.GROUP,
customStateProps: {
integrationLevel: integrationLevels.GROUP,
},
});
expect(findConfirmationModal().exists()).toBe(true);
@ -115,7 +134,9 @@ describe('IntegrationForm', () => {
describe('resetPath is empty', () => {
it('does not render ResetConfirmationModal and button', () => {
createComponent({
integrationLevel: integrationLevels.GROUP,
customStateProps: {
integrationLevel: integrationLevels.GROUP,
},
});
expect(findResetButton().exists()).toBe(false);
@ -126,8 +147,10 @@ describe('IntegrationForm', () => {
describe('resetPath is present', () => {
it('renders ResetConfirmationModal and button', () => {
createComponent({
integrationLevel: integrationLevels.GROUP,
resetPath: 'resetPath',
customStateProps: {
integrationLevel: integrationLevels.GROUP,
resetPath: 'resetPath',
},
});
expect(findResetButton().exists()).toBe(true);
@ -139,7 +162,9 @@ describe('IntegrationForm', () => {
describe('integrationLevel is project', () => {
it('does not render ConfirmationModal', () => {
createComponent({
integrationLevel: 'project',
customStateProps: {
integrationLevel: 'project',
},
});
expect(findConfirmationModal().exists()).toBe(false);
@ -147,8 +172,10 @@ describe('IntegrationForm', () => {
it('does not render ResetConfirmationModal and button', () => {
createComponent({
integrationLevel: 'project',
resetPath: 'resetPath',
customStateProps: {
integrationLevel: 'project',
resetPath: 'resetPath',
},
});
expect(findResetButton().exists()).toBe(false);
@ -158,7 +185,9 @@ describe('IntegrationForm', () => {
describe('type is "slack"', () => {
beforeEach(() => {
createComponent({ type: 'slack' });
createComponent({
customStateProps: { type: 'slack' },
});
});
it('does not render JiraTriggerFields', () => {
@ -172,14 +201,19 @@ describe('IntegrationForm', () => {
describe('type is "jira"', () => {
it('renders JiraTriggerFields', () => {
createComponent({ type: 'jira' });
createComponent({
customStateProps: { type: 'jira' },
});
expect(findJiraTriggerFields().exists()).toBe(true);
});
describe('featureFlag jiraIssuesIntegration is false', () => {
it('does not render JiraIssuesFields', () => {
createComponent({ type: 'jira' }, { jiraIssuesIntegration: false });
createComponent({
customStateProps: { type: 'jira' },
featureFlags: { jiraIssuesIntegration: false },
});
expect(findJiraIssuesFields().exists()).toBe(false);
});
@ -187,8 +221,10 @@ describe('IntegrationForm', () => {
describe('featureFlag jiraIssuesIntegration is true', () => {
it('renders JiraIssuesFields', () => {
createComponent({ type: 'jira' }, { jiraIssuesIntegration: true });
createComponent({
customStateProps: { type: 'jira' },
featureFlags: { jiraIssuesIntegration: true },
});
expect(findJiraIssuesFields().exists()).toBe(true);
});
});
@ -200,8 +236,10 @@ describe('IntegrationForm', () => {
const type = 'slack';
createComponent({
triggerEvents: events,
type,
customStateProps: {
triggerEvents: events,
type,
},
});
expect(findTriggerFields().exists()).toBe(true);
@ -218,7 +256,9 @@ describe('IntegrationForm', () => {
];
createComponent({
fields,
customStateProps: {
fields,
},
});
const dynamicFields = wrapper.findAll(DynamicField);
@ -232,13 +272,11 @@ describe('IntegrationForm', () => {
describe('defaultState state is null', () => {
it('does not render OverrideDropdown', () => {
createComponent(
{},
{},
{
createComponent({
initialState: {
defaultState: null,
},
);
});
expect(findOverrideDropdown().exists()).toBe(false);
});
@ -246,18 +284,43 @@ describe('IntegrationForm', () => {
describe('defaultState state is an object', () => {
it('renders OverrideDropdown', () => {
createComponent(
{},
{},
{
createComponent({
initialState: {
defaultState: {
...mockIntegrationProps,
},
},
);
});
expect(findOverrideDropdown().exists()).toBe(true);
});
});
describe('with `helpHtml` prop', () => {
const mockTestId = 'jest-help-html-test';
setHTMLFixture(`
<div data-testid="${mockTestId}">
<svg class="gl-icon">
<use></use>
</svg>
</div>
`);
it('renders `helpHtml`', async () => {
const mockHelpHtml = document.querySelector(`[data-testid="${mockTestId}"]`);
createComponent({
props: {
helpHtml: mockHelpHtml.outerHTML,
},
});
const helpHtml = wrapper.findByTestId(mockTestId);
expect(helpHtml.isVisible()).toBe(true);
expect(helpHtml.find('svg').isVisible()).toBe(true);
});
});
});
});

View file

@ -945,6 +945,12 @@ RSpec.describe QuickActions::InterpretService do
it_behaves_like 'assign_reviewer command'
end
context 'with the "request_review" alias' do
let(:content) { "/request_review @#{developer.username}" }
it_behaves_like 'assign_reviewer command'
end
context 'with no user' do
let(:content) { '/assign_reviewer' }