Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-03 21:10:02 +00:00
parent 524639c706
commit 8e2f50b44d
19 changed files with 423 additions and 258 deletions

View File

@ -2,12 +2,12 @@
import FrequentItemsApp from '~/frequent_items/components/app.vue';
import eventHub from '~/frequent_items/event_hub';
import VuexModuleProvider from '~/vue_shared/components/vuex_module_provider.vue';
import TopNavMenuItem from './top_nav_menu_item.vue';
import TopNavMenuSections from './top_nav_menu_sections.vue';
export default {
components: {
FrequentItemsApp,
TopNavMenuItem,
TopNavMenuSections,
VuexModuleProvider,
},
inheritAttrs: false,
@ -32,11 +32,11 @@ export default {
},
},
computed: {
linkGroups() {
menuSections() {
return [
{ key: 'primary', links: this.linksPrimary },
{ key: 'secondary', links: this.linksSecondary },
].filter((x) => x.links?.length);
{ id: 'primary', menuItems: this.linksPrimary },
{ id: 'secondary', menuItems: this.linksSecondary },
].filter((x) => x.menuItems?.length);
},
},
mounted() {
@ -57,19 +57,6 @@ export default {
</vuex-module-provider>
</div>
</div>
<div
v-for="({ key, links }, groupIndex) in linkGroups"
:key="key"
:class="{ 'gl-mt-3': groupIndex !== 0 }"
class="gl-mt-auto gl-pt-3 gl-border-1 gl-border-t-solid gl-border-gray-100"
data-testid="menu-item-group"
>
<top-nav-menu-item
v-for="(link, linkIndex) in links"
:key="link.title"
:menu-item="link"
:class="{ 'gl-mt-1': linkIndex !== 0 }"
/>
</div>
<top-nav-menu-sections class="gl-mt-auto" :sections="menuSections" with-top-border />
</div>
</template>

View File

@ -1,17 +1,15 @@
<script>
import { cloneDeep } from 'lodash';
import { FREQUENT_ITEMS_PROJECTS, FREQUENT_ITEMS_GROUPS } from '~/frequent_items/constants';
import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
import TopNavContainerView from './top_nav_container_view.vue';
import TopNavMenuItem from './top_nav_menu_item.vue';
const ACTIVE_CLASS = 'gl-shadow-none! gl-font-weight-bold! active';
const SECONDARY_GROUP_CLASS = 'gl-pt-3 gl-mt-3 gl-border-1 gl-border-t-solid gl-border-gray-100';
import TopNavMenuSections from './top_nav_menu_sections.vue';
export default {
components: {
KeepAliveSlots,
TopNavContainerView,
TopNavMenuItem,
TopNavMenuSections,
},
props: {
primary: {
@ -31,29 +29,25 @@ export default {
},
},
data() {
// It's expected that primary & secondary never change, so these are treated as "init" props.
// We need to clone so that we can mutate the data without mutating the props
const menuSections = [
{ id: 'primary', menuItems: cloneDeep(this.primary) },
{ id: 'secondary', menuItems: cloneDeep(this.secondary) },
].filter((x) => x.menuItems?.length);
return {
activeId: '',
menuSections,
};
},
computed: {
menuItemGroups() {
return [
{ key: 'primary', items: this.primary, classes: '' },
{
key: 'secondary',
items: this.secondary,
classes: SECONDARY_GROUP_CLASS,
},
].filter((x) => x.items?.length);
},
allMenuItems() {
return this.menuItemGroups.flatMap((x) => x.items);
},
activeMenuItem() {
return this.allMenuItems.find((x) => x.id === this.activeId);
return this.menuSections.flatMap((x) => x.menuItems);
},
activeView() {
return this.activeMenuItem?.view;
const active = this.allMenuItems.find((x) => x.active);
return active?.view;
},
menuClass() {
if (!this.activeView) {
@ -63,61 +57,26 @@ export default {
return '';
},
},
created() {
// Initialize activeId based on initialization prop
this.activeId = this.allMenuItems.find((x) => x.active)?.id;
},
methods: {
onClick({ id, href }) {
// If we're a link, let's just do the default behavior so the view won't change
if (href) {
return;
}
this.activeId = id;
},
menuItemClasses(menuItem) {
if (menuItem.id === this.activeId) {
return ACTIVE_CLASS;
}
return '';
onMenuItemClick({ id }) {
this.allMenuItems.forEach((menuItem) => {
this.$set(menuItem, 'active', id === menuItem.id);
});
},
},
FREQUENT_ITEMS_PROJECTS,
FREQUENT_ITEMS_GROUPS,
// expose for unit tests
ACTIVE_CLASS,
SECONDARY_GROUP_CLASS,
};
</script>
<template>
<div class="gl-display-flex gl-align-items-stretch">
<div
class="gl-w-grid-size-30 gl-flex-shrink-0 gl-bg-gray-10"
class="gl-w-grid-size-30 gl-flex-shrink-0 gl-bg-gray-10 gl-py-3 gl-px-5"
:class="menuClass"
data-testid="menu-sidebar"
>
<div
class="gl-py-3 gl-px-5 gl-h-full gl-display-flex gl-align-items-stretch gl-flex-direction-column"
>
<div
v-for="group in menuItemGroups"
:key="group.key"
:class="group.classes"
data-testid="menu-item-group"
>
<top-nav-menu-item
v-for="(menu, index) in group.items"
:key="menu.id"
data-testid="menu-item"
:class="[{ 'gl-mt-1': index !== 0 }, menuItemClasses(menu)]"
:menu-item="menu"
@click="onClick(menu)"
/>
</div>
</div>
<top-nav-menu-sections :sections="menuSections" @menu-item-click="onMenuItemClick" />
</div>
<keep-alive-slots
v-show="activeView"

View File

@ -4,6 +4,8 @@ import { kebabCase, mapKeys } from 'lodash';
const getDataKey = (key) => `data-${kebabCase(key)}`;
const ACTIVE_CLASS = 'gl-shadow-none! gl-font-weight-bold! active';
export default {
components: {
GlButton,
@ -20,6 +22,7 @@ export default {
return mapKeys(this.menuItem.data || {}, (value, key) => getDataKey(key));
},
},
ACTIVE_CLASS,
};
</script>
@ -28,7 +31,7 @@ export default {
category="tertiary"
:href="menuItem.href"
class="top-nav-menu-item gl-display-block"
:class="menuItem.css_class"
:class="[menuItem.css_class, { [$options.ACTIVE_CLASS]: menuItem.active }]"
v-bind="dataAttrs"
v-on="$listeners"
>

View File

@ -0,0 +1,62 @@
<script>
import TopNavMenuItem from './top_nav_menu_item.vue';
const BORDER_CLASSES = 'gl-pt-3 gl-border-1 gl-border-t-solid gl-border-gray-100';
export default {
components: {
TopNavMenuItem,
},
props: {
sections: {
type: Array,
required: true,
},
withTopBorder: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
onClick(menuItem) {
// If we're a link, let's just do the default behavior so the view won't change
if (menuItem.href) {
return;
}
this.$emit('menu-item-click', menuItem);
},
getMenuSectionClasses(index) {
// This is a method instead of a computed so we don't have to incur the cost of
// creating a whole new array/object.
return {
[BORDER_CLASSES]: this.withTopBorder || index > 0,
'gl-mt-3': index > 0,
};
},
},
// Expose for unit tests
BORDER_CLASSES,
};
</script>
<template>
<div class="gl-display-flex gl-align-items-stretch gl-flex-direction-column">
<div
v-for="({ id, menuItems }, sectionIndex) in sections"
:key="id"
:class="getMenuSectionClasses(sectionIndex)"
data-testid="menu-section"
>
<top-nav-menu-item
v-for="(menuItem, menuItemIndex) in menuItems"
:key="menuItem.id"
:menu-item="menuItem"
data-testid="menu-item"
:class="{ 'gl-mt-1': menuItemIndex > 0 }"
@click="onClick(menuItem)"
/>
</div>
</div>
</template>

View File

@ -546,6 +546,12 @@ body {
color: #dbdbdb;
vertical-align: baseline;
}
img.emoji {
height: 20px;
vertical-align: top;
width: 20px;
margin-top: 1px;
}
.chart {
overflow: hidden;
height: 220px;

View File

@ -530,6 +530,12 @@ body {
color: #525252;
vertical-align: baseline;
}
img.emoji {
height: 20px;
vertical-align: top;
width: 20px;
margin-top: 1px;
}
.chart {
overflow: hidden;
height: 220px;

View File

@ -19,3 +19,9 @@ swap:
info: information
repo: repository
utilize: use
owner access: the Owner role
owner permission: the Owner role
owner permissions: the Owner role
maintainer access: the Maintainer role
maintainer permission: the Maintainer role
maintainer permissions: the Maintainer role

View File

@ -9,7 +9,7 @@ type: howto
NOTE:
If your GitLab installation uses external (not managed by Omnibus) PostgreSQL
instances, the Omnibus roles will not be able to perform all necessary
instances, the Omnibus roles are unable to perform all necessary
configuration steps. In this case,
[follow the Geo with external PostgreSQL instances document instead](external_database.md).
@ -27,8 +27,8 @@ in your testing/production environment.
## PostgreSQL replication
The GitLab **primary** node where the write operations happen will connect to
the **primary** database server, and **secondary** nodes will
The GitLab **primary** node where the write operations happen connects to
the **primary** database server, and **secondary** nodes
connect to their own database servers (which are also read-only).
We recommend using [PostgreSQL replication slots](https://medium.com/@tk512/replication-slots-in-postgresql-b4b03d277c75)
@ -75,13 +75,13 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
gitlab-ctl set-geo-primary-node
```
This command will use your defined `external_url` in `/etc/gitlab/gitlab.rb`.
This command uses your defined `external_url` in `/etc/gitlab/gitlab.rb`.
1. GitLab 10.4 and up only: Do the following to make sure the `gitlab` database user has a password defined:
NOTE:
Until FDW settings are removed in GitLab version 14.0, avoid using single or double quotes in the
password for PostgreSQL as that will lead to errors when reconfiguring.
password for PostgreSQL as that leads to errors when reconfiguring.
Generate a MD5 hash of the desired password:
@ -106,13 +106,13 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
1. Omnibus GitLab already has a [replication user](https://wiki.postgresql.org/wiki/Streaming_Replication)
called `gitlab_replicator`. You must set the password for this user manually.
You will be prompted to enter a password:
You are prompted to enter a password:
```shell
gitlab-ctl set-replication-password
```
This command will also read the `postgresql['sql_replication_user']` Omnibus
This command also reads the `postgresql['sql_replication_user']` Omnibus
setting in case you have changed `gitlab_replicator` username to something
else.
@ -154,7 +154,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
echo "External address: $(curl --silent "ipinfo.io/ip")"
```
In most cases, the following addresses will be used to configure GitLab
In most cases, the following addresses are used to configure GitLab
Geo:
| Configuration | Address |
@ -172,7 +172,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
for more details.
NOTE:
If you need to use `0.0.0.0` or `*` as the listen_address, you will also need to add
If you need to use `0.0.0.0` or `*` as the listen_address, you also need to add
`127.0.0.1/32` to the `postgresql['md5_auth_cidr_addresses']` setting, to allow Rails to connect through
`127.0.0.1`. For more information, see [omnibus-5258](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5258).
@ -262,7 +262,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
`5432` to the **primary** server's private address.
1. A certificate was automatically generated when GitLab was reconfigured. This
will be used automatically to protect your PostgreSQL traffic from
is used automatically to protect your PostgreSQL traffic from
eavesdroppers, but to protect against active ("man-in-the-middle") attackers,
the **secondary** node needs a copy of the certificate. Make a copy of the PostgreSQL
`server.crt` file on the **primary** node by running this command:
@ -272,7 +272,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
```
Copy the output into a clipboard or into a local file. You
will need it when setting up the **secondary** node! The certificate is not sensitive
need it when setting up the **secondary** node! The certificate is not sensitive
data.
### Step 2. Configure the **secondary** server
@ -325,7 +325,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
-T server.crt ~gitlab-psql/.postgresql/root.crt
```
PostgreSQL will now only recognize that exact certificate when verifying TLS
PostgreSQL now only recognizes that exact certificate when verifying TLS
connections. The certificate can only be replicated by someone with access
to the private key, which is **only** present on the **primary** node.
@ -423,7 +423,7 @@ data before running `pg_basebackup`.
WARNING:
Each Geo **secondary** node must have its own unique replication slot name.
Using the same slot name between two secondaries will break PostgreSQL replication.
Using the same slot name between two secondaries breaks PostgreSQL replication.
```shell
gitlab-ctl replicate-geo-database \
@ -441,7 +441,7 @@ data before running `pg_basebackup`.
to list them all, but here are a couple of tips:
- If PostgreSQL is listening on a non-standard port, add `--port=` as well.
- If your database is too large to be transferred in 30 minutes, you will need
- If your database is too large to be transferred in 30 minutes, you need
to increase the timeout, e.g., `--backup-timeout=3600` if you expect the
initial replication to take under an hour.
- Pass `--sslmode=disable` to skip PostgreSQL TLS authentication altogether
@ -452,9 +452,9 @@ data before running `pg_basebackup`.
the instructions above are carefully written to ensure protection against
both passive eavesdroppers and active "man-in-the-middle" attackers.
- Change the `--slot-name` to the name of the replication slot
to be used on the **primary** database. The script will attempt to create the
to be used on the **primary** database. The script attempts to create the
replication slot automatically if it does not exist.
- If you're repurposing an old server into a Geo **secondary** node, you'll need to
- If you're repurposing an old server into a Geo **secondary** node, you need to
add `--force` to the command line.
- When not in a production machine you can disable backup step if you
really sure this is what you want by adding `--skip-backup`
@ -484,7 +484,7 @@ subject to change without notice.
This experimental implementation has the following limitations:
- Whenever `gitlab-ctl reconfigure` runs on a Patroni Leader instance, there's a
chance the node will be demoted due to the required short-time restart. To
chance of the node be demoted due to the required short-time restart. To
avoid this, you can pause auto-failover by running `gitlab-ctl patroni pause`.
After a reconfigure, it resumes on its own.
@ -530,7 +530,7 @@ Leader instance**:
# You need one entry for each secondary, with a unique name following PostgreSQL slot_name constraints:
#
# Configuration syntax will be: 'unique_slotname' => { 'type' => 'physical' },
# Configuration syntax is: 'unique_slotname' => { 'type' => 'physical' },
# We don't support setting a permanent replication slot for logical replication type
patroni['replication_slots'] = {
'geo_secondary' => { 'type' => 'physical' }
@ -559,14 +559,14 @@ Leader instance**:
#### Step 2. Configure the internal load balancer on the primary site
To avoid reconfiguring the Standby Leader on the secondary site whenever a new
Leader is elected on the primary site, we'll need to set up a TCP internal load
balancer which will give a single endpoint for connecting to the Patroni
Leader is elected on the primary site, we need to set up a TCP internal load
balancer which gives a single endpoint for connecting to the Patroni
cluster's Leader.
The Omnibus GitLab packages do not include a Load Balancer. Here's how you
could do it with [HAProxy](https://www.haproxy.org/).
The following IPs and names will be used as an example:
The following IPs and names are used as an example:
- `10.6.0.21`: Patroni 1 (`patroni1.internal`)
- `10.6.0.21`: Patroni 2 (`patroni2.internal`)
@ -662,7 +662,7 @@ Follow the minimal configuration for the PgBouncer node:
NOTE:
If you are converting a secondary site to a Patroni Cluster, you must start
on the PostgreSQL instance. It will become the Patroni Standby Leader instance,
on the PostgreSQL instance. It becomes the Patroni Standby Leader instance,
and then you can switchover to another replica if you need.
For each Patroni instance on the secondary site:
@ -734,7 +734,7 @@ For each Patroni instance on the secondary site:
1. Before migrating, it is recommended that there is no replication lag between the primary and secondary sites and that replication is paused. In GitLab 13.2 and later, you can pause and resume replication with `gitlab-ctl geo-replication-pause` and `gitlab-ctl geo-replication-resume` on a Geo secondary database node.
1. Follow the [instructions to migrate repmgr to Patroni](../../postgresql/replication_and_failover.md#switching-from-repmgr-to-patroni). When configuring Patroni on each primary site database node, add `patroni['replication_slots'] = { '<slot_name>' => 'physical' }`
to `gitlab.rb` where `<slot_name>` is the name of the replication slot for your Geo secondary. This will ensure that Patroni recognizes the replication slot as permanent and will not drop it upon restarting.
to `gitlab.rb` where `<slot_name>` is the name of the replication slot for your Geo secondary. This ensures that Patroni recognizes the replication slot as permanent and not drop it upon restarting.
1. If database replication to the secondary was paused before migration, resume replication once Patroni is confirmed working on the primary.
### Migrating a single PostgreSQL node to Patroni
@ -750,7 +750,7 @@ With Patroni it's now possible to support that. In order to migrate the existing
1. [Configure a Standby Cluster](#step-4-configure-a-standby-cluster-on-the-secondary-site)
on that single node machine.
You will end up with a "Standby Cluster" with a single node. That allows you to later on add additional Patroni nodes
You end up with a "Standby Cluster" with a single node. That allows you to later on add additional Patroni nodes
by following the same instructions above.
### Configuring Patroni cluster for the tracking PostgreSQL database
@ -934,8 +934,8 @@ Patroni implementation on Omnibus that do not allow us to manage two different
clusters on the same machine, we recommend setting up a new Patroni cluster for
the tracking database by following the same instructions above.
The secondary nodes will backfill the new tracking database, and no data
synchronization will be required.
The secondary nodes backfill the new tracking database, and no data
synchronization is required.
## Troubleshooting

View File

@ -15,6 +15,7 @@ use the `#docs-processes` channel.
In addition to this page, the following resources can help you craft and contribute to documentation:
- [Doc contribution guidelines](../index.md)
- [A-Z word list](word_list.md)
- [Doc style and consistency testing](../testing.md)
- [UI text guidelines](https://design.gitlab.com/content/error-messages/)
- [GitLab Handbook style guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines)
@ -371,7 +372,7 @@ Capitalize names of:
- Methods or methodologies. For example, Continuous Integration,
Continuous Deployment, Scrum, and Agile.
Follow the capitalization style listed at the [authoritative source](#links-to-external-documentation)
Follow the capitalization style listed at the authoritative source
for the entity, which may use non-standard case styles. For example: GitLab and
npm.
@ -499,41 +500,6 @@ You can use these fake tokens as examples:
| Health check token | `Tu7BgjR9qeZTEyRzGG2P` |
| Request profile token | `7VgpS4Ax5utVD2esNstz` |
### Usage list
<!-- vale off -->
| Usage | Guidance |
|-----------------------|----------|
| above | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **previously** instead. |
| admin, admin area | Use **administration**, **administrator**, **administer**, or **Admin Area** instead. ([Vale](../testing.md#vale) rule: [`Admin.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/Admin.yml)) |
| allow, enable | Try to avoid, unless you are talking about security-related features. For example, instead of "This feature allows you to create a pipeline," use "Use this feature to create a pipeline." This phrasing is more active and is from the user perspective, rather than the person who implemented the feature. [View details](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows). |
| and/or | Use **or** instead, or another sensible construction. |
| below | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **following** instead. |
| currently | Do not use when talking about the product or its features. The documentation describes the product as it is today. ([Vale](../testing.md#vale) rule: [`CurrentStatus.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/CurrentStatus.yml)) |
| easily | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| e.g. | Do not use Latin abbreviations. Use **for example**, **such as**, **for instance**, or **like** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
| future tense | When possible, use present tense instead. For example, use `after you execute this command, GitLab displays the result` instead of `after you execute this command, GitLab will display the result`. ([Vale](../testing.md#vale) rule: [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml)) |
| handy | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| high availability, HA | Do not use. Instead, direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) for information about configuring GitLab for handling greater amounts of users. |
| I | Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale](../testing.md#vale) rule: [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml)) |
| i.e. | Do not use Latin abbreviations. Use **that is** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
| jargon | Do not use. Define the term or [link to a definition](#links-to-external-documentation). |
| may, might | **Might** means something has the probability of occurring. **May** gives permission to do something. Consider **can** instead of **may**. |
| me, myself, mine | Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale](../testing.md#vale) rule: [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml)) |
| permissions | Do not use roles and permissions interchangeably. Each user is assigned a role. Each role includes a set of permissions. |
| please | Do not use. For details, see the [Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please). |
| profanity | Do not use. Doing so may negatively affect other users and contributors, which is contrary to the GitLab value of [Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion). |
| roles | Do not use roles and permissions interchangeably. Each user is assigned a role. Each role includes a set of permissions. |
| scalability | Do not use when talking about increasing GitLab performance for additional users. The words scale or scaling are sometimes acceptable, but references to increasing GitLab performance for additional users should direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) page. |
| simply | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| slashes | Instead of **and/or**, use **or** or another sensible construction. This rule also applies to other slashes, like **follow/unfollow**. Some exceptions (like **CI/CD**) are allowed. |
| subgroup | Use instead of `sub-group`. |
| that | Do not use. Example: `the file that you save` can be `the file you save`. |
| useful | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| utilize | Do not use. Use **use** instead. It's more succinct and easier for non-native English speakers to understand. |
| via | Do not use Latin abbreviations. Use **with**, **through**, or **by using** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
<!-- vale on -->
### Contractions
Contractions are encouraged, and can create a friendly and informal tone,
@ -1592,7 +1558,7 @@ GitLab users are [assigned roles](../../../user/permissions.md) that confer spec
- _Maintainer_ is an example of a role.
- _Create new issue_ is an example of a permission.
[Do not use](#usage-list) these terms interchangeably.
[Do not use](word_list.md) these terms interchangeably.
## GitLab versions

View File

@ -0,0 +1,45 @@
---
stage: none
group: Style Guide
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
description: 'Writing styles, markup, formatting, and other standards for GitLab Documentation.'
---
# A-Z word list
To help ensure consistency in the documentation, follow this guidance.
<!-- vale off -->
| Usage | Guidance |
|-----------------------|----------|
| above | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **previously** instead. |
| admin, admin area | Use **administration**, **administrator**, **administer**, or **Admin Area** instead. ([Vale](../testing.md#vale) rule: [`Admin.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/Admin.yml)) |
| allow, enable | Try to avoid, unless you are talking about security-related features. For example, instead of "This feature allows you to create a pipeline," use "Use this feature to create a pipeline." This phrasing is more active and is from the user perspective, rather than the person who implemented the feature. [View details](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows). |
| and/or | Use **or** instead, or another sensible construction. |
| below | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **following** instead. |
| currently | Do not use when talking about the product or its features. The documentation describes the product as it is today. ([Vale](../testing.md#vale) rule: [`CurrentStatus.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/CurrentStatus.yml)) |
| easily | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| e.g. | Do not use Latin abbreviations. Use **for example**, **such as**, **for instance**, or **like** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
| future tense | When possible, use present tense instead. For example, use `after you execute this command, GitLab displays the result` instead of `after you execute this command, GitLab will display the result`. ([Vale](../testing.md#vale) rule: [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml)) |
| handy | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| high availability, HA | Do not use. Instead, direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) for information about configuring GitLab for handling greater amounts of users. |
| I | Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale](../testing.md#vale) rule: [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml)) |
| i.e. | Do not use Latin abbreviations. Use **that is** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
| jargon | Do not use. Define the term or link to a definition. |
| may, might | **Might** means something has the probability of occurring. **May** gives permission to do something. Consider **can** instead of **may**. |
| me, myself, mine | Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale](../testing.md#vale) rule: [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml)) |
| permissions | Do not use roles and permissions interchangeably. Each user is assigned a role. Each role includes a set of permissions. |
| please | Do not use. For details, see the [Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please). |
| profanity | Do not use. Doing so may negatively affect other users and contributors, which is contrary to the GitLab value of [Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion). |
| roles | Do not use roles and permissions interchangeably. Each user is assigned a role. Each role includes a set of permissions. |
| scalability | Do not use when talking about increasing GitLab performance for additional users. The words scale or scaling are sometimes acceptable, but references to increasing GitLab performance for additional users should direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) page. |
| simply | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| slashes | Instead of **and/or**, use **or** or another sensible construction. This rule also applies to other slashes, like **follow/unfollow**. Some exceptions (like **CI/CD**) are allowed. |
| subgroup | Use instead of `sub-group`. |
| that | Do not use. Example: `the file that you save` can be `the file you save`. |
| useful | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
| utilize | Do not use. Use **use** instead. It's more succinct and easier for non-native English speakers to understand. |
| via | Do not use Latin abbreviations. Use **with**, **through**, or **by using** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
<!-- vale on -->

View File

@ -204,7 +204,9 @@ un-threaded and ordered chronologically, newest to oldest:
You can enable the Service Level Agreement Countdown timer on incidents to track
the Service Level Agreements (SLAs) you hold with your customers. The timer is
automatically started when the incident is created, and shows the time
remaining before the SLA period expires. To configure the timer:
remaining before the SLA period expires. The timer is also dynamically updated
every 15 minutes so you do not have to refresh the page to see the time remaining.
To configure the timer:
1. Navigate to **Settings > Operations**.
1. Scroll to **Incidents** and click **Expand**, then select the

View File

@ -173,9 +173,10 @@ You can [configure](#customizing-the-container-scanning-settings) analyzers by u
| `ADDITIONAL_CA_CERT_BUNDLE` | `""` | Bundle of CA certs that you want to trust. See [Using a custom SSL CA certificate authority](#using-a-custom-ssl-ca-certificate-authority) for more details. | All |
| `CI_APPLICATION_REPOSITORY` | `$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG` | Docker repository URL for the image to be scanned. | All |
| `CI_APPLICATION_TAG` | `$CI_COMMIT_SHA` | Docker repository tag for the image to be scanned. | All |
| `CS_ANALYZER_IMAGE` | `$SECURE_ANALYZERS_PREFIX/$CS_PROJECT:$CS_MAJOR_VERSION` | Docker image of the analyzer. | All |
| `CS_DOCKER_INSECURE` | `"false"` | Allow access to secure Docker registries using HTTPS without validating the certificates. | All |
| `CS_REGISTRY_INSECURE` | `"false"` | Allow access to insecure registries (HTTP only). Should only be set to `true` when testing the image locally. | All |
| `CS_ANALYZER_IMAGE` | `$SECURE_ANALYZERS_PREFIX/$CS_PROJECT:$CS_MAJOR_VERSION` | Docker image of the analyzer. | All |
| `CS_DOCKER_INSECURE` | `"false"` | Allow access to secure Docker registries using HTTPS without validating the certificates. | All |
| `CS_REGISTRY_INSECURE` | `"false"` | Allow access to insecure registries (HTTP only). Should only be set to `true` when testing the image locally. | All |
| `CS_SEVERITY_THRESHOLD` | `UNKNOWN` | Severity level threshold. The scanner outputs vulnerabilities with severity level higher than or equal to this threshold. Supported levels are Unknown, Low, Medium, High, and Critical. | Trivy |
| `DOCKER_IMAGE` | `$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG` | The Docker image to be scanned. If set, this variable overrides the `$CI_APPLICATION_REPOSITORY` and `$CI_APPLICATION_TAG` variables. | All |
| `DOCKER_PASSWORD` | `$CI_REGISTRY_PASSWORD` | Password for accessing a Docker registry requiring authentication. | All |
| `DOCKER_USER` | `$CI_REGISTRY_USER` | Username for accessing a Docker registry requiring authentication. | All |

View File

@ -210,11 +210,9 @@ request contains a denied license. For more details, see [Enabling license appro
Prerequisites:
- At least one [security scanner job](#security-scanning-tools) must be enabled.
- Maintainer or Owner [permissions](../permissions.md#project-members-permissions).
- Maintainer or Owner [role](../permissions.md#project-members-permissions).
For this approval group, you must set the number of approvals required to greater than zero. You
must have Maintainer or Owner [permissions](../permissions.md#project-members-permissions)
to manage approval rules.
For this approval group, you must set the number of approvals required to greater than zero.
Follow these steps to enable `Vulnerability-Check`:

View File

@ -40,14 +40,16 @@ authenticate with GitLab by using the `CI_JOB_TOKEN`.
CI/CD templates, which you can use to get started, are in [this repository](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
Learn more about using CI/CD to build:
Learn more about using the GitLab Package Registry with CI/CD:
- [Composer packages](../composer_repository/index.md#publish-a-composer-package-by-using-cicd)
- [Conan packages](../conan_repository/index.md#publish-a-conan-package-by-using-cicd)
- [Generic packages](../generic_packages/index.md#publish-a-generic-package-by-using-cicd)
- [Maven packages](../maven_repository/index.md#create-maven-packages-with-gitlab-cicd)
- [npm packages](../npm_registry/index.md#publish-an-npm-package-by-using-cicd)
- [NuGet packages](../nuget_repository/index.md#publish-a-nuget-package-by-using-cicd)
- [Composer](../composer_repository/index.md#publish-a-composer-package-by-using-cicd)
- [Conan](../conan_repository/index.md#publish-a-conan-package-by-using-cicd)
- [Generic](../generic_packages/index.md#publish-a-generic-package-by-using-cicd)
- [Maven](../maven_repository/index.md#create-maven-packages-with-gitlab-cicd)
- [npm](../npm_registry/index.md#publish-an-npm-package-by-using-cicd)
- [NuGet](../nuget_repository/index.md#publish-a-nuget-package-by-using-cicd)
- [PyPI](../pypi_repository/#authenticate-with-a-ci-job-token)
- [RubyGems](../rubygems_registry/#authenticate-with-a-ci-job-token)
If you use CI/CD to build a package, extended activity information is displayed
when you view the package details:
@ -81,6 +83,22 @@ To delete a package in the UI, from your group or project:
The package is permanently deleted.
## Delete files associated with a package
To delete package files, you must have suitable [permissions](../../permissions.md).
You can delete packages by using [the API](../../../api/packages.md#delete-a-package-file) or the UI.
To delete package files in the UI, from your group or project:
1. Go to **Packages & Registries > Package Registry**.
1. Find the name of the package you want to delete.
1. Select the package to view additional details.
1. Find the name of the file you would like to delete.
1. Expand the ellipsis and select **Delete file**.
The package files are permanently deleted.
## Disable the Package Registry
The Package Registry is automatically enabled.

View File

@ -2,7 +2,8 @@
module QA
RSpec.describe 'Create' do
describe 'Merge request creation from fork', :smoke do
describe 'Merge request creation from fork' do
# TODO: Please add this back to :smoke suite as soon as https://gitlab.com/gitlab-org/gitlab/-/issues/332588 is addressed
it 'can merge feature branch fork to mainline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1701' do
merge_request = Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request|
merge_request.fork_branch = 'feature-branch'

View File

@ -4,7 +4,7 @@ import FrequentItemsApp from '~/frequent_items/components/app.vue';
import { FREQUENT_ITEMS_PROJECTS } from '~/frequent_items/constants';
import eventHub from '~/frequent_items/event_hub';
import TopNavContainerView from '~/nav/components/top_nav_container_view.vue';
import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue';
import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue';
import VuexModuleProvider from '~/vue_shared/components/vuex_module_provider.vue';
import { TEST_NAV_DATA } from '../mock_data';
@ -34,11 +34,7 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
});
};
const findMenuItems = (parent = wrapper) => parent.findAll(TopNavMenuItem);
const findMenuItemsModel = (parent = wrapper) =>
findMenuItems(parent).wrappers.map((x) => x.props());
const findMenuItemGroups = () => wrapper.findAll('[data-testid="menu-item-group"]');
const findMenuItemGroupsModel = () => findMenuItemGroups().wrappers.map(findMenuItemsModel);
const findMenuSections = () => wrapper.findComponent(TopNavMenuSections);
const findFrequentItemsApp = () => {
const parent = wrapper.findComponent(VuexModuleProvider);
@ -89,23 +85,16 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
});
});
it('renders menu item groups', () => {
expect(findMenuItemGroupsModel()).toEqual([
TEST_NAV_DATA.primary.map((menuItem) => ({ menuItem })),
TEST_NAV_DATA.secondary.map((menuItem) => ({ menuItem })),
]);
});
it('renders menu sections', () => {
const sections = [
{ id: 'primary', menuItems: TEST_NAV_DATA.primary },
{ id: 'secondary', menuItems: TEST_NAV_DATA.secondary },
];
it('only the first group does not have margin top', () => {
expect(findMenuItemGroups().wrappers.map((x) => x.classes('gl-mt-3'))).toEqual([false, true]);
});
it('only the first menu item does not have margin top', () => {
const actual = findMenuItems(findMenuItemGroups().at(1)).wrappers.map((x) =>
x.classes('gl-mt-1'),
);
expect(actual).toEqual([false, ...TEST_NAV_DATA.secondary.slice(1).fill(true)]);
expect(findMenuSections().props()).toEqual({
sections,
withTopBorder: true,
});
});
});
@ -117,8 +106,8 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
});
it('renders one menu item group', () => {
expect(findMenuItemGroupsModel()).toEqual([
TEST_NAV_DATA.primary.map((menuItem) => ({ menuItem })),
expect(findMenuSections().props('sections')).toEqual([
{ id: 'primary', menuItems: TEST_NAV_DATA.primary },
]);
});
});

View File

@ -1,67 +1,62 @@
import { shallowMount } from '@vue/test-utils';
import { shallowMount, mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import TopNavDropdownMenu from '~/nav/components/top_nav_dropdown_menu.vue';
import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue';
import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue';
import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
import { TEST_NAV_DATA } from '../mock_data';
const SECONDARY_GROUP_CLASSES = TopNavDropdownMenu.SECONDARY_GROUP_CLASS.split(' ');
describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(TopNavDropdownMenu, {
const createComponent = (props = {}, mountFn = shallowMount) => {
wrapper = mountFn(TopNavDropdownMenu, {
propsData: {
primary: TEST_NAV_DATA.primary,
secondary: TEST_NAV_DATA.secondary,
views: TEST_NAV_DATA.views,
...props,
},
stubs: {
// Stub the keep-alive-slots so we don't render frequent items which uses a store
KeepAliveSlots: true,
},
});
};
const findMenuItems = (parent = wrapper) => parent.findAll('[data-testid="menu-item"]');
const findMenuItemsModel = (parent = wrapper) =>
findMenuItems(parent).wrappers.map((x) => ({
menuItem: x.props('menuItem'),
isActive: x.classes('active'),
}));
const findMenuItemGroups = () => wrapper.findAll('[data-testid="menu-item-group"]');
const findMenuItemGroupsModel = () =>
findMenuItemGroups().wrappers.map((x) => ({
classes: x.classes(),
items: findMenuItemsModel(x),
}));
const findMenuItems = () => wrapper.findAllComponents(TopNavMenuItem);
const findMenuSections = () => wrapper.find(TopNavMenuSections);
const findMenuSidebar = () => wrapper.find('[data-testid="menu-sidebar"]');
const findMenuSubview = () => wrapper.findComponent(KeepAliveSlots);
const hasFullWidthMenuSidebar = () => findMenuSidebar().classes('gl-w-full');
const createItemsGroupModelExpectation = ({
primary = TEST_NAV_DATA.primary,
secondary = TEST_NAV_DATA.secondary,
activeIndex = -1,
} = {}) => [
{
classes: [],
items: primary.map((menuItem, index) => ({ isActive: index === activeIndex, menuItem })),
},
{
classes: SECONDARY_GROUP_CLASSES,
items: secondary.map((menuItem) => ({ isActive: false, menuItem })),
},
];
const withActiveIndex = (menuItems, activeIndex) =>
menuItems.map((x, idx) => ({
...x,
active: idx === activeIndex,
}));
afterEach(() => {
wrapper.destroy();
});
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation();
});
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('renders menu item groups', () => {
expect(findMenuItemGroupsModel()).toEqual(createItemsGroupModelExpectation());
it('renders menu sections', () => {
expect(findMenuSections().props()).toEqual({
sections: [
{ id: 'primary', menuItems: TEST_NAV_DATA.primary },
{ id: 'secondary', menuItems: TEST_NAV_DATA.secondary },
],
withTopBorder: false,
});
});
it('has full width menu sidebar', () => {
@ -74,36 +69,25 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
expect(subview.isVisible()).toBe(false);
expect(subview.props()).toEqual({ slotKey: '' });
});
it('the first menu item in a group does not render margin top', () => {
const actual = findMenuItems(findMenuItemGroups().at(0)).wrappers.map((x) =>
x.classes('gl-mt-1'),
);
expect(actual).toEqual([false, ...TEST_NAV_DATA.primary.slice(1).fill(true)]);
});
});
describe('with pre-initialized active view', () => {
const primaryWithActive = [
TEST_NAV_DATA.primary[0],
{
...TEST_NAV_DATA.primary[1],
active: true,
},
...TEST_NAV_DATA.primary.slice(2),
];
beforeEach(() => {
createComponent({
primary: primaryWithActive,
});
// We opt for a small integration test, to make sure the event is handled correctly
// as it would in prod.
createComponent(
{
primary: withActiveIndex(TEST_NAV_DATA.primary, 1),
},
mount,
);
});
it('renders menu item groups', () => {
expect(findMenuItemGroupsModel()).toEqual(
createItemsGroupModelExpectation({ primary: primaryWithActive, activeIndex: 1 }),
);
it('renders menu sections', () => {
expect(findMenuSections().props('sections')).toStrictEqual([
{ id: 'primary', menuItems: withActiveIndex(TEST_NAV_DATA.primary, 1) },
{ id: 'secondary', menuItems: TEST_NAV_DATA.secondary },
]);
});
it('does not have full width menu sidebar', () => {
@ -114,11 +98,11 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
const subview = findMenuSubview();
expect(subview.isVisible()).toBe(true);
expect(subview.props('slotKey')).toBe(primaryWithActive[1].view);
expect(subview.props('slotKey')).toBe(TEST_NAV_DATA.primary[1].view);
});
it('does not change view if non-view menu item is clicked', async () => {
const secondaryLink = findMenuItems().at(primaryWithActive.length);
const secondaryLink = findMenuItems().at(TEST_NAV_DATA.primary.length);
// Ensure this doesn't have a view
expect(secondaryLink.props('menuItem').view).toBeUndefined();
@ -127,10 +111,10 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
await nextTick();
expect(findMenuSubview().props('slotKey')).toBe(primaryWithActive[1].view);
expect(findMenuSubview().props('slotKey')).toBe(TEST_NAV_DATA.primary[1].view);
});
describe('when other view menu item is clicked', () => {
describe('when menu item is clicked', () => {
let primaryLink;
beforeEach(async () => {
@ -144,13 +128,20 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
});
it('changes active view', () => {
expect(findMenuSubview().props('slotKey')).toBe(primaryWithActive[0].view);
expect(findMenuSubview().props('slotKey')).toBe(TEST_NAV_DATA.primary[0].view);
});
it('changes active status on menu item', () => {
expect(findMenuItemGroupsModel()).toStrictEqual(
createItemsGroupModelExpectation({ primary: primaryWithActive, activeIndex: 0 }),
);
expect(findMenuSections().props('sections')).toStrictEqual([
{
id: 'primary',
menuItems: withActiveIndex(TEST_NAV_DATA.primary, 0),
},
{
id: 'secondary',
menuItems: withActiveIndex(TEST_NAV_DATA.secondary, -1),
},
]);
});
});
});

View File

@ -7,7 +7,6 @@ const TEST_MENU_ITEM = {
icon: 'search',
href: '/pretty/good/burger',
view: 'burger-view',
css_class: 'test-super-crazy test-class',
data: { qa_selector: 'not-a-real-selector', method: 'post', testFoo: 'test' },
};
@ -49,12 +48,6 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
expect(button.text()).toBe(TEST_MENU_ITEM.title);
});
it('renders button classes', () => {
const button = findButton();
expect(button.classes()).toEqual(expect.arrayContaining(TEST_MENU_ITEM.css_class.split(' ')));
});
it('renders button data attributes', () => {
const button = findButton();
@ -89,4 +82,29 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
expect(findButtonIcons()).toEqual(expectedIcons);
});
});
describe.each`
desc | active | cssClass | expectedClasses
${'default'} | ${false} | ${''} | ${[]}
${'with css class'} | ${false} | ${'test-css-class testing-123'} | ${['test-css-class', 'testing-123']}
${'with css class & active'} | ${true} | ${'test-css-class'} | ${['test-css-class', ...TopNavMenuItem.ACTIVE_CLASS.split(' ')]}
`('$desc', ({ active, cssClass, expectedClasses }) => {
beforeEach(() => {
createComponent({
menuItem: {
...TEST_MENU_ITEM,
active,
css_class: cssClass,
},
});
});
it('renders expected classes', () => {
expect(wrapper.classes()).toStrictEqual([
'top-nav-menu-item',
'gl-display-block',
...expectedClasses,
]);
});
});
});

View File

@ -0,0 +1,107 @@
import { shallowMount } from '@vue/test-utils';
import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue';
const TEST_SECTIONS = [
{
id: 'primary',
menuItems: [{ id: 'test', href: '/test/href' }, { id: 'foo' }, { id: 'bar' }],
},
{
id: 'secondary',
menuItems: [{ id: 'lorem' }, { id: 'ipsum' }],
},
];
describe('~/nav/components/top_nav_menu_sections.vue', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(TopNavMenuSections, {
propsData: {
sections: TEST_SECTIONS,
...props,
},
});
};
const findMenuItemModels = (parent) =>
parent.findAll('[data-testid="menu-item"]').wrappers.map((x) => ({
menuItem: x.props('menuItem'),
classes: x.classes(),
}));
const findSectionModels = () =>
wrapper.findAll('[data-testid="menu-section"]').wrappers.map((x) => ({
classes: x.classes(),
menuItems: findMenuItemModels(x),
}));
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('renders sections with menu items', () => {
expect(findSectionModels()).toEqual([
{
classes: [],
menuItems: [
{
menuItem: TEST_SECTIONS[0].menuItems[0],
classes: [],
},
...TEST_SECTIONS[0].menuItems.slice(1).map((menuItem) => ({
menuItem,
classes: ['gl-mt-1'],
})),
],
},
{
classes: [...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-mt-3'],
menuItems: [
{
menuItem: TEST_SECTIONS[1].menuItems[0],
classes: [],
},
...TEST_SECTIONS[1].menuItems.slice(1).map((menuItem) => ({
menuItem,
classes: ['gl-mt-1'],
})),
],
},
]);
});
it('when clicked menu item with href, does nothing', () => {
const menuItem = wrapper.findAll('[data-testid="menu-item"]').at(0);
menuItem.vm.$emit('click');
expect(wrapper.emitted()).toEqual({});
});
it('when clicked menu item without href, emits "menu-item-click"', () => {
const menuItem = wrapper.findAll('[data-testid="menu-item"]').at(1);
menuItem.vm.$emit('click');
expect(wrapper.emitted('menu-item-click')).toEqual([[TEST_SECTIONS[0].menuItems[1]]]);
});
});
describe('with withTopBorder=true', () => {
beforeEach(() => {
createComponent({ withTopBorder: true });
});
it('renders border classes for top section', () => {
expect(findSectionModels().map((x) => x.classes)).toEqual([
[...TopNavMenuSections.BORDER_CLASSES.split(' ')],
[...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-mt-3'],
]);
});
});
});