Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e1e342d79b
commit
25c07d7230
39 changed files with 367 additions and 88 deletions
|
@ -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:
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -108,6 +108,7 @@ export default {
|
|||
class="d-flex"
|
||||
icon="remove"
|
||||
icon-classes="mr-2"
|
||||
data-qa-selector="delete_button"
|
||||
@click="deleteEntry(path)"
|
||||
/>
|
||||
</li>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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 }]"
|
||||
>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 > 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
|
||||
|
|
|
@ -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') }
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move "number of changed files" into Web IDE sidebar badge
|
||||
merge_request: 51166
|
||||
author: Kev @KevSlashNull
|
||||
type: added
|
5
changelogs/unreleased/25381-add-request-review-alias.yml
Normal file
5
changelogs/unreleased/25381-add-request-review-alias.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add a /request_review alias for /assign_reviewer
|
||||
merge_request: 51751
|
||||
author:
|
||||
type: added
|
6
changelogs/unreleased/ps-clean-new-gl-button-refs.yml
Normal file
6
changelogs/unreleased/ps-clean-new-gl-button-refs.yml
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)** |
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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' }
|
||||
|
||||
|
|
Loading…
Reference in a new issue