Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-01-23 18:11:12 +00:00
parent a8b96c3072
commit cfc8827f6b
89 changed files with 842 additions and 396 deletions

View File

@ -88,7 +88,7 @@ export const billingPlanNames = {
[billingPlans.ULTIMATE]: s__('BillingPlans|Ultimate'),
};
const INTEGRATION_TYPE_SLACK = 'slack';
export const INTEGRATION_TYPE_SLACK = 'slack';
const INTEGRATION_TYPE_SLACK_APPLICATION = 'gitlab_slack_application';
const INTEGRATION_TYPE_MATTERMOST = 'mattermost';

View File

@ -1,7 +1,9 @@
<script>
import { GlIcon, GlLink, GlTable, GlTooltipDirective } from '@gitlab/ui';
import { INTEGRATION_TYPE_SLACK } from '~/integrations/constants';
import { sprintf, s__, __ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@ -13,6 +15,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagsMixin()],
props: {
integrations: {
type: Array,
@ -55,6 +58,15 @@ export default {
},
];
},
filteredIntegrations() {
if (this.glFeatures.integrationSlackAppNotifications) {
return this.integrations.filter(
(integration) =>
!(integration.name === INTEGRATION_TYPE_SLACK && integration.active === false),
);
}
return this.integrations;
},
},
methods: {
getStatusTooltipTitle(integration) {
@ -67,7 +79,7 @@ export default {
</script>
<template>
<gl-table :items="integrations" :fields="fields" :empty-text="emptyText" show-empty fixed>
<gl-table :items="filteredIntegrations" :fields="fields" :empty-text="emptyText" show-empty fixed>
<template #cell(active)="{ item }">
<gl-icon
v-if="item.active"

View File

@ -50,6 +50,14 @@ export const timelineEventTagsI18n = Object.freeze({
endTime: __('End time'),
});
export const timelineEventTagsPopover = Object.freeze({
title: s__('Incident|Event tag'),
message: s__(
'Incident|Adding an event tag associates the timeline comment with specific incident metrics.',
),
link: __('Learn more'),
});
export const MAX_TEXT_LENGTH = 280;
export const TIMELINE_EVENT_TAGS = Object.values(timelineEventTagsI18n).map((item) => ({

View File

@ -3,6 +3,7 @@ import { GlDatepicker, GlFormInput, GlFormGroup, GlButton, GlCollapsibleListbox
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __, sprintf } from '~/locale';
import TimelineEventsTagsPopover from './timeline_events_tags_popover.vue';
import { MAX_TEXT_LENGTH, TIMELINE_EVENT_TAGS, timelineFormI18n } from './constants';
import { getUtcShiftedDate, getPreviousEventTags } from './utils';
@ -21,6 +22,7 @@ export default {
],
components: {
MarkdownField,
TimelineEventsTagsPopover,
GlDatepicker,
GlFormInput,
GlFormGroup,
@ -197,8 +199,13 @@ export default {
<p class="gl-ml-3 gl-align-self-end gl-line-height-32">{{ __('UTC') }}</p>
</div>
</div>
<gl-form-group v-if="glFeatures.incidentEventTags" :label="$options.i18n.tagsLabel">
<gl-form-group v-if="glFeatures.incidentEventTags">
<label class="gl-display-flex gl-align-items-center gl-gap-3" for="timeline-input-tags">
{{ $options.i18n.tagsLabel }}
<timeline-events-tags-popover />
</label>
<gl-collapsible-listbox
id="timeline-input-tags"
:selected="selectedTags"
:toggle-text="listboxText"
:items="tags"

View File

@ -0,0 +1,42 @@
<script>
import { GlIcon, GlPopover, GlLink } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { timelineEventTagsPopover } from './constants';
export default {
name: 'TimelineEventsTagsPopover',
components: {
GlIcon,
GlPopover,
GlLink,
},
i18n: timelineEventTagsPopover,
learnMoreLink: helpPagePath('ee/operations/incident_management/incident_timeline_events', {
anchor: 'incident-tags',
}),
};
</script>
<template>
<span>
<gl-icon id="timeline-events-tag-question" name="question-o" class="gl-text-blue-600" />
<gl-popover
target="timeline-events-tag-question"
triggers="hover focus"
placement="top"
container="viewport"
:title="$options.i18n.title"
>
<div>
<p class="gl-mb-0">
{{ $options.i18n.message }}
</p>
<gl-link target="_blank" class="gl-font-sm" :href="$options.learnMoreLink">{{
$options.i18n.link
}}</gl-link
>.
</div>
</gl-popover>
</span>
</template>

View File

@ -0,0 +1,34 @@
<script>
import { GlDisclosureDropdown, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
GlDisclosureDropdown,
},
i18n: {
createNew: __('Create new...'),
},
props: {
groups: {
type: Array,
required: true,
},
},
};
</script>
<template>
<gl-disclosure-dropdown
v-gl-tooltip:super-sidebar.bottom="$options.i18n.createNew"
category="tertiary"
icon="plus"
:items="groups"
no-caret
text-sr-only
:toggle-text="$options.i18n.createNew"
/>
</template>

View File

@ -31,6 +31,7 @@ export default {
<template>
<aside
id="super-sidebar"
class="super-sidebar gl-fixed gl-bottom-0 gl-left-0 gl-display-flex gl-flex-direction-column gl-bg-gray-10 gl-border-r gl-border-gray-a-08 gl-z-index-9999"
data-testid="super-sidebar"
>

View File

@ -4,6 +4,7 @@ import { __ } from '~/locale';
import SafeHtml from '~/vue_shared/directives/safe_html';
import NewNavToggle from '~/nav/components/new_nav_toggle.vue';
import logo from '../../../../views/shared/_logo.svg';
import CreateMenu from './create_menu.vue';
import Counter from './counter.vue';
export default {
@ -12,10 +13,12 @@ export default {
GlAvatar,
GlDropdown,
GlIcon,
CreateMenu,
NewNavToggle,
Counter,
},
i18n: {
createNew: __('Create new...'),
issues: __('Issues'),
mergeRequests: __('Merge requests'),
todoList: __('To-Do list'),
@ -39,11 +42,7 @@ export default {
<div class="gl-flex-grow-1">
<a v-safe-html="$options.logo" :href="rootPath"></a>
</div>
<gl-dropdown variant="link" no-caret>
<template #button-content>
<gl-icon name="plus" class="gl-vertical-align-middle gl-text-black-normal" />
</template>
</gl-dropdown>
<create-menu :groups="sidebarData.create_new_menu_groups" />
<button class="gl-border-none">
<gl-icon name="search" class="gl-vertical-align-middle" />
</button>

View File

@ -37,6 +37,8 @@ module Types
# Life-cycle timestamps:
field :created_at, Types::TimeType, null: false,
description: "When the job was created."
field :erased_at, Types::TimeType, null: true,
description: "When the job was erased."
field :finished_at, Types::TimeType, null: true,
description: 'When a job has finished running.'
field :queued_at, Types::TimeType, null: true,

View File

@ -232,7 +232,7 @@ module ApplicationHelper
end
def support_url
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || "#{promo_url}/getting-help/"
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || "#{promo_url}/get-help/"
end
def instance_review_permitted?

View File

@ -59,7 +59,7 @@ module Nav
end
{
title: _('This group'),
title: _('In this group'),
menu_items: menu_items.compact
}
end
@ -110,7 +110,7 @@ module Nav
end
{
title: _('This project'),
title: _('In this project'),
menu_items: menu_items
}
end
@ -152,7 +152,7 @@ module Nav
end
{
title: _('GitLab'),
title: _('In GitLab'),
menu_items: menu_items
}
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
module SidebarsHelper
include Nav::NewDropdownHelper
def sidebar_tracking_attributes_by_object(object)
sidebar_attributes_for_object(object).fetch(:tracking_attrs, {})
end
@ -31,7 +33,7 @@ module SidebarsHelper
Sidebars::Groups::Context.new(**context_data)
end
def super_sidebar_context(user)
def super_sidebar_context(user, group:, project:)
{
name: user.name,
username: user.username,
@ -39,12 +41,29 @@ module SidebarsHelper
assigned_open_issues_count: user.assigned_open_issues_count,
assigned_open_merge_requests_count: user.assigned_open_merge_requests_count,
todos_pending_count: user.todos_pending_count,
issues_dashboard_path: issues_dashboard_path(assignee_username: user.username)
issues_dashboard_path: issues_dashboard_path(assignee_username: user.username),
create_new_menu_groups: create_new_menu_groups(group: group, project: project)
}
end
private
def create_new_menu_groups(group:, project:)
new_dropdown_sections = new_dropdown_view_model(group: group, project: project)[:menu_sections]
show_headers = new_dropdown_sections.length > 1
new_dropdown_sections.map do |section|
{
name: show_headers ? section[:title] : '',
items: section[:menu_items].map do |item|
{
text: item[:title],
href: item[:href]
}
end
}
end
end
def sidebar_attributes_for_object(object)
case object
when Project

View File

@ -12,7 +12,7 @@
= f.gitlab_ui_checkbox_component :help_page_hide_commercial_content, _('Hide marketing-related entries from the Help page')
.form-group
= f.label :help_page_support_url, _('Support page URL'), class: 'label-bold'
= f.text_field :help_page_support_url, class: 'form-control gl-form-input', placeholder: 'https://company.example.com/getting-help', :'aria-describedby' => 'support_help_block'
= f.text_field :help_page_support_url, class: 'form-control gl-form-input', placeholder: 'https://company.example.com/get-help', :'aria-describedby' => 'support_help_block'
%span.form-text.text-muted#support_help_block= _('Alternate support URL for Help page and Help dropdown.')
.form-group

View File

@ -12,14 +12,17 @@
= gl_badge_tag migration.status_name.to_s.humanize, { size: :sm, variant: batched_migration_status_badge_variant(migration) }
%td{ role: 'cell', data: { label: _('Action') } }
- if migration.active?
= button_to pause_admin_background_migration_path(migration, database: params[:database]),
class: 'gl-button btn btn-icon has-tooltip', title: _('Pause'), 'aria-label' => _('Pause') do
= sprite_icon('pause', css_class: 'gl-button-icon gl-icon')
= render Pajamas::ButtonComponent.new(icon: 'pause',
method: :post,
href: pause_admin_background_migration_path(migration, database: params[:database]),
button_options: { class: 'has-tooltip', title: _('Pause'), 'aria-label' => _('Pause') })
- elsif migration.paused?
= button_to resume_admin_background_migration_path(migration, database: params[:database]),
class: 'gl-button btn btn-icon has-tooltip', title: _('Resume'), 'aria-label' => _('Resume') do
= sprite_icon('play', css_class: 'gl-button-icon gl-icon')
= render Pajamas::ButtonComponent.new(icon: 'play',
method: :post,
href: resume_admin_background_migration_path(migration, database: params[:database]),
button_options: { class: 'has-tooltip', title: _('Resume'), 'aria-label' => _('Resume') })
- elsif migration.failed?
= button_to retry_admin_background_migration_path(migration, database: params[:database]),
class: 'gl-button btn btn-icon has-tooltip', title: _('Retry'), 'aria-label' => _('Retry') do
= sprite_icon('retry', css_class: 'gl-button-icon gl-icon')
= render Pajamas::ButtonComponent.new(icon: 'retry',
method: :post,
href: retry_admin_background_migration_path(migration, database: params[:database]),
button_options: { class: 'has-tooltip', title: _('Retry'), 'aria-label' => _('Retry') })

View File

@ -2,7 +2,7 @@
- @left_sidebar = true
.layout-page.hide-when-top-nav-responsive-open{ class: page_with_sidebar_class }
- if show_super_sidebar?
- sidebar_data = super_sidebar_context(current_user).to_json
- sidebar_data = super_sidebar_context(current_user, group: @group, project: @project).to_json
%aside.js-super-sidebar.nav-sidebar{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url } }
- elsif defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"

View File

@ -86,7 +86,7 @@ When creating Geo-Based record sets, GCP applies a nearest match for the source
1. Select **Network Services** > **Cloud DNS**.
1. Select the Zone configured for your domain.
1. Select **Add Record Set**.
1. Enter the DNS Name for your Location-aware public URL e.g. `gitlab.example.com`.
1. Enter the DNS Name for your Location-aware public URL, for example, `gitlab.example.com`.
1. Select the **Routing Policy**: **Geo-Based**.
1. Select **Add Managed RRData**.
1. Select **Source Region**: **us-central1**.

View File

@ -166,7 +166,7 @@ jq --raw-output '[.route, .ua] | @tsv' api_json.log | sort | uniq -c | sort -n
1234 /api/:version/internal/allowed GitLab-Shell
```
This sample response seems normal. A custom tool or script might be causing a high load
This sample response seems typical. A custom tool or script might be causing a high load
if the output contains many:
- Third party libraries like `python-requests` or `curl`.

View File

@ -158,7 +158,7 @@ sudo -u git -H bundle exec rails runner -e production /path/to/script.rb
Rails Runner does not produce the same output as the console.
If you set a variable on the console, the console will generate useful debug output
If you set a variable on the console, the console generates useful debug output
such as the variable contents or properties of referenced entity:
```ruby
@ -176,7 +176,7 @@ root
1
```
Some basic knowledge of Ruby will be very useful. Try
Some basic knowledge of Ruby is very useful. Try
[this 30-minute tutorial](https://try.ruby-lang.org/) for a quick introduction.
Rails experience is helpful but not essential.
@ -425,7 +425,7 @@ D, [2020-03-05T17:18:30.406047 #910] DEBUG -- : User Load (2.6ms) SELECT "use
```
For more on different ways to retrieve data from the database using Active
Record, please see the [Active Record Query Interface documentation](https://guides.rubyonrails.org/active_record_querying.html).
Record, see the [Active Record Query Interface documentation](https://guides.rubyonrails.org/active_record_querying.html).
## Query the database using an Active Record model
@ -547,7 +547,7 @@ be the fastest way to get to the root of the problem.
### Interacting with Active Record objects
At the end of the day, Active Record objects are just normal Ruby objects. As
At the end of the day, Active Record objects are just standard Ruby objects. As
such, we can define methods on them which perform arbitrary actions.
For example, GitLab developers have added some methods which help with

View File

@ -1136,7 +1136,7 @@ To enable the read-only mode:
1. Next, trigger one of the garbage collect commands:
WARNING:
You must use `/opt/gitlab/embedded/bin/registry` to recycle unused tags. If you use `gitlab-ctl registry-garbage-collect`, you **will bring the container registry down**.
You must use `/opt/gitlab/embedded/bin/registry` to recycle unused tags. If you use `gitlab-ctl registry-garbage-collect`, **the container registry goes down**.
```shell
# Recycling unused tags
@ -1706,7 +1706,7 @@ case, since we know that since the login succeeded, we probably need to look
at the communication between the client and the Registry.
The REST API between the Docker client and Registry is described
[in the Docker documentation](https://docs.docker.com/registry/spec/api/). Normally, one would just
[in the Docker documentation](https://docs.docker.com/registry/spec/api/). Usually, one would just
use Wireshark or tcpdump to capture the traffic and see where things went
wrong. However, since all communications between Docker clients and servers
are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even

View File

@ -719,7 +719,10 @@ To set the maximum size of GitLab Pages site in a project, overriding the inheri
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
1. Enter a value under **Maximum size of pages** in MB.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../../user/project/pages/index.md#menu-position-test).
1. In **Maximum size of pages**, enter the size in MB.
1. Select **Save changes**.
## Set maximum number of GitLab Pages custom domains for a project

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Sometimes it is necessary to move your databases from one PostgreSQL instance to
another. For example, if you are using AWS Aurora and are preparing to
enable Database Load Balancing, you will need to move your databases to
enable Database Load Balancing, you need to move your databases to
RDS for PostgreSQL.
To move databases from one instance to another:
@ -36,7 +36,7 @@ To move databases from one instance to another:
/opt/gitlab/embedded/bin/pg_dump -h $SRC_PGHOST -U $SRC_PGUSER -c -C -f praefect_production.sql praefect_production
```
1. Restore the databases to the destination (this will overwrite any existing databases with the same names):
1. Restore the databases to the destination (this overwrites any existing databases with the same names):
```shell
/opt/gitlab/embedded/bin/psql -h $DST_PGHOST -U $DST_PGUSER -f praefect_production.sql postgres

View File

@ -11233,6 +11233,7 @@ CI/CD variables for a GitLab instance.
| <a id="cijobdetailedstatus"></a>`detailedStatus` | [`DetailedStatus`](#detailedstatus) | Detailed status of the job. |
| <a id="cijobdownstreampipeline"></a>`downstreamPipeline` | [`Pipeline`](#pipeline) | Downstream pipeline for a bridge. |
| <a id="cijobduration"></a>`duration` | [`Int`](#int) | Duration of the job in seconds. |
| <a id="cijoberasedat"></a>`erasedAt` | [`Time`](#time) | When the job was erased. |
| <a id="cijobfinishedat"></a>`finishedAt` | [`Time`](#time) | When a job has finished running. |
| <a id="cijobid"></a>`id` | [`JobID`](#jobid) | ID of the job. |
| <a id="cijobkind"></a>`kind` | [`CiJobKind!`](#cijobkind) | Indicates the type of job. |

View File

@ -1201,8 +1201,7 @@ GET /groups?search=foobar
> Introduced in GitLab 14.8.
Get a list of users provisioned by a given group. Does not include subgroups.
Users in this list are considered [enterprise users](../user/enterprise_user/index.md).
Get a list of users provisioned by a given group. Does not include users provisioned by subgroups.
Requires at least the Maintainer role on the group.

View File

@ -44,6 +44,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.802Z",
"started_at": "2015-12-24T17:54:27.722Z",
"finished_at": "2015-12-24T17:54:27.895Z",
"erased_at": null,
"duration": 0.173,
"queued_duration": 0.010,
"artifacts_file": {
@ -112,6 +113,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.727Z",
"started_at": "2015-12-24T17:54:24.729Z",
"finished_at": "2015-12-24T17:54:24.921Z",
"erased_at": null,
"duration": 0.192,
"queued_duration": 0.023,
"artifacts_expire_at": "2016-01-23T17:54:24.921Z",
@ -199,6 +201,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.727Z",
"started_at": "2015-12-24T17:54:24.729Z",
"finished_at": "2015-12-24T17:54:24.921Z",
"erased_at": null,
"duration": 0.192,
"queued_duration": 0.023,
"artifacts_expire_at": "2016-01-23T17:54:24.921Z",
@ -258,6 +261,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.802Z",
"started_at": "2015-12-24T17:54:27.722Z",
"finished_at": "2015-12-24T17:54:27.895Z",
"erased_at": null,
"duration": 0.173,
"queued_duration": 0.023,
"artifacts_file": {
@ -361,6 +365,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.802Z",
"started_at": "2015-12-24T17:54:27.722Z",
"finished_at": "2015-12-24T17:58:27.895Z",
"erased_at": null,
"duration": 240,
"queued_duration": 0.123,
"id": 7,
@ -449,6 +454,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.880Z",
"started_at": "2015-12-24T17:54:30.733Z",
"finished_at": "2015-12-24T17:54:31.198Z",
"erased_at": null,
"duration": 0.465,
"queued_duration": 0.123,
"artifacts_expire_at": "2016-01-23T17:54:31.198Z",
@ -600,6 +606,7 @@ Example of response
"created_at": "2015-12-24T15:51:21.880Z",
"started_at": "2015-12-24T17:54:30.733Z",
"finished_at": "2015-12-24T17:54:31.198Z",
"erased_at": null,
"duration": 0.465,
"queued_duration": 0.010,
"artifacts_expire_at": "2016-01-23T17:54:31.198Z",
@ -704,6 +711,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": "2016-01-11T10:14:09.526Z",
"finished_at": null,
"erased_at": null,
"duration": 8,
"queued_duration": 0.010,
"id": 1,
@ -757,6 +765,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": null,
"finished_at": null,
"erased_at": null,
"duration": null,
"queued_duration": 0.010,
"id": 1,
@ -821,6 +830,7 @@ Example of response
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": "2016-01-11T10:13:33.506Z",
"finished_at": "2016-01-11T10:15:10.506Z",
"erased_at": "2016-01-11T11:30:19.914Z",
"duration": 97.0,
"queued_duration": 0.010,
"status": "failed",
@ -894,6 +904,7 @@ Example response:
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": null,
"finished_at": null,
"erased_at": null,
"duration": null,
"queued_duration": 0.010,
"id": 1,

View File

@ -29,7 +29,8 @@ In GitLab 14.8 and earlier, projects in personal namespaces have an `access_leve
The `group_saml_identity` attribute is only visible to a group owner for [SSO enabled groups](../user/group/saml_sso/index.md).
The `email` attribute is only visible to group Owners for any [enterprise user](../user/enterprise_user/index.md).
The `email` attribute is only visible to group owners when the user was provisioned by the group.
Users are provisioned by the group when the account was created via [SCIM](../user/group/saml_sso/scim_setup.md) or by first sign-in with [SAML SSO for GitLab.com groups](../user/group/saml_sso/index.md).
## List all members of a group or project

View File

@ -46,7 +46,7 @@ It has a pipeline that looks like the following:
Using a DAG, you can relate the `_a` jobs to each other separately from the `_b` jobs,
and even if service `a` takes a very long time to build, service `b` doesn't
wait for it and finishes as quickly as it can. In this very same pipeline, `_c` and
`_d` can be left alone and run together in staged sequence just like any normal
`_d` can be left alone and run together in staged sequence just like any standard
GitLab pipeline.
## Use cases
@ -68,7 +68,7 @@ as quickly as possible.
Relationships are defined between jobs using the [`needs` keyword](../yaml/index.md#needs).
Note that `needs` also works with the [parallel](../yaml/index.md#parallel) keyword,
The `needs` keyword also works with the [parallel](../yaml/index.md#parallel) keyword,
giving you powerful options for parallelization within your pipeline.
## Limitations

View File

@ -136,7 +136,7 @@ and [view your pipeline status](https://marketplace.visualstudio.com/items?itemN
Pipelines can be manually executed, with predefined or manually-specified [variables](../variables/index.md).
You might do this if the results of a pipeline (for example, a code build) are required outside the normal
You might do this if the results of a pipeline (for example, a code build) are required outside the standard
operation of the pipeline.
To execute a pipeline manually:
@ -425,7 +425,7 @@ You can group the jobs by:
you visualize the entire pipeline, including all cross-project inter-dependencies.
If a stage contains more than 100 jobs, only the first 100 jobs are listed in the
pipeline graph. The remaining jobs still run as normal. To see the jobs:
pipeline graph. The remaining jobs still run as usual. To see the jobs:
- Select the pipeline, and the jobs are listed on the right side of the pipeline details page.
- On the left sidebar, select **CI/CD > Jobs**.

View File

@ -20,9 +20,9 @@ If you are working on a feature or enhancement, use the
If you are not a GitLab team member, or do not have the Developer role for the GitLab repository, to update GitLab documentation:
1. Select an [issue](https://about.gitlab.com/handbook/product/ux/technical-writing/#community-contribution-opportunities) you'd like to work on.
- You don't need an issue to open a merge request.
- For a Hackathon, mention `@docs-hackathon` in a comment and ask for the issue to be assigned to you.
To be fair to other contributors, if you see someone has already asked to work on the issue, choose another issue.
- If you're not taking part in a Hackathon, you don't need an issue to open a merge request.
If you are looking for issues to work on and don't see any that suit you, you can always fix [Vale](testing.md#vale) issues.
1. Go to the [GitLab repository](https://gitlab.com/gitlab-org/gitlab).
1. In the top right, select **Fork**. Forking makes a copy of the repository on GitLab.com.

View File

@ -352,7 +352,7 @@ when trying to clone via HTTPS.
When using Kerberos ticket-based authentication in an Active Directory domain,
it may be necessary to increase the maximum header size allowed by NGINX,
as extensions to the Kerberos protocol may result in HTTP authentication headers
larger than the default size of 8kB. Configure `large_client_header_buffers`
larger than the default size of 8 kB. Configure `large_client_header_buffers`
to a larger value in [the NGINX configuration](https://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers).
## Troubleshooting

View File

@ -1,92 +1,11 @@
---
stage: Create
group: Source Code
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
comments: false
redirect_to: '../../tutorials/make_your_first_git_commit.md'
remove_date: '2023-04-23'
---
# Getting started **(FREE)**
This document was moved to [another location](../../tutorials/make_your_first_git_commit.md).
## Instantiating Repositories
- Create a new repository by instantiating it through:
```shell
git init
```
- Copy an existing project by cloning the repository through:
```shell
git clone <url>
```
NOTE:
You can also clone GitLab projects with the
[GitLab Workflow VS Code extension](../../user/project/repository/vscode.md).
To learn more, read about the extension's
[`Git: Clone` command](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#clone-gitlab-projects).
## Central Repositories
- To instantiate a central repository a `--bare` flag is required.
- Bare repositories don't allow file editing or committing changes.
- Create a bare repository with:
```shell
git init --bare project-name.git
```
## Instantiate workflow with clone
1. Create a project in your user namespace.
- Choose to import from **Any Repository by URL** and use <https://gitlab.com/gitlab-org/training-examples.git>.
1. Create a '`Workspace`' directory in your home directory.
1. Clone the '`training-examples`' project.
```shell
mkdir ~/workspace
cd ~/workspace
git clone git@gitlab.example.com:<username>/training-examples.git
cd training-examples
```
## Git concepts
**Untracked files**
New files that Git has not been told to track previously.
**Working area**
Files that have been modified but are not committed.
**Staging area**
Modified files that have been marked to go in the next commit.
## Committing Workflow
1. Edit '`edit_this_file.rb`' in '`training-examples`'
1. See it listed as a changed file (working area)
1. View the differences
1. Stage the file
1. Commit
1. Push the commit to the remote
1. View the Git log
```shell
# Edit `edit_this_file.rb`
git status
git diff
git add <file>
git commit -m 'My change'
git push origin master
git log
```
## Note
- `git fetch` vs `git pull`
- Pull is `git fetch` + `git merge`
<!-- This redirect file can be deleted after <2023-04-23>. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -38,6 +38,8 @@ of the page to activate it in the GitLab instance.
## System header and footer messages
> **Enable header and footer in emails** checkbox [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344819) in GitLab 15.9.
You can add a small header message, a small footer message, or both, to the interface
of your GitLab instance. These messages appear on all projects and pages of the
instance, including the sign in / sign up page. The default color is white text on

View File

@ -1,71 +0,0 @@
---
stage: Manage
group: Authentication and Authorization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
type: reference
---
# Enterprise users **(PREMIUM SAAS)**
Enterprise users have user accounts that are administered by an organization that
has purchased a [GitLab subscription](../../subscriptions/index.md).
Enterprise users are identified by the [**Enterprise** badge](../project/badges.md)
next to their names on the [Members list](../group/manage.md#filter-and-sort-members-in-a-group).
## Provision an enterprise user
A user account is considered an enterprise account when:
- A user without an existing GitLab user account uses the group's
[SAML SSO](../group/saml_sso/index.md) to sign in for the first time.
- [SCIM](../group/saml_sso/scim_setup.md) creates the user account on behalf of
the group.
A user can also [manually connect an identity provider (IdP) to a GitLab account whose email address matches the subscribing organization's domain](../group/saml_sso/index.md#linking-saml-to-your-existing-gitlabcom-account).
By selecting **Authorize** when connecting these two accounts, the user account
with the matching email address is classified as an enterprise user. However, this
user account does not have an **Enterprise** badge in GitLab.
Although a user can be a member of more than one group, each user account can be
provisioned by only one group. As a result, a user is considered an enterprise
user under one top-level group only.
## Manage enterprise users in a namespace
A top-level Owner of a namespace on a paid plan can retrieve information about and
manage enterprise user accounts in that namespace.
These enterprise user-specific actions are in addition to the standard
[group member permissions](../permissions.md#group-members-permissions).
### Disable two-factor authentication
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/9484) in GitLab 15.8.
Top-level group Owners can disable two-factor authentication (2FA) for enterprise users.
To disable 2FA:
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the left sidebar, select **Group information > Members**.
1. Find a user with the **Enterprise** and **2FA** badges.
1. Select **More actions** (**{ellipsis_v}**) and select **Disable two-factor authentication**.
### Prevent users from creating groups and projects outside the corporate group
A SAML IdP administrator or a top-level group Owner can use a SAML response to set:
- Whether users can create groups.
- The maximum number of personal projects users can create.
For more information, see the [supported user attributes for SAML responses](../group/saml_sso/index.md#supported-user-attributes).
### Bypass email confirmation for provisioned users
A top-level group Owner can [set up verified domains to bypass confirmation emails](../group/saml_sso/index.md#bypass-user-email-confirmation-with-verified-domains).
### Get users' email addresses through the API
A top-level group Owner can use the [group and project members API](../../api/members.md)
to access users' information, including email addresses.

View File

@ -131,7 +131,7 @@ Filter a group to find members. By default, all members in the group and subgrou
In lists of group members, entries can display the following badges:
- **SAML**, to indicate the member has a [SAML account](saml_sso/index.md) connected to them.
- **Enterprise**, to indicate that the member is an [enterprise user](../enterprise_user/index.md).
- **Enterprise**, to indicate that [SCIM created the account](saml_sso/scim_setup.md).
1. On the top bar, select **Main menu > Groups** and find your group.
1. Above the list of members, in the **Filter members** box, enter filter criteria.

View File

@ -333,7 +333,7 @@ To migrate users to a new email domain, users must:
## User access and management
> - SAML user provisioning [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/268142) in GitLab 13.7.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325712) in GitLab 14.0, GitLab users created by [SAML SSO](index.md#user-access-and-management) or SCIM provisioning are displayed with an ][**Enterprise**](../../enterprise_user/index.md) badge in the **Members** view.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325712) in GitLab 14.0, GitLab users created by [SAML SSO](index.md#user-access-and-management) or SCIM provisioning are displayed with an **Enterprise** badge in the **Members** view.
After group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard.
If [SCIM](scim_setup.md) is configured, see [user access](scim_setup.md#user-access) on the SCIM page.
@ -431,7 +431,7 @@ convert the information to XML. An example SAML response is shown here.
By default, users provisioned with SAML or SCIM are sent a verification email to verify their identity. Instead, you can
[configure GitLab with a custom domain](../../project/pages/custom_domains_ssl_tls_certification/index.md) and GitLab
automatically confirms user accounts. Users still receive an [enterprise user](../../enterprise_user/index.md) welcome email. Confirmation is bypassed for
automatically confirms user accounts. Users still receive an enterprise user welcome email. Confirmation is bypassed for
users:
- That are provisioned with SAML or SCIM.

View File

@ -170,7 +170,7 @@ encounter issues.
## User access
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325712) in GitLab 14.0, GitLab users created by [SAML SSO](index.md#user-access-and-management) or SCIM provisioning are displayed with an [**Enterprise**](../../enterprise_user/index.md) badge in the **Members** view.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325712) in GitLab 14.0, GitLab users created by [SAML SSO](index.md#user-access-and-management) or SCIM provisioning are displayed with an **Enterprise** badge in the **Members** view.
During the synchronization process, all new users:

View File

@ -1,6 +1,6 @@
---
stage: Create
group: Source Code
stage: Manage
group: Organization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---

View File

@ -34,18 +34,25 @@ For example:
| Code Owner approval rule | Frontend: Code Style | `*.css` files | A frontend engineer reviews CSS file changes for adherence to project style standards. |
| Code Owner approval rule | Backend: Code Review | `*.rb` files | A backend engineer reviews the logic and code style of Ruby files. |
## Code Owners file
A `CODEOWNERS` file (with no extension) can specify users or [shared groups](members/share_project_with_groups.md)
that are responsible for specific files and directories in a repository. Each repository
can have a single `CODEOWNERS` file, and it must be found one of these three locations:
- In the root directory of the repository.
- In the `.gitlab/` directory.
- In the `docs/` directory.
A CODEOWNERS file in any other location is ignored.
## Set up Code Owners
Create a `CODEOWNERS` file to specify users or [shared groups](members/share_project_with_groups.md)
that are responsible for specific files and directories in a repository. Each repository
can have a single `CODEOWNERS` file. To create it:
1. Create a file named `CODEOWNERS` (with no extension) in one of these locations:
1. Choose the location where you want to specify Code Owners:
- In the root directory of the repository
- In the `.gitlab/` directory
- In the `docs/` directory
1. In that location, create a file named `CODEOWNERS`.
- In the root directory of the repository
- In the `.gitlab/` directory
- In the `docs/` directory
1. In the file, enter text that follows one of these patterns:

View File

@ -8,8 +8,6 @@ disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/appro
# Merge request approvals **(FREE)**
> Redesign [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in GitLab 11.8 and [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/10685) in 12.0.
You can configure your merge requests so that they must be approved before
they can be merged. While [GitLab Free](https://about.gitlab.com/pricing/) allows
all users with Developer or greater [permissions](../../../permissions.md) to

View File

@ -59,8 +59,8 @@ To add your custom domain to GitLab Pages:
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. In the top right, select **New Domain**.
1. In **Domain**, enter your domain.
1. In the top-right corner, select **New Domain**.
1. In **Domain**, enter the domain name.
1. Optional. In **Certificate**, turn off the **Automatic certificate management using Let's Encrypt** toggle to add an [SSL/TLS certificate](#adding-an-ssltls-certificate-to-pages). You can also add the certificate and key later.
1. Select **Create New Domain**.
@ -168,10 +168,13 @@ If you're using Cloudflare, check
Once you have added all the DNS records:
1. Go back at your project's **Settings > Pages** (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test)).
1. Locate your domain name and select **Details**.
1. Select the **Retry verification** button to activate your new domain.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. Next to the domain name, select **Edit**.
1. In **Verification status**, select **Retry verification** (**{retry}**).
![Verify your domain](img/retry_domain_verification_v12_0.png)
@ -290,12 +293,28 @@ meet these requirements.
#### Steps
- To add the certificate at the time you add a new domain, go to your
project's **Settings > Pages > New Domain** (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test)), add the domain name and the
certificate.
- To add the certificate to a domain previously added, go to your
project's **Settings > Pages**, locate your domain name, select **Details** and **Edit** to add the certificate.
- To add the certificate at the time you add a new domain:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. In the top-right corner, select **New Domain**.
1. In **Domain**, enter the domain name.
1. In **Certificate**, turn off the **Automatic certificate management using Let's Encrypt** toggle to add an [SSL/TLS certificate](#adding-an-ssltls-certificate-to-pages).
1. Select **Create New Domain**.
- To add the certificate to a domain previously added:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. Next to the domain name, select **Edit**.
1. In **Certificate**, turn off the **Automatic certificate management using Let's Encrypt** toggle to add an [SSL/TLS certificate](#adding-an-ssltls-certificate-to-pages).
1. Select **Save changes**.
NOTE:
The Pages menu entry may also be located at **Deployments > Pages**, [more information](../index.md#menu-position-test)
@ -322,9 +341,13 @@ domain (as long as you've set a valid certificate for it).
To enable this setting:
1. Navigate to your project's **Settings > Pages** (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test)).
1. Tick the checkbox **Force HTTPS (requires valid certificates)**.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. Select the **Force HTTPS (requires valid certificates)** checkbox.
1. Select **Save changes**.
If you use Cloudflare CDN in front of GitLab Pages, make sure to set the SSL connection setting to
`full` instead of `flexible`. For more details, see the [Cloudflare CDN directions](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes#h_4e0d1a7c-eb71-4204-9e22-9d3ef9ef7fef).

View File

@ -42,11 +42,13 @@ For **self-managed** GitLab instances, make sure your administrator has
Once you've met the requirements, enable Let's Encrypt integration:
1. Navigate to your project's **Settings > Pages** (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test)).
1. Find your domain and select **Details**.
1. Select **Edit** in the top-right corner.
1. Enable Let's Encrypt integration by switching **Automatic certificate management using Let's Encrypt**:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. Next to the domain name, select **Edit**.
1. Turn on the **Automatic certificate management using Let's Encrypt** toggle.
![Enable Let's Encrypt](img/lets_encrypt_integration_v12_1.png)
@ -70,31 +72,37 @@ associated Pages domain. GitLab also renews it automatically.
If you get an error **Something went wrong while obtaining the Let's Encrypt certificate**, first, make sure that your pages site is set to "Everyone" in your project's **Settings > General > Visibility**. This allows the Let's Encrypt Servers reach your pages site. Once this is confirmed, you can try obtaining the certificate again by following these steps:
1. Go to your project's **Settings > Pages** (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test)).
1. Select **Edit** on your domain.
1. Select **Retry**.
1. If you're still seeing the same error:
1. Make sure you have properly set only one `CNAME` or `A` DNS record for your domain.
1. Make sure your domain **doesn't have** an `AAAA` DNS record.
1. If you have a `CAA` DNS record for your domain or any higher level domains, make sure [it includes `letsencrypt.org`](https://letsencrypt.org/docs/caa/).
1. Make sure [your domain is verified](index.md#1-add-a-custom-domain).
1. Go to step 1.
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. Next to the domain name, select **Edit**.
1. In **Verification status**, select **Retry verification** (**{retry}**).
1. If you're still getting the same error:
1. Make sure you have properly set only one `CNAME` or `A` DNS record for your domain.
1. Make sure your domain **doesn't have** an `AAAA` DNS record.
1. If you have a `CAA` DNS record for your domain or any higher level domains, make sure [it includes `letsencrypt.org`](https://letsencrypt.org/docs/caa/).
1. Make sure [your domain is verified](index.md#1-add-a-custom-domain).
1. Go to step 1.
### Message "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later." hangs for more than an hour
If you've enabled Let's Encrypt integration, but a certificate is absent after an hour and you see the message, "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.", try to remove and add the domain for GitLab Pages again by following these steps:
1. Go to your project's **Settings > Pages** (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test)).
1. Select **Remove** on your domain.
1. [Add the domain again and verify it](index.md#1-add-a-custom-domain).
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
1. Next to the domain name, select **Remove**.
1. [Add the domain again, and verify it](index.md#1-add-a-custom-domain).
1. [Enable Let's Encrypt integration for your domain](#enabling-lets-encrypt-integration-for-your-custom-domain).
1. If you still see the same message after some time:
1. Make sure you have properly set only one `CNAME` or `A` DNS record for your domain.
1. Make sure your domain **doesn't have** an `AAAA` DNS record.
1. If you have a `CAA` DNS record for your domain or any higher level domains, make sure [it includes `letsencrypt.org`](https://letsencrypt.org/docs/caa/).
1. Go to step 1.
1. If you're still getting the same error:
1. Make sure you have properly set only one `CNAME` or `A` DNS record for your domain.
1. Make sure your domain **doesn't have** an `AAAA` DNS record.
1. If you have a `CAA` DNS record for your domain or any higher level domains, make sure [it includes `letsencrypt.org`](https://letsencrypt.org/docs/caa/).
1. Go to step 1.
<!-- Include any troubleshooting steps that you can foresee. If you know beforehand what issues
one might have when setting this up, or when something is changed, or on upgrading, it's

View File

@ -27,9 +27,11 @@ these steps, you may have to do additional configuration for the Pages site to g
If everything is configured correctly, the site can take approximately 30 minutes to deploy.
To view the pipeline, go to **CI/CD > Pipelines**.
When the pipeline is finished, go to **Settings > Pages** to find the link to
your Pages website. (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test))
your Pages website.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
For every change pushed to your repository, GitLab CI/CD runs a new pipeline
that immediately publishes your changes to the Pages site.

View File

@ -24,9 +24,10 @@ To fork a sample project and create a Pages website:
GitLab CI/CD builds and deploys your site.
The site can take approximately 30 minutes to deploy.
When the pipeline is finished, go to **Settings > Pages** to find the link
to your website from your project. (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test))
When the pipeline is finished, go to **Settings > Pages** to find the link to
your Pages website.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
For every change pushed to your repository, GitLab CI/CD runs a new pipeline
that immediately publishes your changes to the Pages site.

View File

@ -175,10 +175,11 @@ deploy your website:
1. Save and commit the `.gitlab-ci.yml` file.
1. Go to **CI/CD > Pipelines** to watch the pipeline.
1. When the pipeline succeeds, go to **Settings > Pages**
to view the URL where your site is now available. (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test))
1. When the pipeline is finished, go to **Settings > Pages** to find the link to
your Pages website.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
When this `pages` job completes successfully, a special `pages:deploy` job
appears in the pipeline view. It prepares the content of the website for the
GitLab Pages daemon. GitLab runs it in the background and doesn't use a runner.

View File

@ -24,8 +24,9 @@ configured to generate a Pages site.
site.
When the pipeline is finished, go to **Settings > Pages** to find the link to
your Pages website. (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test))
your Pages website.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
For every change pushed to your repository, GitLab CI/CD runs a new pipeline
that immediately publishes your changes to the Pages site.

View File

@ -34,8 +34,10 @@ a pipeline deploys your Pages website.
To complete the setup and generate a GitLab Pages deployment:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages** (Note: this may also be
located at **Deployments > Pages**, [more information](../index.md#menu-position-test)).
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](../index.md#menu-position-test).
A **Get Started with Pages** form appears. If this form is not available,
see [Troubleshooting](#if-the-get-started-with-pages-form-is-not-available).
1. For **Step 1**, enter an image name and verify that your files are in a `public` folder.
@ -61,10 +63,10 @@ and on the right side, select **Download artifacts**.
### If the `Get Started with Pages` form is not available
When you go to **Settings > Pages**, the form is not available if you:
The `Get Started with Pages` form is not available if you:
- Deployed a GitLab Pages site before.
- Committed a `.gitlab-ci.yml` through the forms at least one time.
- Committed `.gitlab-ci.yml` through the forms at least one time.
To fix this issue:

View File

@ -68,6 +68,9 @@ To remove your pages:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Settings > Pages**.
If this path is not visible, select **Deployments > Pages**.
[This location is part of an experiment](index.md#menu-position-test).
1. Select **Remove pages**.
## Subdomains of subdomains

View File

@ -180,6 +180,47 @@ you can sign individual commits manually, or configure Git to default to signed
git config --global commit.gpgsign true
```
#### Set signing key conditionally
If you maintain signing keys for separate purposes, such as work and personal
use, use an `IncludeIf` statement in your `.gitconfig` file to set which key
you sign commits with.
Prerequisites:
- Requires Git version 2.13 or later.
1. In the same directory as your main `~/.gitconfig` file, create a second file,
such as `.gitconfig-gitlab`.
1. In your main `~/.gitconfig` file, add your Git settings for work in non-GitLab projects.
1. Append this information to the end of your main `~/.gitconfig` file:
```ini
# The contents of this file are included only for GitLab.com URLs
[includeIf "hasconfig:remote.*.url:https://gitlab.com/**"]
# Edit this line to point to your alternate configuration file
path = ~/.gitconfig-gitlab
```
1. In your alternate `.gitconfig-gitlab` file, add the configuration overrides to
use when you're committing to a GitLab repository. All settings from your
main `~/.gitconfig` file are retained unless you explicitly override them.
In this example,
```ini
# Alternate ~/.gitconfig-gitlab file
# These values are used for repositories matching the string 'gitlab.com',
# and override their corresponding values in ~/.gitconfig
[user]
email = you@example.com
signingkey = <KEY ID>
[commit]
gpgsign = true
```
## Verify commits
You can review commits for a merge request, or for an entire project:

View File

@ -6,10 +6,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Web Editor **(FREE)**
You can use the Web Editor to make changes directly from the GitLab UI instead of
cloning a project and using the command line.
You can use the Web Editor to make changes to a single file directly from the
GitLab UI. To make changes to multiple files, see [Web IDE](../web_ide/index.md).
From the project dashboard or repository, you can:
In the Web Editor, you can:
- [Create a file](#create-a-file).
- [Edit a file](#edit-a-file).
@ -18,8 +18,8 @@ From the project dashboard or repository, you can:
- [Create a branch](#create-a-branch).
- [Create a tag](#create-a-tag).
Your [primary email address](../../../user/profile/index.md#change-the-email-displayed-on-your-commits)
is used by default for any change you commit through the Web Editor.
Your [primary email address is used by default](../../../user/profile/index.md#change-the-email-displayed-on-your-commits)
for any change you commit through the Web Editor.
## Create a file
@ -28,18 +28,22 @@ To create a text file in the Web Editor:
1. On the top bar, select **Main menu > Projects** and find your project.
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
1. From the dropdown list, select **New file**.
1. Complete the fields.
- From the **Select a template type** dropdown list, you can apply a template to the new file.
- To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected.
1. Complete the fields. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected.
1. Select **Commit changes**.
## Edit a file
To edit a file in the Web Editor:
To edit a text file in the Web Editor:
1. On the top bar, select **Main menu > Projects** and find your project.
1. Go to your file.
1. Next to the display buttons, select **Edit**.
1. In the upper-right corner of the file, select **Edit**.
If **Edit** is not visible:
1. Next to **Open in Web IDE** or **Open in Gitpod**, select the down arrow (**{chevron-lg-down}**).
1. From the dropdown list, select **Edit** as your default setting.
1. Select **Edit**.
### Keyboard shortcuts

View File

@ -113,6 +113,8 @@ With Service Desk, you can use templates for:
#### Email header and footer **(FREE SELF)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344819) in GitLab 15.9.
Instance administrators can add a small header or footer to the GitLab instance and make them
visible in the email template. For more information, see
[System header and footer messages](../admin_area/appearance.md#system-header-and-footer-messages).

View File

@ -6,9 +6,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Web IDE **(FREE)**
The Web Integrated Development Environment (IDE) editor streamlines the process
to contribute changes to your projects, by providing an advanced editor with
commit staging.
The Web IDE is an advanced editor with commit staging.
You can use the Web IDE to make changes to multiple files directly from the
GitLab UI. For a more basic implementation, see [Web Editor](../repository/web_editor.md).
NOTE:
The Web IDE is being updated to use VS Code. For details,

View File

@ -15,6 +15,7 @@ module API
expose :created_at, documentation: { type: 'dateTime', example: '2015-12-24T15:51:21.880Z' }
expose :started_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:30.733Z' }
expose :finished_at, documentation: { type: 'dateTime', example: '2015-12-24T17:54:31.198Z' }
expose :erased_at, documentation: { type: 'dateTime', example: '2015-12-24T18:00:29.728Z' }
expose :duration,
documentation: { type: 'number', format: 'float', desc: 'Time spent running', example: 0.465 }
expose :queued_duration,

View File

@ -21378,6 +21378,9 @@ msgstr ""
msgid "Improve quality with test cases"
msgstr ""
msgid "In GitLab"
msgstr ""
msgid "In case of pull mirroring, your user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
@ -21390,9 +21393,15 @@ msgstr ""
msgid "In the background, we're attempting to connect you again."
msgstr ""
msgid "In this group"
msgstr ""
msgid "In this page you will find information about the settings that are used in your current instance."
msgstr ""
msgid "In this project"
msgstr ""
msgid "In use"
msgstr ""
@ -22143,6 +22152,9 @@ msgstr ""
msgid "Incident|Add new timeline event"
msgstr ""
msgid "Incident|Adding an event tag associates the timeline comment with specific incident metrics."
msgstr ""
msgid "Incident|Alert details"
msgstr ""
@ -22176,6 +22188,9 @@ msgstr ""
msgid "Incident|Error updating incident timeline event: %{error}"
msgstr ""
msgid "Incident|Event tag"
msgstr ""
msgid "Incident|Incident"
msgstr ""
@ -43370,9 +43385,6 @@ msgstr ""
msgid "This process deletes the project repository and all related resources."
msgstr ""
msgid "This project"
msgstr ""
msgid "This project can be restored until %{date}."
msgstr ""

View File

@ -57,8 +57,8 @@
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.1.0",
"@gitlab/svgs": "3.16.0",
"@gitlab/ui": "52.11.0",
"@gitlab/svgs": "3.17.0",
"@gitlab/ui": "52.13.1",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230120231236",
"@rails/actioncable": "6.1.4-7",

View File

@ -8,18 +8,25 @@ module QA
QA::EE::Resource::Settings::Elasticsearch.fabricate_via_api! unless advanced_search_on
end
after do
Runtime::Search.disable_elasticsearch(api_client) if !advanced_search_on && !api_client.nil?
end
# TODO: convert check_advanced_search_status method to use the API instead of the UI once the functionality exists
# https://gitlab.com/gitlab-org/gitlab/-/issues/382849 and then we can resume turning off advanced search after the
# tests as in the `after` block here. For now the advanced search tests will have the side effect of turning on
# advanced search if it wasn't enabled before the tests run.
# after do
# Runtime::Search.disable_elasticsearch(api_client) if !advanced_search_on && !api_client.nil?
# end
# TODO: convert this method to use the API instead of the UI once the functionality exists
# https://gitlab.com/gitlab-org/gitlab/-/issues/382849
def check_advanced_search_status
Flow::Login.sign_in
QA::Page::Main::Menu.perform do |menu|
menu.search_for('lorem ipsum')
QA::Support::Retrier.retry_on_exception(
max_attempts: Runtime::Search::RETRY_MAX_ITERATION,
sleep_interval: Runtime::Search::RETRY_SLEEP_INTERVAL) do
QA::Page::Main::Menu.perform do |menu|
menu.search_for('lorem ipsum')
end
page.has_text?('Advanced search is enabled')
end
page.has_text?('Advanced search is enabled')
end
end
end

View File

@ -92,11 +92,11 @@ RSpec.describe "Admin > Admin sees background migrations", feature_category: :da
expect(page).not_to have_content('Paused')
expect(page).to have_content('Active')
click_button('Pause')
click_on('Pause')
expect(page).not_to have_content('Active')
expect(page).to have_content('Paused')
click_button('Resume')
click_on('Resume')
expect(page).not_to have_content('Paused')
expect(page).to have_content('Active')
end
@ -123,7 +123,7 @@ RSpec.describe "Admin > Admin sees background migrations", feature_category: :da
tab = find_link 'Failed'
tab.click
expect(page).to have_selector("[method='post'][action='/admin/background_migrations/#{failed_migration.id}/retry?database=main']")
expect(page).to have_selector("[data-method='post'][href='/admin/background_migrations/#{failed_migration.id}/retry?database=main']")
end
end
@ -144,7 +144,7 @@ RSpec.describe "Admin > Admin sees background migrations", feature_category: :da
expect(page).to have_content('0.00%')
expect(page).to have_content(failed_migration.status_name.to_s)
click_button('Retry')
click_on('Retry')
expect(page).not_to have_content(failed_migration.job_class_name)
expect(page).not_to have_content(failed_migration.table_name)
expect(page).not_to have_content('0.00%')

View File

@ -861,7 +861,7 @@ RSpec.describe 'Admin updates settings', feature_category: :not_owned do
context 'Nav bar' do
it 'shows default help links in nav' do
default_support_url = "https://#{ApplicationHelper.promo_host}/getting-help/"
default_support_url = "https://#{ApplicationHelper.promo_host}/get-help/"
visit root_dashboard_path

View File

@ -7,6 +7,7 @@ RSpec.describe 'User activates Slack notifications', :js, feature_category: :int
context 'when integration is not configured yet' do
before do
stub_feature_flags(integration_slack_app_notifications: false)
visit_project_integration('Slack notifications')
end

View File

@ -11,6 +11,7 @@
"created_at",
"started_at",
"finished_at",
"erased_at",
"duration",
"queued_duration",
"user",
@ -35,6 +36,7 @@
"created_at": { "type": "string" },
"started_at": { "type": ["null", "string"] },
"finished_at": { "type": ["null", "string"] },
"erased_at": { "type": ["null", "string"] },
"duration": { "type": ["null", "number"] },
"queued_duration": { "type": ["null", "number"] },
"user": { "$ref": "user/basic.json" },

View File

@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { redirectTo } from '~/lib/utils/url_utility';
import BroadcastMessagesBase from '~/admin/broadcast_messages/components/base.vue';
import MessagesTable from '~/admin/broadcast_messages/components/messages_table.vue';
@ -86,7 +87,7 @@ describe('BroadcastMessagesBase', () => {
it('removes a deleted message from visibleMessages on success', async () => {
createComponent();
const { id, delete_path } = MOCK_MESSAGES[0];
axiosMock.onDelete(delete_path).replyOnce(200);
axiosMock.onDelete(delete_path).replyOnce(HTTP_STATUS_OK);
findTable().vm.$emit('delete-message', id);
await waitForPromises();
@ -102,7 +103,7 @@ describe('BroadcastMessagesBase', () => {
const { id, delete_path } = messages[0];
createComponent({ messages, messagesCount: messages.length });
axiosMock.onDelete(delete_path).replyOnce(200);
axiosMock.onDelete(delete_path).replyOnce(HTTP_STATUS_OK);
findTable().vm.$emit('delete-message', id);
await waitForPromises();

View File

@ -17,6 +17,7 @@ import * as types from '~/feature_flags/store/edit/mutation_types';
import state from '~/feature_flags/store/edit/state';
import { mapStrategiesToRails } from '~/feature_flags/store/helpers';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
jest.mock('~/lib/utils/url_utility');
@ -55,7 +56,9 @@ describe('Feature flags Edit Module actions', () => {
},
],
};
mock.onPut(mockedState.endpoint, mapStrategiesToRails(featureFlag)).replyOnce(200);
mock
.onPut(mockedState.endpoint, mapStrategiesToRails(featureFlag))
.replyOnce(HTTP_STATUS_OK);
return testAction(
updateFeatureFlag,
@ -155,7 +158,7 @@ describe('Feature flags Edit Module actions', () => {
describe('success', () => {
it('dispatches requestFeatureFlag and receiveFeatureFlagSuccess', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 1 });
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(HTTP_STATUS_OK, { id: 1 });
return testAction(
fetchFeatureFlag,

View File

@ -11,6 +11,7 @@ import {
import * as types from '~/feature_flags/store/new/mutation_types';
import state from '~/feature_flags/store/new/state';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
jest.mock('~/lib/utils/url_utility');
@ -48,7 +49,9 @@ describe('Feature flags New Module Actions', () => {
},
],
};
mock.onPost(mockedState.endpoint, mapStrategiesToRails(actionParams)).replyOnce(200);
mock
.onPost(mockedState.endpoint, mapStrategiesToRails(actionParams))
.replyOnce(HTTP_STATUS_OK);
return testAction(
createFeatureFlag,

View File

@ -3,6 +3,7 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'spec/test_constants';
import GpgBadges from '~/gpg_badges';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('GpgBadges', () => {
let mock;
@ -63,7 +64,7 @@ describe('GpgBadges', () => {
});
it('fetches commit signatures', async () => {
mock.onGet(dummyUrl).replyOnce(200);
mock.onGet(dummyUrl).replyOnce(HTTP_STATUS_OK);
await GpgBadges.fetch();
@ -75,7 +76,7 @@ describe('GpgBadges', () => {
});
it('fetches commit signatures with search parameters with spaces', async () => {
mock.onGet(dummyUrl).replyOnce(200);
mock.onGet(dummyUrl).replyOnce(HTTP_STATUS_OK);
setForm({ search: 'my search' });
await GpgBadges.fetch();
@ -88,7 +89,7 @@ describe('GpgBadges', () => {
});
it('fetches commit signatures with search parameters with plus symbols', async () => {
mock.onGet(dummyUrl).replyOnce(200);
mock.onGet(dummyUrl).replyOnce(HTTP_STATUS_OK);
setForm({ search: 'my+search' });
await GpgBadges.fetch();
@ -101,7 +102,7 @@ describe('GpgBadges', () => {
});
it('displays a loading spinner', async () => {
mock.onGet(dummyUrl).replyOnce(200);
mock.onGet(dummyUrl).replyOnce(HTTP_STATUS_OK);
await GpgBadges.fetch();
expect(document.querySelector('.js-loading-gpg-badge:empty')).toBe(null);
@ -111,7 +112,7 @@ describe('GpgBadges', () => {
});
it('replaces the loading spinner', async () => {
mock.onGet(dummyUrl).replyOnce(200, dummyResponse);
mock.onGet(dummyUrl).replyOnce(HTTP_STATUS_OK, dummyResponse);
await GpgBadges.fetch();
expect(document.querySelector('.js-loading-gpg-badge')).toBe(null);

View File

@ -6,6 +6,7 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import InviteMembersBanner from '~/groups/components/invite_members_banner.vue';
import eventHub from '~/invite_members/event_hub';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
jest.mock('~/lib/utils/common_utils');
@ -89,7 +90,7 @@ describe('InviteMembersBanner', () => {
it('sends the dismissEvent when the banner is dismissed', () => {
mockTrackingOnWrapper();
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
mockAxios.onPost(provide.calloutsPath).replyOnce(HTTP_STATUS_OK);
const dismissEvent = 'invite_members_banner_dismissed';
wrapper.findComponent(GlBanner).vm.$emit('close');
@ -136,7 +137,7 @@ describe('InviteMembersBanner', () => {
});
it('should close the banner when dismiss is clicked', async () => {
mockAxios.onPost(provide.calloutsPath).replyOnce(200);
mockAxios.onPost(provide.calloutsPath).replyOnce(HTTP_STATUS_OK);
expect(wrapper.findComponent(GlBanner).exists()).toBe(true);
wrapper.findComponent(GlBanner).vm.$emit('close');

View File

@ -1,5 +1,6 @@
import { GlTable, GlIcon } from '@gitlab/ui';
import { GlTable, GlIcon, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { INTEGRATION_TYPE_SLACK } from '~/integrations/constants';
import IntegrationsTable from '~/integrations/index/components/integrations_table.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@ -10,12 +11,17 @@ describe('IntegrationsTable', () => {
const findTable = () => wrapper.findComponent(GlTable);
const createComponent = (propsData = {}) => {
const createComponent = (propsData = {}, flagIsOn = false) => {
wrapper = mount(IntegrationsTable, {
propsData: {
integrations: mockActiveIntegrations,
...propsData,
},
provide: {
glFeatures: {
integrationSlackAppNotifications: flagIsOn,
},
},
});
};
@ -50,4 +56,51 @@ describe('IntegrationsTable', () => {
expect(findTable().findComponent(GlIcon).exists()).toBe(shouldRenderActiveIcon);
});
});
describe('integrations filtering', () => {
const slackActive = {
...mockActiveIntegrations[0],
name: INTEGRATION_TYPE_SLACK,
title: 'Slack',
};
const slackInactive = {
...mockInactiveIntegrations[0],
name: INTEGRATION_TYPE_SLACK,
title: 'Slack',
};
describe.each`
desc | flagIsOn | integrations | expectedIntegrations
${'only active'} | ${false} | ${mockActiveIntegrations} | ${mockActiveIntegrations}
${'only active'} | ${true} | ${mockActiveIntegrations} | ${mockActiveIntegrations}
${'only inactive'} | ${true} | ${mockInactiveIntegrations} | ${mockInactiveIntegrations}
${'only inactive'} | ${false} | ${mockInactiveIntegrations} | ${mockInactiveIntegrations}
${'active and inactive'} | ${true} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]}
${'active and inactive'} | ${false} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]}
${'Slack active with active'} | ${false} | ${[slackActive, ...mockActiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations]}
${'Slack active with active'} | ${true} | ${[slackActive, ...mockActiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations]}
${'Slack active with inactive'} | ${false} | ${[slackActive, ...mockInactiveIntegrations]} | ${[slackActive, ...mockInactiveIntegrations]}
${'Slack active with inactive'} | ${true} | ${[slackActive, ...mockInactiveIntegrations]} | ${[slackActive, ...mockInactiveIntegrations]}
${'Slack inactive with active'} | ${false} | ${[slackInactive, ...mockActiveIntegrations]} | ${[slackInactive, ...mockActiveIntegrations]}
${'Slack inactive with active'} | ${true} | ${[slackInactive, ...mockActiveIntegrations]} | ${mockActiveIntegrations}
${'Slack inactive with inactive'} | ${false} | ${[slackInactive, ...mockInactiveIntegrations]} | ${[slackInactive, ...mockInactiveIntegrations]}
${'Slack inactive with inactive'} | ${true} | ${[slackInactive, ...mockInactiveIntegrations]} | ${mockInactiveIntegrations}
${'Slack active with active and inactive'} | ${true} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]}
${'Slack active with active and inactive'} | ${false} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]}
${'Slack inactive with active and inactive'} | ${true} | ${[slackInactive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]}
${'Slack inactive with active and inactive'} | ${false} | ${[slackInactive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[slackInactive, ...mockActiveIntegrations, ...mockInactiveIntegrations]}
`('when $desc and flag "$flagIsOn"', ({ flagIsOn, integrations, expectedIntegrations }) => {
beforeEach(() => {
createComponent({ integrations }, flagIsOn);
});
it('renders correctly', () => {
const links = wrapper.findAllComponents(GlLink);
expect(links).toHaveLength(expectedIntegrations.length);
expectedIntegrations.forEach((integration, index) => {
expect(links.at(index).text()).toBe(integration.title);
});
});
});
});
});

View File

@ -0,0 +1,48 @@
import { nextTick } from 'vue';
import { GlIcon, GlPopover, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { helpPagePath } from '~/helpers/help_page_helper';
import TimelineEventsTagsPopover from '~/issues/show/components/incidents/timeline_events_tags_popover.vue';
describe('TimelineEventsTagsPopover component', () => {
let wrapper;
const mountComponent = () => {
wrapper = shallowMount(TimelineEventsTagsPopover, {
stubs: {
GlPopover,
},
});
};
beforeEach(() => {
mountComponent();
});
const findQuestionIcon = () => wrapper.findComponent(GlIcon);
const findPopover = () => wrapper.findComponent(GlPopover);
const findDocumentationLink = () => findPopover().findComponent(GlLink);
describe('question icon', () => {
it('should open a popover with a link when hovered', async () => {
findQuestionIcon().vm.$emit('hover');
await nextTick();
expect(findPopover().exists()).toBe(true);
expect(findDocumentationLink().exists()).toBe(true);
});
});
describe('documentation link', () => {
it('redirects to a correct documentation page', async () => {
findQuestionIcon().vm.$emit('hover');
await nextTick();
expect(findDocumentationLink().attributes('href')).toBe(
helpPagePath('/ee/operations/incident_management/incident_timeline_events', {
anchor: 'incident-tags',
}),
);
});
});
});

View File

@ -1,6 +1,6 @@
import { ApolloLink, Observable } from '@apollo/client/core';
import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('StartupJSLink', () => {
const FORWARDED_RESPONSE = { data: 'FORWARDED_RESPONSE' };
@ -38,7 +38,7 @@ describe('StartupJSLink', () => {
let startupLink;
let link;
function mockFetchCall(status = 200, response = STARTUP_JS_RESPONSE) {
function mockFetchCall(status = HTTP_STATUS_OK, response = STARTUP_JS_RESPONSE) {
const p = {
ok: status >= 200 && status < 300,
status,
@ -210,7 +210,7 @@ describe('StartupJSLink', () => {
window.gl = {
startup_graphql_calls: [
{
fetchCall: mockFetchCall(200, ERROR_RESPONSE),
fetchCall: mockFetchCall(HTTP_STATUS_OK, ERROR_RESPONSE),
query: STARTUP_JS_QUERY,
variables: { id: 3 },
},
@ -227,7 +227,7 @@ describe('StartupJSLink', () => {
window.gl = {
startup_graphql_calls: [
{
fetchCall: mockFetchCall(200, { 'no-data': 'yay' }),
fetchCall: mockFetchCall(HTTP_STATUS_OK, { 'no-data': 'yay' }),
query: STARTUP_JS_QUERY,
variables: { id: 3 },
},
@ -340,7 +340,7 @@ describe('StartupJSLink', () => {
variables: { id: 3 },
},
{
fetchCall: mockFetchCall(200, STARTUP_JS_RESPONSE_TWO),
fetchCall: mockFetchCall(HTTP_STATUS_OK, STARTUP_JS_RESPONSE_TWO),
query: STARTUP_JS_QUERY_TWO,
variables: { id: 3 },
},

View File

@ -1,7 +1,7 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import setupAxiosStartupCalls from '~/lib/utils/axios_startup_calls';
import { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('setupAxiosStartupCalls', () => {
const AXIOS_RESPONSE = { text: 'AXIOS_RESPONSE' };
@ -32,9 +32,9 @@ describe('setupAxiosStartupCalls', () => {
beforeEach(() => {
window.gl = {};
mock = new MockAdapter(axios);
mock.onGet('/non-startup').reply(200, AXIOS_RESPONSE);
mock.onGet('/startup').reply(200, AXIOS_RESPONSE);
mock.onGet('/startup-failing').reply(200, AXIOS_RESPONSE);
mock.onGet('/non-startup').reply(HTTP_STATUS_OK, AXIOS_RESPONSE);
mock.onGet('/startup').reply(HTTP_STATUS_OK, AXIOS_RESPONSE);
mock.onGet('/startup-failing').reply(HTTP_STATUS_OK, AXIOS_RESPONSE);
});
afterEach(() => {
@ -53,7 +53,7 @@ describe('setupAxiosStartupCalls', () => {
beforeEach(() => {
window.gl.startup_calls = {
'/startup': {
fetchCall: mockFetchCall(200),
fetchCall: mockFetchCall(HTTP_STATUS_OK),
},
'/startup-failing': {
fetchCall: mockFetchCall(HTTP_STATUS_BAD_REQUEST),
@ -81,7 +81,7 @@ describe('setupAxiosStartupCalls', () => {
const { headers, data, status, statusText } = await axios.get('/startup');
expect(headers).toEqual({ 'content-type': 'application/json' });
expect(status).toBe(200);
expect(status).toBe(HTTP_STATUS_OK);
expect(statusText).toBe('MOCK-FETCH 200');
expect(data).toEqual(STARTUP_JS_RESPONSE);
expect(data).not.toEqual(AXIOS_RESPONSE);
@ -127,7 +127,7 @@ describe('setupAxiosStartupCalls', () => {
it('removes GitLab Base URL from startup call', async () => {
window.gl.startup_calls = {
'/startup': {
fetchCall: mockFetchCall(200),
fetchCall: mockFetchCall(HTTP_STATUS_OK),
},
};
setupAxiosStartupCalls(axios);
@ -140,7 +140,7 @@ describe('setupAxiosStartupCalls', () => {
it('sorts the params in the requested API url', async () => {
window.gl.startup_calls = {
'/startup?alpha=true&bravo=true': {
fetchCall: mockFetchCall(200),
fetchCall: mockFetchCall(HTTP_STATUS_OK),
},
};
setupAxiosStartupCalls(axios);

View File

@ -28,8 +28,8 @@ describe('axios_utils', () => {
return axios.waitForAll().finally(() => {
expect(handler).toHaveBeenCalledTimes(2);
expect(handler.mock.calls[0][0].status).toBe(200);
expect(handler.mock.calls[1][0].response.status).toBe(500);
expect(handler.mock.calls[0][0].status).toBe(HTTP_STATUS_OK);
expect(handler.mock.calls[1][0].response.status).toBe(HTTP_STATUS_INTERNAL_SERVER_ERROR);
});
});
});

View File

@ -5,6 +5,7 @@ exports[`EmptyState shows gettingStarted state 1`] = `
<!---->
<gl-empty-state-stub
contentclass=""
description="Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
invertindarkmode="true"
primarybuttonlink="/clustersPath"
@ -22,6 +23,7 @@ exports[`EmptyState shows noData state 1`] = `
<!---->
<gl-empty-state-stub
contentclass=""
description="You are connected to the Prometheus server, but there is currently no data to display."
invertindarkmode="true"
primarybuttonlink="/settingsPath"
@ -39,6 +41,7 @@ exports[`EmptyState shows unableToConnect state 1`] = `
<!---->
<gl-empty-state-stub
contentclass=""
description="Ensure connectivity is available from the GitLab server to the Prometheus server"
invertindarkmode="true"
primarybuttonlink="/documentationPath"

View File

@ -3,6 +3,7 @@
exports[`GroupEmptyState given state BAD_QUERY passes the expected props to GlEmptyState 1`] = `
Object {
"compact": true,
"contentClass": Array [],
"description": null,
"invertInDarkMode": true,
"primaryButtonLink": "/path/to/settings",
@ -31,6 +32,7 @@ exports[`GroupEmptyState given state BAD_QUERY renders the slotted content 1`] =
exports[`GroupEmptyState given state CONNECTION_FAILED passes the expected props to GlEmptyState 1`] = `
Object {
"compact": true,
"contentClass": Array [],
"description": "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating.",
"invertInDarkMode": true,
"primaryButtonLink": "/path/to/settings",
@ -48,6 +50,7 @@ exports[`GroupEmptyState given state CONNECTION_FAILED renders the slotted conte
exports[`GroupEmptyState given state FOO STATE passes the expected props to GlEmptyState 1`] = `
Object {
"compact": true,
"contentClass": Array [],
"description": "An error occurred while loading the data. Please try again.",
"invertInDarkMode": true,
"primaryButtonLink": null,
@ -65,6 +68,7 @@ exports[`GroupEmptyState given state FOO STATE renders the slotted content 1`] =
exports[`GroupEmptyState given state LOADING passes the expected props to GlEmptyState 1`] = `
Object {
"compact": true,
"contentClass": Array [],
"description": "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available.",
"invertInDarkMode": true,
"primaryButtonLink": null,
@ -82,6 +86,7 @@ exports[`GroupEmptyState given state LOADING renders the slotted content 1`] = `
exports[`GroupEmptyState given state NO_DATA passes the expected props to GlEmptyState 1`] = `
Object {
"compact": true,
"contentClass": Array [],
"description": null,
"invertInDarkMode": true,
"primaryButtonLink": null,
@ -110,6 +115,7 @@ exports[`GroupEmptyState given state NO_DATA renders the slotted content 1`] = `
exports[`GroupEmptyState given state TIMEOUT passes the expected props to GlEmptyState 1`] = `
Object {
"compact": true,
"contentClass": Array [],
"description": null,
"invertInDarkMode": true,
"primaryButtonLink": null,
@ -138,6 +144,7 @@ exports[`GroupEmptyState given state TIMEOUT renders the slotted content 1`] = `
exports[`GroupEmptyState given state UNKNOWN_ERROR passes the expected props to GlEmptyState 1`] = `
Object {
"compact": true,
"contentClass": Array [],
"description": "An error occurred while loading the data. Please try again.",
"invertInDarkMode": true,
"primaryButtonLink": null,

View File

@ -9,6 +9,7 @@ import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
import axios from '~/lib/utils/axios_utils';
import {
HTTP_STATUS_INTERNAL_SERVER_ERROR,
HTTP_STATUS_OK,
HTTP_STATUS_SERVICE_UNAVAILABLE,
} from '~/lib/utils/http_status';
import * as notesConstants from '~/notes/constants';
@ -179,7 +180,7 @@ describe('Actions Notes Store', () => {
describe('async methods', () => {
beforeEach(() => {
axiosMock.onAny().reply(200, {});
axiosMock.onAny().reply(HTTP_STATUS_OK, {});
});
describe('closeMergeRequest', () => {
@ -253,7 +254,7 @@ describe('Actions Notes Store', () => {
const pollResponse = { notes: [], last_fetched_at: '123456' };
const pollHeaders = { 'poll-interval': `${pollInterval}` };
const successMock = () =>
axiosMock.onGet(notesDataMock.notesPath).reply(200, pollResponse, pollHeaders);
axiosMock.onGet(notesDataMock.notesPath).reply(HTTP_STATUS_OK, pollResponse, pollHeaders);
const failureMock = () =>
axiosMock.onGet(notesDataMock.notesPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
const advanceAndRAF = async (time) => {
@ -350,7 +351,7 @@ describe('Actions Notes Store', () => {
.onGet(notesDataMock.notesPath)
.replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR) // cause one error
.onGet(notesDataMock.notesPath)
.replyOnce(200, pollResponse, pollHeaders) // then a success
.replyOnce(HTTP_STATUS_OK, pollResponse, pollHeaders) // then a success
.onGet(notesDataMock.notesPath)
.reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); // and then more errors
@ -403,7 +404,7 @@ describe('Actions Notes Store', () => {
const endpoint = `${TEST_HOST}/note`;
beforeEach(() => {
axiosMock.onDelete(endpoint).replyOnce(200, {});
axiosMock.onDelete(endpoint).replyOnce(HTTP_STATUS_OK, {});
document.body.dataset.page = '';
});
@ -472,7 +473,7 @@ describe('Actions Notes Store', () => {
const endpoint = `${TEST_HOST}/note`;
beforeEach(() => {
axiosMock.onDelete(endpoint).replyOnce(200, {});
axiosMock.onDelete(endpoint).replyOnce(HTTP_STATUS_OK, {});
document.body.dataset.page = '';
});
@ -512,7 +513,7 @@ describe('Actions Notes Store', () => {
};
beforeEach(() => {
axiosMock.onAny().reply(200, res);
axiosMock.onAny().reply(HTTP_STATUS_OK, res);
});
it('commits ADD_NEW_NOTE and dispatches updateMergeRequestWidget', () => {
@ -547,7 +548,7 @@ describe('Actions Notes Store', () => {
};
beforeEach(() => {
axiosMock.onAny().replyOnce(200, res);
axiosMock.onAny().replyOnce(HTTP_STATUS_OK, res);
});
it('does not commit ADD_NEW_NOTE or dispatch updateMergeRequestWidget', () => {
@ -568,7 +569,7 @@ describe('Actions Notes Store', () => {
};
beforeEach(() => {
axiosMock.onAny().reply(200, res);
axiosMock.onAny().reply(HTTP_STATUS_OK, res);
});
describe('as note', () => {
@ -759,7 +760,7 @@ describe('Actions Notes Store', () => {
it('updates discussion if response contains disussion', () => {
const discussion = { notes: [] };
axiosMock.onAny().reply(200, { discussion });
axiosMock.onAny().reply(HTTP_STATUS_OK, { discussion });
return testAction(
actions.replyToDiscussion,
@ -778,7 +779,7 @@ describe('Actions Notes Store', () => {
it('adds a reply to a discussion', () => {
const res = {};
axiosMock.onAny().reply(200, res);
axiosMock.onAny().reply(HTTP_STATUS_OK, res);
return testAction(
actions.replyToDiscussion,
@ -1191,7 +1192,7 @@ describe('Actions Notes Store', () => {
describe('if response contains no errors', () => {
it('dispatches requestDeleteDescriptionVersion', () => {
axiosMock.onDelete(endpoint).replyOnce(200);
axiosMock.onDelete(endpoint).replyOnce(HTTP_STATUS_OK);
return testAction(
actions.softDeleteDescriptionVersion,
payload,
@ -1443,7 +1444,7 @@ describe('Actions Notes Store', () => {
});
it('updates the discussions and dispatches `updateResolvableDiscussionsCounts`', () => {
axiosMock.onAny().reply(200, { discussion });
axiosMock.onAny().reply(HTTP_STATUS_OK, { discussion });
return testAction(
actions.fetchDiscussions,
{},
@ -1509,7 +1510,7 @@ describe('Actions Notes Store', () => {
const actionPayload = { config, path: 'test-path', perPage: 20 };
it('updates the discussions and dispatches `updateResolvableDiscussionsCounts if there are no headers', () => {
axiosMock.onAny().reply(200, { discussion }, {});
axiosMock.onAny().reply(HTTP_STATUS_OK, { discussion }, {});
return testAction(
actions.fetchDiscussionsBatch,
actionPayload,
@ -1524,7 +1525,7 @@ describe('Actions Notes Store', () => {
});
it('dispatches itself if there is `x-next-page-cursor` header', () => {
axiosMock.onAny().reply(200, { discussion }, { 'x-next-page-cursor': 1 });
axiosMock.onAny().reply(HTTP_STATUS_OK, { discussion }, { 'x-next-page-cursor': 1 });
return testAction(
actions.fetchDiscussionsBatch,
actionPayload,

View File

@ -30,6 +30,7 @@ exports[`packages_list_app renders 1`] = `
<div
class="gl-max-w-full gl-m-auto"
data-testid="gl-empty-state-content"
>
<div
class="gl-mx-auto gl-my-0 gl-p-5"

View File

@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
import { createAlert } from '~/flash';
import { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { MISSING_DELETE_PATH_ERROR } from '~/packages_and_registries/infrastructure_registry/list/constants';
import * as actions from '~/packages_and_registries/infrastructure_registry/list/stores/actions';
import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types';
@ -183,7 +183,7 @@ describe('Actions Package list store', () => {
},
};
it('should perform a delete operation on _links.delete_api_path', () => {
mock.onDelete(payload._links.delete_api_path).replyOnce(200);
mock.onDelete(payload._links.delete_api_path).replyOnce(HTTP_STATUS_OK);
Api.projectPackages = jest.fn().mockResolvedValue({ data: 'foo' });
return testAction(

View File

@ -3,6 +3,7 @@ import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import PersistentUserCallout from '~/persistent_user_callout';
jest.mock('~/flash');
@ -88,7 +89,7 @@ describe('PersistentUserCallout', () => {
${'primary'}
${'secondary'}
`('POSTs endpoint and removes container when clicking $button close', async ({ button }) => {
mockAxios.onPost(dismissEndpoint).replyOnce(200);
mockAxios.onPost(dismissEndpoint).replyOnce(HTTP_STATUS_OK);
buttons[button].click();
@ -140,7 +141,7 @@ describe('PersistentUserCallout', () => {
it('defers loading of a link until callout is dismissed', async () => {
const { href, target } = deferredLink;
mockAxios.onPost(dismissEndpoint).replyOnce(200);
mockAxios.onPost(dismissEndpoint).replyOnce(HTTP_STATUS_OK);
deferredLink.click();
@ -161,7 +162,7 @@ describe('PersistentUserCallout', () => {
});
it('does not follow link when notification is closed', async () => {
mockAxios.onPost(dismissEndpoint).replyOnce(200);
mockAxios.onPost(dismissEndpoint).replyOnce(HTTP_STATUS_OK);
button.click();
@ -195,7 +196,7 @@ describe('PersistentUserCallout', () => {
it('uses a link to trigger callout and defers following until callout is finished', async () => {
const { href } = link;
mockAxios.onPost(dismissEndpoint).replyOnce(200);
mockAxios.onPost(dismissEndpoint).replyOnce(HTTP_STATUS_OK);
link.click();

View File

@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import * as urlUtility from '~/lib/utils/url_utility';
import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarMediator from '~/sidebar/sidebar_mediator';
@ -36,10 +37,10 @@ describe('Sidebar mediator', () => {
});
it('saves assignees', () => {
mock.onPut(mediatorMockData.endpoint).reply(200, {});
mock.onPut(mediatorMockData.endpoint).reply(HTTP_STATUS_OK, {});
return mediator.saveAssignees('issue[assignee_ids]').then((resp) => {
expect(resp.status).toEqual(200);
expect(resp.status).toEqual(HTTP_STATUS_OK);
});
});
@ -91,7 +92,7 @@ describe('Sidebar mediator', () => {
it('fetches the data', async () => {
const mockData = Mock.responseMap.GET[mediatorMockData.endpoint];
mock.onGet(mediatorMockData.endpoint).reply(200, mockData);
mock.onGet(mediatorMockData.endpoint).reply(HTTP_STATUS_OK, mockData);
const spy = jest.spyOn(mediator, 'processFetchedData').mockReturnValue(Promise.resolve());
await mediator.fetch();
@ -120,7 +121,7 @@ describe('Sidebar mediator', () => {
it('fetches autocomplete projects', () => {
const searchTerm = 'foo';
mock.onGet(mediatorMockData.projectsAutocompleteEndpoint).reply(200, {});
mock.onGet(mediatorMockData.projectsAutocompleteEndpoint).reply(HTTP_STATUS_OK, {});
const getterSpy = jest
.spyOn(mediator.service, 'getProjectsAutocomplete')
.mockReturnValue(Promise.resolve({ data: {} }));
@ -137,7 +138,7 @@ describe('Sidebar mediator', () => {
it('moves issue', () => {
const mockData = Mock.responseMap.POST[mediatorMockData.moveIssueEndpoint];
const moveToProjectId = 7;
mock.onPost(mediatorMockData.moveIssueEndpoint).reply(200, mockData);
mock.onPost(mediatorMockData.moveIssueEndpoint).reply(HTTP_STATUS_OK, mockData);
mediator.store.setMoveToProjectId(moveToProjectId);
const moveIssueSpy = jest
.spyOn(mediator.service, 'moveIssue')

View File

@ -13,10 +13,6 @@ describe('Counter component', () => {
label: __('Issues'),
};
afterEach(() => {
wrapper.destroy();
});
const findButton = () => wrapper.find('button');
const findIcon = () => wrapper.getComponent(GlIcon);
const findLink = () => wrapper.find('a');

View File

@ -0,0 +1,33 @@
import { GlDisclosureDropdown } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { __ } from '~/locale';
import CreateMenu from '~/super_sidebar/components/create_menu.vue';
import { createNewMenuGroups } from '../mock_data';
describe('CreateMenu component', () => {
let wrapper;
const findGlDisclosureDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
const createWrapper = () => {
wrapper = shallowMountExtended(CreateMenu, {
propsData: {
groups: createNewMenuGroups,
},
});
};
describe('default', () => {
beforeEach(() => {
createWrapper();
});
it("sets the toggle's label", () => {
expect(findGlDisclosureDropdown().props('toggleText')).toBe(__('Create new...'));
});
it('passes the groups to the disclosure dropdown', () => {
expect(findGlDisclosureDropdown().props('items')).toBe(createNewMenuGroups);
});
});
});

View File

@ -8,10 +8,6 @@ describe('SuperSidebar component', () => {
const findUserBar = () => wrapper.findComponent(UserBar);
afterEach(() => {
wrapper.destroy();
});
const createWrapper = (props = {}) => {
wrapper = shallowMountExtended(SuperSidebar, {
propsData: {

View File

@ -1,5 +1,6 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { __ } from '~/locale';
import CreateMenu from '~/super_sidebar/components/create_menu.vue';
import Counter from '~/super_sidebar/components/counter.vue';
import UserBar from '~/super_sidebar/components/user_bar.vue';
import { sidebarData } from '../mock_data';
@ -7,12 +8,9 @@ import { sidebarData } from '../mock_data';
describe('UserBar component', () => {
let wrapper;
const findCreateMenu = () => wrapper.findComponent(CreateMenu);
const findCounter = (at) => wrapper.findAllComponents(Counter).at(at);
afterEach(() => {
wrapper.destroy();
});
const createWrapper = (props = {}) => {
wrapper = shallowMountExtended(UserBar, {
propsData: {
@ -31,6 +29,10 @@ describe('UserBar component', () => {
createWrapper();
});
it('passes the "Create new..." menu groups to the create-menu component', () => {
expect(findCreateMenu().props('groups')).toBe(sidebarData.create_new_menu_groups);
});
it('renders issues counter', () => {
expect(findCounter(0).props('count')).toBe(sidebarData.assigned_open_issues_count);
expect(findCounter(0).props('href')).toBe(sidebarData.issues_dashboard_path);

View File

@ -1,3 +1,44 @@
export const createNewMenuGroups = [
{
name: 'This group',
items: [
{
text: 'New project/repository',
href: '/projects/new?namespace_id=22',
},
{
text: 'New subgroup',
href: '/groups/new?parent_id=22#create-group-pane',
},
{
text: 'New epic',
href: '/groups/gitlab-org/-/epics/new',
},
{
text: 'Invite members',
href: '/groups/gitlab-org/-/group_members',
},
],
},
{
name: 'GitLab',
items: [
{
text: 'New project/repository',
href: '/projects/new',
},
{
text: 'New group',
href: '/groups/new',
},
{
text: 'New snippet',
href: '/-/snippets/new',
},
],
},
];
export const sidebarData = {
name: 'Administrator',
username: 'root',
@ -6,4 +47,5 @@ export const sidebarData = {
assigned_open_merge_requests_count: 2,
todos_pending_count: 3,
issues_dashboard_path: 'path/to/issues',
create_new_menu_groups: createNewMenuGroups,
};

View File

@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import dismissibleContainer from '~/vue_shared/components/dismissible_container.vue';
describe('DismissibleContainer', () => {
@ -28,7 +29,7 @@ describe('DismissibleContainer', () => {
});
it('successfully dismisses', () => {
mockAxios.onPost(propsData.path).replyOnce(200);
mockAxios.onPost(propsData.path).replyOnce(HTTP_STATUS_OK);
const button = findBtn();
button.trigger('click');

View File

@ -22,6 +22,7 @@ RSpec.describe Types::Ci::JobType do
detailedStatus
duration
downstreamPipeline
erasedAt
finished_at
id
kind

View File

@ -211,7 +211,7 @@ RSpec.describe ApplicationHelper do
describe '#support_url' do
context 'when alternate support url is specified' do
let(:alternate_url) { 'http://company.example.com/getting-help' }
let(:alternate_url) { 'http://company.example.com/get-help' }
it 'returns the alternate support url' do
stub_application_setting(help_page_support_url: alternate_url)
@ -222,7 +222,7 @@ RSpec.describe ApplicationHelper do
context 'when alternate support url is not specified' do
it 'builds the support url from the promo_url' do
expect(helper.support_url).to eq(helper.promo_url + '/getting-help/')
expect(helper.support_url).to eq(helper.promo_url + '/get-help/')
end
end
end

View File

@ -77,7 +77,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'has project menu item' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: _('GitLab'),
title: _('In GitLab'),
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'general_new_project',
title: 'New project/repository',
@ -95,7 +95,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'has group menu item' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: _('GitLab'),
title: _('In GitLab'),
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'general_new_group',
title: 'New group',
@ -113,7 +113,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'has new snippet menu item' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: _('GitLab'),
title: _('In GitLab'),
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'general_new_snippet',
title: 'New snippet',
@ -151,7 +151,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'has new project menu item' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: 'This group',
title: 'In this group',
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'new_project',
title: 'New project/repository',
@ -169,7 +169,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'has new subgroup menu item' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: 'This group',
title: 'In this group',
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'new_subgroup',
title: 'New subgroup',
@ -184,7 +184,7 @@ RSpec.describe Nav::NewDropdownHelper do
context 'when can invite members' do
let(:with_can_admin_in_group) { true }
let(:with_invite_members_experiment) { true }
let(:expected_title) { 'This group' }
let(:expected_title) { 'In this group' }
let(:expected_href) { "/groups/#{group.full_path}/-/group_members" }
it_behaves_like 'invite member link shared example'
@ -218,7 +218,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'shows new issue menu item' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: 'This project',
title: 'In this project',
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'new_issue',
title: 'New issue',
@ -236,7 +236,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'shows merge project' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: 'This project',
title: 'In this project',
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'new_mr',
title: 'New merge request',
@ -254,7 +254,7 @@ RSpec.describe Nav::NewDropdownHelper do
it 'shows new snippet' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: 'This project',
title: 'In this project',
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'new_snippet',
title: 'New snippet',
@ -269,7 +269,7 @@ RSpec.describe Nav::NewDropdownHelper do
context 'when invite members experiment' do
let(:with_invite_members_experiment) { true }
let(:with_can_admin_project_member) { true }
let(:expected_title) { 'This project' }
let(:expected_title) { 'In this project' }
let(:expected_href) { "/#{project.path_with_namespace}/-/project_members" }
it_behaves_like 'invite member link shared example'

View File

@ -47,15 +47,19 @@ RSpec.describe SidebarsHelper do
describe '#super_sidebar_context' do
let(:user) { build(:user) }
let(:group) { build(:group) }
subject { helper.super_sidebar_context(user) }
subject { helper.super_sidebar_context(user, group: group, project: nil) }
it 'returns sidebar values from user', :use_clean_rails_memory_store_caching do
before do
allow(helper).to receive(:current_user) { user }
Rails.cache.write(['users', user.id, 'assigned_open_issues_count'], 1)
Rails.cache.write(['users', user.id, 'assigned_open_merge_requests_count'], 2)
Rails.cache.write(['users', user.id, 'todos_pending_count'], 3)
end
expect(subject).to eq({
it 'returns sidebar values from user', :use_clean_rails_memory_store_caching do
expect(subject).to include({
name: user.name,
username: user.username,
avatar_url: user.avatar_url,
@ -65,5 +69,42 @@ RSpec.describe SidebarsHelper do
issues_dashboard_path: issues_dashboard_path(assignee_username: user.username)
})
end
it 'returns "Create new" menu groups without headers', :use_clean_rails_memory_store_caching do
expect(subject[:create_new_menu_groups]).to eq([
{
name: "",
items: [
{ href: "/projects/new", text: "New project/repository" },
{ href: "/groups/new", text: "New group" },
{ href: "/-/snippets/new", text: "New snippet" }
]
}
])
end
it 'returns "Create new" menu groups with headers', :use_clean_rails_memory_store_caching do
allow(group).to receive(:persisted?).and_return(true)
allow(helper).to receive(:can?).and_return(true)
expect(subject[:create_new_menu_groups]).to contain_exactly(
a_hash_including(
name: "In this group",
items: array_including(
{ href: "/projects/new", text: "New project/repository" },
{ href: "/groups/new#create-group-pane", text: "New subgroup" },
{ href: "/groups/#{group.full_path}/-/group_members", text: "Invite members" }
)
),
a_hash_including(
name: "In GitLab",
items: array_including(
{ href: "/projects/new", text: "New project/repository" },
{ href: "/groups/new", text: "New group" },
{ href: "/-/snippets/new", text: "New snippet" }
)
)
)
end
end
end

View File

@ -126,6 +126,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
it 'returns specific job data' do
expect(json_response['finished_at']).to be_nil
expect(json_response['erased_at']).to be_nil
end
it 'avoids N+1 queries', :skip_before_request do
@ -651,6 +652,18 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
end
end
context 'when job is erased' do
let(:job) do
create(:ci_build, pipeline: pipeline, erased_at: Time.now)
end
it 'returns specific job data' do
get api("/projects/#{project.id}/jobs/#{job.id}", api_user)
expect(Time.parse(json_response['erased_at'])).to be_like_time(job.erased_at)
end
end
context 'when trace artifact record exists with no stored file', :skip_before_request do
before do
create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project)

View File

@ -7,6 +7,7 @@ RSpec.shared_examples 'integration settings form' do
it 'displays all the integrations' do
aggregate_failures do
integrations.each do |integration|
stub_feature_flags(integration_slack_app_notifications: false)
navigate_to_integration(integration)
page.within('form.integration-settings-form') do

View File

@ -3,7 +3,7 @@
require 'spec_helper'
require 'rake'
RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_category: :database do
before :all do
Rake.application.rake_require 'active_record/railties/databases'
Rake.application.rake_require 'tasks/seed_fu'
@ -352,6 +352,94 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
end
end
describe 'dictionary generate' do
let(:db_config) { instance_double(ActiveRecord::DatabaseConfigurations::HashConfig, name: 'fake_db') }
let(:model) { ActiveRecord::Base }
let(:connection) { model.connection }
let(:base_models) { { 'fake_db' => model }.with_indifferent_access }
let(:tables) { %w[table1] }
let(:views) { %w[view1] }
let(:table_file_path) { 'db/docs/table1.yml' }
let(:view_file_path) { 'db/docs/views/view1.yml' }
before do
allow(Gitlab::Database).to receive(:db_config_for_connection).and_return(db_config)
allow(Gitlab::Database).to receive(:database_base_models).and_return(base_models)
allow(connection).to receive(:tables).and_return(tables)
allow(connection).to receive(:views).and_return(views)
end
after do
File.delete(table_file_path)
File.delete(view_file_path)
end
context 'when the dictionary files do not exist' do
it 'generate the dictionary files' do
run_rake_task('gitlab:db:dictionary:generate')
expect(File).to exist(File.join(table_file_path))
expect(File).to exist(File.join(view_file_path))
end
end
context 'when the dictionary files already exist' do
let(:table_class) do
Class.new(ApplicationRecord) do
self.table_name = 'table1'
end
end
let(:view_class) do
Class.new(ApplicationRecord) do
self.table_name = 'view1'
end
end
table_metadata = {
'table_name' => 'table1',
'classes' => [],
'feature_categories' => [],
'description' => nil,
'introduced_by_url' => nil,
'milestone' => 14.3
}
view_metadata = {
'view_name' => 'view1',
'classes' => [],
'feature_categories' => [],
'description' => nil,
'introduced_by_url' => nil,
'milestone' => 14.3
}
before do
stub_const('TableClass', table_class)
stub_const('ViewClass', view_class)
File.write(table_file_path, table_metadata.to_yaml)
File.write(view_file_path, view_metadata.to_yaml)
allow(model).to receive(:descendants).and_return([table_class, view_class])
end
it 'update the dictionary content' do
run_rake_task('gitlab:db:dictionary:generate')
table_metadata = YAML.safe_load(File.read(table_file_path))
expect(table_metadata['classes']).to match_array(['TableClass'])
view_metadata = YAML.safe_load(File.read(view_file_path))
expect(view_metadata['classes']).to match_array(['ViewClass'])
end
end
end
describe 'unattended' do
using RSpec::Parameterized::TableSyntax

View File

@ -1140,15 +1140,15 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.2.0"
"@gitlab/svgs@3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.16.0.tgz#ba7f566c160bd408f3fce63b92bbf9d66a0e6a4d"
integrity sha512-sL2D6yNxq4eT5go6d9k9QBbYyvlqWbxWUFB+Ty0/xxxx1ZaWlBs/PpGERgebPZ9uk5v4FIW1mI+Ra8ZVFBOjww==
"@gitlab/svgs@3.17.0":
version "3.17.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.17.0.tgz#beeda4bd2b97ec2637bebe1760dbe283d6a599ef"
integrity sha512-+5wsh/FG7SSkUQjehROl+0nRgrg/XRNUa9h3LkxpksP0AXy4j6xuYuq+7xucDGJDdXo43tUftLc9w7u/SCmgQA==
"@gitlab/ui@52.11.0":
version "52.11.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-52.11.0.tgz#a81f5e4901531fca3d2340cc17f1282d2370665c"
integrity sha512-6ocP2GhIpaq4+Zf05n1nBoTckyHEYGf9KRs0CNyOyNoCe28fSTRX91KTdC4hcvam9IdQZY049X+tcG6bRxooCg==
"@gitlab/ui@52.13.1":
version "52.13.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-52.13.1.tgz#178854d01de1637240e60d06572ff01fc927bc5a"
integrity sha512-TSS5ghAto0ZPGeVmucrrYgvfBBhesvvKm4wPz29MkQRv3yCUbTJ+emBgXLDECDvUZvZPfwomN6QqsyQzVezz5g==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"