Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2022-01-11 00:13:53 +00:00
parent fbb529e46c
commit d9c0b200e1
25 changed files with 1017 additions and 181 deletions

View File

@ -99,8 +99,7 @@ export default {
};
},
isLastDeployment() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return this.environment?.isLastDeployment || this.environment?.lastDeployment?.['last?'];
return this.environment?.isLastDeployment || this.environment?.lastDeployment?.isLast;
},
},
methods: {

View File

@ -1,8 +1,9 @@
<script>
import { GlDropdown, GlDropdownItem, GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { formatTime } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
import actionMutation from '../graphql/mutations/action.mutation.graphql';
export default {
directives: {
@ -12,7 +13,6 @@ export default {
GlDropdown,
GlDropdownItem,
GlIcon,
GlLoadingIcon,
},
props: {
actions: {
@ -20,6 +20,11 @@ export default {
required: false,
default: () => [],
},
graphql: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -49,7 +54,11 @@ export default {
this.isLoading = true;
eventHub.$emit('postAction', { endpoint: action.playPath });
if (this.graphql) {
this.$apollo.mutate({ mutation: actionMutation, variables: { action } });
} else {
eventHub.$emit('postAction', { endpoint: action.playPath });
}
},
isActionDisabled(action) {
@ -70,18 +79,16 @@ export default {
<template>
<gl-dropdown
v-gl-tooltip
:text="title"
:title="title"
:loading="isLoading"
:aria-label="title"
:disabled="isLoading"
icon="play"
text-sr-only
right
data-container="body"
data-testid="environment-actions-button"
>
<template #button-content>
<gl-icon name="play" />
<gl-icon name="chevron-down" />
<gl-loading-icon v-if="isLoading" size="sm" />
</template>
<gl-dropdown-item
v-for="(action, i) in actions"
:key="i"

View File

@ -2,9 +2,11 @@
import { GlButton, GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import folderQuery from '../graphql/queries/folder.query.graphql';
import EnvironmentItem from './new_environment_item.vue';
export default {
components: {
EnvironmentItem,
GlButton,
GlCollapse,
GlIcon,
@ -51,16 +53,25 @@ export default {
folderPath() {
return this.nestedEnvironment.latest.folderPath;
},
environments() {
return this.folder?.environments;
},
},
methods: {
toggleCollapse() {
this.visible = !this.visible;
},
isFirstEnvironment(index) {
return index === 0;
},
},
};
</script>
<template>
<div class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-px-3 gl-pt-3 gl-pb-5">
<div
:class="{ 'gl-pb-5': !visible }"
class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-px-3 gl-pt-3"
>
<div class="gl-w-full gl-display-flex gl-align-items-center">
<gl-button
class="gl-mr-4 gl-fill-current-color gl-text-gray-500"
@ -77,6 +88,15 @@ export default {
<gl-badge size="sm" class="gl-mr-auto">{{ count }}</gl-badge>
<gl-link v-if="visible" :href="folderPath">{{ $options.i18n.link }}</gl-link>
</div>
<gl-collapse :visible="visible" />
<gl-collapse :visible="visible">
<environment-item
v-for="(environment, index) in environments"
:key="environment.name"
:environment="environment"
:class="{ 'gl-mt-5': isFirstEnvironment(index) }"
class="gl-border-gray-100 gl-border-t-solid gl-border-1 gl-pl-7 gl-pt-3"
in-folder
/>
</gl-collapse>
</div>
</template>

View File

@ -0,0 +1,239 @@
<script>
import {
GlCollapse,
GlDropdown,
GlButton,
GlLink,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import { __ } from '~/locale';
import { truncate } from '~/lib/utils/text_utility';
import isLastDeployment from '../graphql/queries/is_last_deployment.query.graphql';
import ExternalUrl from './environment_external_url.vue';
import Actions from './environment_actions.vue';
import StopComponent from './environment_stop.vue';
import Rollback from './environment_rollback.vue';
import Pin from './environment_pin.vue';
import Monitoring from './environment_monitoring.vue';
import Terminal from './environment_terminal_button.vue';
import Delete from './environment_delete.vue';
export default {
components: {
GlCollapse,
GlDropdown,
GlButton,
GlLink,
Actions,
ExternalUrl,
StopComponent,
Rollback,
Monitoring,
Pin,
Terminal,
Delete,
},
directives: {
GlTooltip,
},
props: {
environment: {
required: true,
type: Object,
},
inFolder: {
required: false,
default: false,
type: Boolean,
},
},
apollo: {
isLastDeployment: {
query: isLastDeployment,
variables() {
return { environment: this.environment };
},
},
},
i18n: {
collapse: __('Collapse'),
expand: __('Expand'),
},
data() {
return { visible: false };
},
computed: {
icon() {
return this.visible ? 'angle-down' : 'angle-right';
},
externalUrl() {
return this.environment.externalUrl;
},
name() {
return this.inFolder ? this.environment.nameWithoutType : this.environment.name;
},
label() {
return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand;
},
actions() {
if (!this.environment?.lastDeployment) {
return [];
}
const { manualActions = [], scheduledActions = [] } = this.environment.lastDeployment;
const combinedActions = [...manualActions, ...scheduledActions];
return combinedActions.map((action) => ({
...action,
}));
},
canStop() {
return this.environment?.canStop;
},
retryPath() {
return this.environment?.lastDeployment?.deployable?.retryPath;
},
hasExtraActions() {
return Boolean(
this.retryPath ||
this.canShowAutoStopDate ||
this.metricsPath ||
this.terminalPath ||
this.canDeleteEnvironment,
);
},
canShowAutoStopDate() {
if (!this.environment?.autoStopAt) {
return false;
}
const autoStopDate = new Date(this.environment?.autoStopAt);
const now = new Date();
return now < autoStopDate;
},
autoStopPath() {
return this.environment?.cancelAutoStopPath ?? '';
},
metricsPath() {
return this.environment?.metricsPath ?? '';
},
terminalPath() {
return this.environment?.terminalPath ?? '';
},
canDeleteEnvironment() {
return Boolean(this.environment?.canDelete && this.environment?.deletePath);
},
displayName() {
return truncate(this.name, 80);
},
},
methods: {
toggleCollapse() {
this.visible = !this.visible;
},
},
};
</script>
<template>
<div>
<div
class="gl-px-3 gl-pt-3 gl-pb-5 gl-display-flex gl-justify-content-space-between gl-align-items-center"
>
<div class="gl-min-w-0 gl-mr-4 gl-display-flex gl-align-items-center">
<gl-button
class="gl-mr-4 gl-min-w-fit-content"
:icon="icon"
:aria-label="label"
size="small"
category="tertiary"
@click="toggleCollapse"
/>
<gl-link
v-gl-tooltip
:href="environment.environmentPath"
class="gl-text-blue-500 gl-text-truncate"
:class="{ 'gl-font-weight-bold': visible }"
:title="name"
>
{{ displayName }}
</gl-link>
</div>
<div>
<div class="btn-group table-action-buttons" role="group">
<external-url
v-if="externalUrl"
:external-url="externalUrl"
data-track-action="click_button"
data-track-label="environment_url"
/>
<actions
v-if="actions.length > 0"
:actions="actions"
data-track-action="click_dropdown"
data-track-label="environment_actions"
graphql
/>
<stop-component
v-if="canStop"
:environment="environment"
class="gl-z-index-2"
data-track-action="click_button"
data-track-label="environment_stop"
graphql
/>
<gl-dropdown
v-if="hasExtraActions"
icon="ellipsis_v"
text-sr-only
:text="__('More actions')"
category="secondary"
no-caret
right
>
<rollback
v-if="retryPath"
:environment="environment"
:is-last-deployment="isLastDeployment"
:retry-url="retryPath"
graphql
data-track-action="click_button"
data-track-label="environment_rollback"
/>
<pin
v-if="canShowAutoStopDate"
:auto-stop-url="autoStopPath"
data-track-action="click_button"
data-track-label="environment_pin"
/>
<monitoring
v-if="metricsPath"
:monitoring-url="metricsPath"
data-track-action="click_button"
data-track-label="environment_monitoring"
/>
<terminal
v-if="terminalPath"
:terminal-path="terminalPath"
data-track-action="click_button"
data-track-label="environment_terminal"
/>
<delete
v-if="canDeleteEnvironment"
:environment="environment"
data-track-action="click_button"
data-track-label="environment_delete"
graphql
/>
</gl-dropdown>
</div>
</div>
</div>
<gl-collapse :visible="visible" />
</div>
</template>

View File

@ -5,20 +5,28 @@ import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_util
import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
import environmentToDeleteQuery from '../graphql/queries/environment_to_delete.query.graphql';
import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql';
import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
import EnvironmentItem from './new_environment_item.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
import DeleteEnvironmentModal from './delete_environment_modal.vue';
export default {
components: {
DeleteEnvironmentModal,
ConfirmRollbackModal,
EnvironmentFolder,
EnableReviewAppModal,
EnvironmentItem,
StopEnvironmentModal,
GlBadge,
GlPagination,
GlTab,
GlTabs,
StopEnvironmentModal,
},
apollo: {
environmentApp: {
@ -39,6 +47,12 @@ export default {
pageInfo: {
query: pageInfoQuery,
},
environmentToDelete: {
query: environmentToDeleteQuery,
},
environmentToRollback: {
query: environmentToRollbackQuery,
},
environmentToStop: {
query: environmentToStopQuery,
},
@ -63,6 +77,8 @@ export default {
isReviewAppModalVisible: false,
page: parseInt(page, 10),
scope,
environmentToDelete: {},
environmentToRollback: {},
environmentToStop: {},
};
},
@ -71,7 +87,10 @@ export default {
return this.environmentApp?.reviewApp?.canSetupReviewApp;
},
folders() {
return this.environmentApp?.environments.filter((e) => e.size > 1) ?? [];
return this.environmentApp?.environments?.filter((e) => e.size > 1) ?? [];
},
environments() {
return this.environmentApp?.environments?.filter((e) => e.size === 1) ?? [];
},
availableCount() {
return this.environmentApp?.availableCount;
@ -164,7 +183,9 @@ export default {
:modal-id="$options.modalId"
data-testid="enable-review-app-modal"
/>
<delete-environment-modal :environment="environmentToDelete" graphql />
<stop-environment-modal :environment="environmentToStop" graphql />
<confirm-rollback-modal :environment="environmentToRollback" graphql />
<gl-tabs
:action-secondary="addEnvironment"
:action-primary="openReviewAppModal"
@ -195,6 +216,12 @@ export default {
class="gl-mb-3"
:nested-environment="folder"
/>
<environment-item
v-for="environment in environments"
:key="environment.name"
class="gl-mb-3 gl-border-gray-100 gl-border-1 gl-border-b-solid"
:environment="environment.latest"
/>
<gl-pagination
align="center"
:total-items="totalItems"

View File

@ -0,0 +1,5 @@
mutation action($action: LocalAction) {
action(action: $action) @client {
errors
}
}

View File

@ -0,0 +1,3 @@
query isLastDeployment($environment: LocalEnvironment) {
isLastDeployment(environment: $environment) @client
}

View File

@ -66,8 +66,7 @@ export const resolvers = (endpoint) => ({
}));
},
isLastDeployment(_, { environment }) {
// eslint-disable-next-line @gitlab/require-i18n-strings
return environment?.lastDeployment?.['last?'];
return environment?.lastDeployment?.isLast;
},
},
Mutation: {
@ -115,6 +114,14 @@ export const resolvers = (endpoint) => ({
data: { environmentToStop: environment },
});
},
action(_, { action: { playPath } }) {
return axios
.post(playPath)
.then(() => buildErrors())
.catch(() =>
buildErrors([s__('Environments|An error occurred while making the request.')]),
);
},
setEnvironmentToDelete(_, { environment }, { client }) {
client.writeQuery({
query: environmentToDeleteQuery,

View File

@ -70,7 +70,7 @@ extend type Query {
environmentToRollback: LocalEnvironment
environmentToStop: LocalEnvironment
isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean
isLastDeployment: Boolean
isLastDeployment(environment: LocalEnvironmentInput): Boolean
}
extend type Mutation {
@ -81,4 +81,5 @@ extend type Mutation {
setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToStop(environment: LocalEnvironmentInput): LocalErrors
action(environment: LocalEnvironmentInput): LocalErrors
}

View File

@ -39,7 +39,7 @@ module HooksHelper
def hook_log_path(hook, hook_log)
case hook
when ProjectHook
when ProjectHook, ServiceHook
hook_log.present.details_path
when SystemHook
admin_hook_hook_log_path(hook, hook_log)

View File

@ -14,68 +14,84 @@ GitLab can be used as a dependency proxy for a variety of common package manager
This is the administration documentation. If you want to learn how to use the
dependency proxies, see the [user guide](../../user/packages/dependency_proxy/index.md).
## Enabling the Dependency Proxy feature
The GitLab Dependency Proxy:
NOTE:
Dependency proxy requires the Puma web server to be enabled.
- Is turned on by default.
- Can be turned off by an administrator.
- Requires the [Puma web server](../operations/puma.md)
to be enabled. Puma is enabled by default in GitLab 13.0 and later.
To enable the dependency proxy feature:
## Turn off the Dependency Proxy
**Omnibus GitLab installations**
The Dependency Proxy is enabled by default. If you are an administrator, you
can turn off the Dependency Proxy. To turn off the Dependency Proxy, follow the instructions that
correspond to your GitLab installation:
- [Omnibus GitLab installations](#omnibus-gitlab-installations)
- [Helm chart installations](#helm-chart-installations)
- [Installations from source](#installations-from-source)
### Omnibus GitLab installations
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
```ruby
gitlab_rails['dependency_proxy_enabled'] = true
gitlab_rails['dependency_proxy_enabled'] = false
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
1. Enable the [Puma web server](../operations/puma.md).
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure)
for the changes to take effect.
**Helm chart installations**
### Helm chart installations
1. After the installation is complete, update the global `appConfig` to enable the feature:
After the installation is complete, update the global `appConfig` to turn off the Dependency Proxy:
```yaml
global:
appConfig:
dependencyProxy:
enabled: true
bucket: gitlab-dependency-proxy
connection: {}
secret:
key:
```
```yaml
global:
appConfig:
dependencyProxy:
enabled: false
bucket: gitlab-dependency-proxy
connection: {}
secret:
key:
```
For more information, see [Configure Charts using Globals](https://docs.gitlab.com/charts/charts/globals.html#configure-appconfig-settings).
**Installations from source**
### Installations from source
1. After the installation is complete, configure the `dependency_proxy`
section in `config/gitlab.yml`. Set to `true` to enable it:
1. After the installation is complete, configure the `dependency_proxy` section in
`config/gitlab.yml`. Set `enabled` to `false` to turn off the Dependency Proxy:
```yaml
dependency_proxy:
enabled: true
enabled: false
```
1. [Restart GitLab](../restart_gitlab.md#installations-from-source "How to restart GitLab") for the changes to take effect.
1. [Restart GitLab](../restart_gitlab.md#installations-from-source "How to restart GitLab")
for the changes to take effect.
Since Puma is already the default web server for installations from source as of GitLab 12.9,
no further changes are needed.
### Multi-node GitLab installations
**Multi-node GitLab installations**
Follow the steps for [Omnibus GitLab installations](#omnibus-gitlab-installations)
for each Web and Sidekiq node.
Follow the steps for **Omnibus GitLab installation** for each Web and Sidekiq nodes.
## Turn on the Dependency Proxy
The Dependency Proxy is turned on by default, but can be turned off by an
administrator. To turn on the Dependency Proxy, follow the instructions in
[Turn off the Dependency Proxy](#turn-off-the-dependency-proxy),
but set the `enabled` fields to `true`.
## Changing the storage path
By default, the dependency proxy files are stored locally, but you can change the default
By default, the Dependency Proxy files are stored locally, but you can change the default
local location or even use object storage.
### Changing the local storage path
The dependency proxy files for Omnibus GitLab installations are stored under
The Dependency Proxy files for Omnibus GitLab installations are stored under
`/var/opt/gitlab/gitlab-rails/shared/dependency_proxy/` and for source
installations under `shared/dependency_proxy/` (relative to the Git home directory).
To change the local storage path:
@ -105,7 +121,7 @@ To change the local storage path:
### Using object storage
Instead of relying on the local storage, you can use an object storage to
store the blobs of the dependency proxy.
store the blobs of the Dependency Proxy.
[Read more about using object storage with GitLab](../object_storage.md).
@ -199,5 +215,3 @@ Feature.disable(:dependency_proxy_for_private_groups)
# Re-enable the authentication
Feature.enable(:dependency_proxy_for_private_groups)
```
The ability to disable this feature will be [removed in 13.9](https://gitlab.com/gitlab-org/gitlab/-/issues/276777).

View File

@ -4,9 +4,14 @@ group: Monitor
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
---
# Tracing **(FREE)**
# Tracing (DEPRECATED) **(FREE)**
> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42645) from GitLab Ultimate to GitLab Free in 13.5.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/42645) from GitLab Ultimate to GitLab Free in 13.5.
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346540) in GitLab 14.7.
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346540)
for use in GitLab 14.7, and is planned for removal in GitLab 15.0.
Tracing provides insight into the performance and health of a deployed application, tracking each
function or microservice that handles a given request. Tracing makes it easy to understand the

View File

@ -5,10 +5,10 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: howto, reference
---
# GitLab and SSH keys **(FREE)**
# Use SSH keys to communicate with GitLab **(FREE)**
Git is a distributed version control system, which means you can work locally,
then share or "push" your changes to a server. In this case, the server is GitLab.
then share or *push* your changes to a server. In this case, the server you push to is GitLab.
GitLab uses the SSH protocol to securely communicate with Git.
When you use SSH keys to authenticate to the GitLab remote server,

View File

@ -3,96 +3,83 @@ 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/engineering/ux/technical-writing/#assignments
description: 'This article describes how to install Git on macOS, Ubuntu Linux and Windows.'
type: howto
---
# Installing Git **(FREE)**
To begin contributing to GitLab projects,
you must install the Git client on your computer.
This article shows you how to install Git on macOS, Ubuntu Linux and Windows.
Information on [installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
To begin contributing to GitLab projects, you must install the appropriate Git client
on your computer. Information about [installing Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
is also available at the official Git website.
## Install Git on macOS using the Homebrew package manager
## Supported operating systems
Although you can use the version of Git shipped with macOS or install the latest
version of Git on macOS by downloading it from the project website, we recommend
installing Git with Homebrew to get access to an extensive selection of
dependency-managed libraries and applications.
Git is available for the following operating systems:
If you don't need access to any additional development libraries or don't have
approximately 15 GB of available disk space for Xcode and Homebrew, use one of
the previously mentioned methods.
- [macOS](#macos)
- [Ubuntu Linux](#ubuntu-linux)
- [Microsoft Windows](#windows)
### Installing Xcode
### macOS
To build dependencies, Homebrew needs the XCode Command Line Tools. Install
it by running in your terminal:
A version of Git is supplied by macOS. You can use this version, or install the latest
version of Git on macOS by downloading it from the project website. We recommend
installing Git with [Homebrew](https://brew.sh/index.html). With Homebrew, you can
access an extensive selection of libraries and applications, with their dependencies
managed for you.
```shell
xcode-select --install
```
Prerequisites:
Click **Install** to download and install it. Alternatively, you can install
the entire [XCode](https://developer.apple.com/xcode/) package through the
macOS App Store.
- 15 GB of available disk space for Homebrew and Xcode.
- Extra disk space for any additional development libraries.
### Installing Homebrew
To install Git on macOS:
With Xcode installed, browse to the [Homebrew website](https://brew.sh/index.html)
for the official Homebrew installation instructions.
1. Open a terminal and install the XCode Command Line Tools:
### Installing Git via Homebrew
```shell
xcode-select --install
```
With Homebrew installed, you are now ready to install Git.
Open a terminal and enter the following command:
Alternatively, you can install the entire [XCode](https://developer.apple.com/xcode/)
package through the macOS App Store.
```shell
brew install git
```
1. Select **Install** to download and install XCode Command Line Tools.
1. Install Homebrew according to the [official Homebrew installation instructions](https://brew.sh/index.html).
1. Install Git by running `brew install git` from your terminal.
1. In a terminal, verify that Git works on your computer:
Congratulations! You should now have Git installed via Homebrew.
```shell
git --version
```
To verify that Git works on your system, run:
### Ubuntu Linux
```shell
git --version
```
On Ubuntu and other Linux operating systems, use the built-in package manager
to install Git:
Next, read our article on [adding an SSH key to GitLab](../../../ssh/index.md).
1. Open a terminal and run these commands to install the latest Git
from the officially
maintained package archives:
## Install Git on Ubuntu Linux
```shell
sudo apt-add-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
```
On Ubuntu and other Linux operating systems
it is recommended to use the built-in package manager to install Git.
1. To verify that Git works on your computer, run:
Open a terminal and enter the following commands
to install the latest Git from the official Git maintained package archives:
```shell
git --version
```
```shell
sudo apt-add-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
```
### Windows
Congratulations! You should now have Git installed via the Ubuntu package manager.
Go to the [Git website](https://git-scm.com/), and then download and install Git for Windows.
To verify that Git works on your system, run:
## After you install Git
```shell
git --version
```
Next, read our article on [adding an SSH key to GitLab](../../../ssh/index.md).
## Installing Git on Windows from the Git website
Open the [Git website](https://git-scm.com/) and download and install Git for Windows.
Next, read our article on [adding an SSH key to GitLab](../../../ssh/index.md).
After you successfully install Git on your computer, read about [adding an SSH key to GitLab](../../../ssh/index.md).
<!-- ## Troubleshooting

View File

@ -74,36 +74,30 @@ Otherwise, to manually go to the **Subscription** area:
1. Select the **Terms of Service** checkbox.
1. Select **Upload License**.
## Add your license at install time
## Add your license during installation
A license can be automatically imported at install time by placing a file named
`Gitlab.gitlab-license` in `/etc/gitlab/` for Omnibus GitLab, or `config/` for source installations.
You can import a license file when you install GitLab.
You can also specify a custom location and filename for the license:
- **For installations from source**
- Place the `Gitlab.gitlab-license` file in the `config/` directory.
- To specify a custom location and filename for the license, set the
`GITLAB_LICENSE_FILE` environment variable with the path to the file:
- Source installations should set the `GITLAB_LICENSE_FILE` environment
variable with the path to a valid GitLab Enterprise Edition license.
```shell
export GITLAB_LICENSE_FILE="/path/to/license/file"
```
```shell
export GITLAB_LICENSE_FILE="/path/to/license/file"
```
- **For Omnibus package**
- Place the `Gitlab.gitlab-license` file in the `/etc/gitlab/` directory.
- To specify a custom location and filename for the license, add this entry to `gitlab.rb`:
- Omnibus GitLab installations should add this entry to `gitlab.rb`:
```ruby
gitlab_rails['initial_license_file'] = "/path/to/license/file"
```
```ruby
gitlab_rails['initial_license_file'] = "/path/to/license/file"
```
WARNING:
These methods only add a license at the time of installation. Use the
**{admin}** **Admin Area** in the web user interface to renew or upgrade licenses.
---
After the license is uploaded, all GitLab Enterprise Edition functionality
is active until the end of the license period. When that period ends, the
instance will [fall back](#what-happens-when-your-license-expires) to Free-only
functionality.
These methods only add a license at the time of installation. To renew or upgrade
a license, upload the license in the **Admin Area** in the web user interface.
## What happens when your license expires
@ -150,39 +144,44 @@ The banner disappears after the new license becomes active.
## Troubleshooting
### There is no Subscription tab in the Admin Area
### No Subscription area in the Admin Area
If you originally installed Community Edition rather than Enterprise Edition you must
[upgrade to Enterprise Edition](../../update/index.md#community-to-enterprise-edition)
before uploading your license.
You cannot upload your license because there is no **Subscription** area.
This issue might occur if:
GitLab.com users can't upload and use a self-managed license. If you
want to use paid features on GitLab.com, you can
[purchase a separate subscription](../../subscriptions/gitlab_com/index.md).
- You're running GitLab Community Edition. Before you upload your license, you
must [upgrade to Enterprise Edition](../../update/index.md#community-to-enterprise-edition).
- You're using GitLab.com. You cannot upload a self-managed license to GitLab.com.
To use paid features on GitLab.com, [purchase a separate subscription](../../subscriptions/gitlab_com/index.md).
### Users exceed license limit upon renewal
If you've added new users to your GitLab instance prior to renewal, you may need to
purchase additional seats to cover those users. If this is the case, and a license
without enough users is uploaded, GitLab displays a message prompting you to purchase
additional users. More information on how to determine the required number of users
and how to add additional seats can be found in the
[licensing FAQ](https://about.gitlab.com/pricing/licensing-faq/).
GitLab displays a message prompting you to purchase
additional users. This issue occurs if you upload a license that does not have enough
users to cover the number of users in your instance.
In GitLab 14.2 and later, for instances that use a license file, you can exceed the number of purchased users and still activate your license.
To fix this issue, purchase additional seats to cover those users.
For more information, read the [licensing FAQ](https://about.gitlab.com/pricing/licensing-faq/).
- If the users over license are less than or equal to 10% of the users in the subscription,
the license is applied and the overage is paid in the next true-up.
- If the users over license are more than 10% of the users in the subscription,
In GitLab 14.2 and later, for instances that use a license file, the following
rules apply:
- If the users over license are less than or equal to 10% of the users in the license
file, the license is applied and you pay the overage in the next renewal.
- If the users over license are more than 10% of the users in the license file,
you cannot apply the license without purchasing more users.
For example, if you purchased a license for 100 users, you can have 110 users when you activate
your license. However, if you have 111, you must purchase more users before you can activate.
For example, if you purchase a license for 100 users, you can have 110 users when you activate
your license. However, if you have 111 users, you must purchase more users before you can activate
the license.
### There is a connectivity issue
### Cannot activate instance due to connectivity error
In GitLab 14.1 and later, to activate your subscription, your GitLab instance must be connected to the internet.
In GitLab 14.1 and later, to activate your subscription with an activation code,
your GitLab instance must be connected to the internet.
If you have an offline or airgapped environment, you can [upload a license file](license.md#activate-gitlab-ee-with-a-license-file) instead.
If you have an offline or airgapped environment,
[upload a license file](license.md#activate-gitlab-ee-with-a-license-file) instead.
If you have questions or need assistance activating your instance, please [contact GitLab Support](https://about.gitlab.com/support/#contact-support).
If you have questions or need assistance activating your instance,
[contact GitLab Support](https://about.gitlab.com/support/#contact-support).

View File

@ -357,6 +357,10 @@ Ensure your SAML identity provider sends an attribute statement named `Groups` o
</saml:AttributeStatement>
```
WARNING:
Setting up Group Sync can disconnect users from SAML IDP if there is any mismatch in the configuration. Ensure the
`Groups` attribute is included in the SAML response, and the **SAML Group Name** matches the `AttributeValue` attribute.
Other attribute names such as `http://schemas.microsoft.com/ws/2008/06/identity/claims/groups`
are not accepted as a source of groups.
See the [SAML troubleshooting page](../../../administration/troubleshooting/group_saml_scim.md)

View File

@ -19,7 +19,8 @@ upstream image from a registry, acting as a pull-through cache.
## Prerequisites
- The Dependency Proxy is enabled by default but can be [turned off by an administrator](../../../administration/packages/dependency_proxy.md).
To use the Dependency Proxy, it must be enabled for the GitLab instance. It's enabled by default,
but [administrators can turn it off](../../../administration/packages/dependency_proxy.md).
### Supported images and packages
@ -32,13 +33,17 @@ The following images and packages are supported.
For a list of planned additions, view the
[direction page](https://about.gitlab.com/direction/package/#dependency-proxy).
## Enable or disable the Dependency Proxy for a group
## Enable or turn off the Dependency Proxy for a group
To enable or disable the Dependency Proxy for a group:
To enable or turn off the Dependency Proxy for a group:
1. Go to your group's **Settings > Packages & Registries**.
1. Expand the **Dependency Proxy** section.
1. To enable the proxy, turn on **Enable Proxy**. To disable it, turn the toggle off.
1. To enable the proxy, turn on **Enable Proxy**. To turn it off, turn the toggle off.
This setting only affects the Dependency Proxy for a group. Only an administrator can
[turn the Dependency Proxy on or off](../../../administration/packages/dependency_proxy.md)
for the entire GitLab instance.
## View the Dependency Proxy

View File

@ -26,7 +26,7 @@ describe('Confirm Rollback Modal Component', () => {
commit: {
shortId: 'abc0123',
},
'last?': true,
isLast: true,
},
modalId: 'test',
};
@ -145,7 +145,7 @@ describe('Confirm Rollback Modal Component', () => {
...environment,
lastDeployment: {
...environment.lastDeployment,
'last?': false,
isLast: false,
},
},
hasMultipleCommits,
@ -167,7 +167,7 @@ describe('Confirm Rollback Modal Component', () => {
...environment,
lastDeployment: {
...environment.lastDeployment,
'last?': false,
isLast: false,
},
},
hasMultipleCommits,
@ -191,7 +191,7 @@ describe('Confirm Rollback Modal Component', () => {
...environment,
lastDeployment: {
...environment.lastDeployment,
'last?': true,
isLast: true,
},
},
hasMultipleCommits,

View File

@ -1,9 +1,13 @@
import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { TEST_HOST } from 'helpers/test_constants';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import EnvironmentActions from '~/environments/components/environment_actions.vue';
import eventHub from '~/environments/event_hub';
import actionMutation from '~/environments/graphql/mutations/action.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
const scheduledJobAction = {
name: 'scheduled action',
@ -25,12 +29,13 @@ describe('EnvironmentActions Component', () => {
const findEnvironmentActionsButton = () =>
wrapper.find('[data-testid="environment-actions-button"]');
function createComponent(props, { mountFn = shallowMount } = {}) {
function createComponent(props, { mountFn = shallowMount, options = {} } = {}) {
wrapper = mountFn(EnvironmentActions, {
propsData: { actions: [], ...props },
directives: {
GlTooltip: createMockDirective(),
},
...options,
});
}
@ -150,4 +155,32 @@ describe('EnvironmentActions Component', () => {
expect(findDropdownItem(expiredJobAction).text()).toContain('00:00:00');
});
});
describe('graphql', () => {
Vue.use(VueApollo);
const action = {
name: 'bar',
play_path: 'https://gitlab.com/play',
};
let mockApollo;
beforeEach(() => {
mockApollo = createMockApollo();
createComponent(
{ actions: [action], graphql: true },
{ options: { apolloProvider: mockApollo } },
);
});
it('should trigger a graphql mutation on click', () => {
jest.spyOn(mockApollo.defaultClient, 'mutate');
findDropdownItem(action).vm.$emit('click');
expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
mutation: actionMutation,
variables: { action },
});
});
});
});

View File

@ -477,7 +477,141 @@ export const resolvedEnvironment = {
externalUrl: 'https://example.org',
environmentType: 'review',
nameWithoutType: 'hello',
lastDeployment: null,
lastDeployment: {
id: 78,
iid: 24,
sha: 'f3ba6dd84f8f891373e9b869135622b954852db1',
ref: { name: 'main', refPath: '/h5bp/html5-boilerplate/-/tree/main' },
status: 'success',
createdAt: '2022-01-07T15:47:27.415Z',
deployedAt: '2022-01-07T15:47:32.450Z',
tag: false,
isLast: true,
user: {
id: 1,
username: 'root',
name: 'Administrator',
state: 'active',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
webUrl: 'http://gck.test:3000/root',
showStatus: false,
path: '/root',
},
deployable: {
id: 1014,
name: 'deploy-prod',
started: '2022-01-07T15:47:31.037Z',
complete: true,
archived: false,
buildPath: '/h5bp/html5-boilerplate/-/jobs/1014',
retryPath: '/h5bp/html5-boilerplate/-/jobs/1014/retry',
playable: false,
scheduled: false,
createdAt: '2022-01-07T15:47:27.404Z',
updatedAt: '2022-01-07T15:47:32.341Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
hasDetails: true,
detailsPath: '/h5bp/html5-boilerplate/-/jobs/1014',
illustration: {
image:
'/assets/illustrations/skipped-job_empty-29a8a37d8a61d1b6f68cf3484f9024e53cd6eb95e28eae3554f8011a1146bf27.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/h5bp/html5-boilerplate/-/jobs/1014/retry',
method: 'post',
buttonTitle: 'Retry this job',
},
},
},
commit: {
id: 'f3ba6dd84f8f891373e9b869135622b954852db1',
shortId: 'f3ba6dd8',
createdAt: '2022-01-07T15:47:26.000+00:00',
parentIds: ['3213b6ac17afab99be37d5d38f38c6c8407387cc'],
title: 'Update .gitlab-ci.yml file',
message: 'Update .gitlab-ci.yml file',
authorName: 'Administrator',
authorEmail: 'admin@example.com',
authoredDate: '2022-01-07T15:47:26.000+00:00',
committerName: 'Administrator',
committerEmail: 'admin@example.com',
committedDate: '2022-01-07T15:47:26.000+00:00',
trailers: {},
webUrl:
'http://gck.test:3000/h5bp/html5-boilerplate/-/commit/f3ba6dd84f8f891373e9b869135622b954852db1',
author: {
id: 1,
username: 'root',
name: 'Administrator',
state: 'active',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
webUrl: 'http://gck.test:3000/root',
showStatus: false,
path: '/root',
},
authorGravatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
commitUrl:
'http://gck.test:3000/h5bp/html5-boilerplate/-/commit/f3ba6dd84f8f891373e9b869135622b954852db1',
commitPath: '/h5bp/html5-boilerplate/-/commit/f3ba6dd84f8f891373e9b869135622b954852db1',
},
manualActions: [
{
id: 1015,
name: 'deploy-staging',
started: null,
complete: false,
archived: false,
buildPath: '/h5bp/html5-boilerplate/-/jobs/1015',
playPath: '/h5bp/html5-boilerplate/-/jobs/1015/play',
playable: true,
scheduled: false,
createdAt: '2022-01-07T15:47:27.422Z',
updatedAt: '2022-01-07T15:47:28.557Z',
status: {
icon: 'status_manual',
text: 'manual',
label: 'manual play action',
group: 'manual',
tooltip: 'manual action',
hasDetails: true,
detailsPath: '/h5bp/html5-boilerplate/-/jobs/1015',
illustration: {
image:
'/assets/illustrations/manual_action-c55aee2c5f9ebe9f72751480af8bb307be1a6f35552f344cc6d1bf979d3422f6.svg',
size: 'svg-394',
title: 'This job requires a manual action',
content:
'This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes.',
},
favicon:
'/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png',
action: {
icon: 'play',
title: 'Play',
path: '/h5bp/html5-boilerplate/-/jobs/1015/play',
method: 'post',
buttonTitle: 'Trigger this manual action',
},
},
},
],
scheduledActions: [],
cluster: null,
},
hasStopAction: false,
rolloutStatus: null,
environmentPath: '/h5bp/html5-boilerplate/-/environments/41',

View File

@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { resolvers } from '~/environments/graphql/resolvers';
import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql';
@ -226,4 +227,21 @@ describe('~/frontend/environments/graphql/resolvers', () => {
});
});
});
describe('action', () => {
it('should POST to the given path', async () => {
mock.onPost(ENDPOINT).reply(200);
const errors = await mockResolvers.Mutation.action(null, { action: { playPath: ENDPOINT } });
expect(errors).toEqual({ __typename: 'LocalEnvironmentErrors', errors: [] });
});
it('should return a nice error message on fail', async () => {
mock.onPost(ENDPOINT).reply(500);
const errors = await mockResolvers.Mutation.action(null, { action: { playPath: ENDPOINT } });
expect(errors).toEqual({
__typename: 'LocalEnvironmentErrors',
errors: [s__('Environments|An error occurred while making the request.')],
});
});
});
});

View File

@ -1,10 +1,13 @@
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import { GlCollapse, GlIcon } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubTransition } from 'helpers/stub_transition';
import { __, s__ } from '~/locale';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import EnvironmentItem from '~/environments/components/new_environment_item.vue';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
Vue.use(VueApollo);
@ -25,13 +28,20 @@ describe('~/environments/components/new_environments_folder.vue', () => {
};
const createWrapper = (propsData, apolloProvider) =>
mountExtended(EnvironmentsFolder, { apolloProvider, propsData });
mountExtended(EnvironmentsFolder, {
apolloProvider,
propsData,
stubs: { transition: stubTransition() },
});
beforeEach(() => {
beforeEach(async () => {
environmentFolderMock = jest.fn();
[nestedEnvironment] = resolvedEnvironmentsApp.environments;
environmentFolderMock.mockReturnValue(resolvedFolder);
wrapper = createWrapper({ nestedEnvironment }, createApolloProvider());
await nextTick();
await waitForPromises();
folderName = wrapper.findByText(nestedEnvironment.name);
button = wrapper.findByRole('button', { name: __('Expand') });
});
@ -57,7 +67,8 @@ describe('~/environments/components/new_environments_folder.vue', () => {
const link = findLink();
expect(collapse.attributes('visible')).toBeUndefined();
expect(icons.wrappers.map((i) => i.props('name'))).toEqual(['angle-right', 'folder-o']);
const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2);
expect(iconNames).toEqual(['angle-right', 'folder-o']);
expect(folderName.classes('gl-font-weight-bold')).toBe(false);
expect(link.exists()).toBe(false);
});
@ -68,10 +79,21 @@ describe('~/environments/components/new_environments_folder.vue', () => {
const link = findLink();
expect(button.attributes('aria-label')).toBe(__('Collapse'));
expect(collapse.attributes('visible')).toBe('true');
expect(icons.wrappers.map((i) => i.props('name'))).toEqual(['angle-down', 'folder-open']);
expect(collapse.attributes('visible')).toBe('visible');
const iconNames = icons.wrappers.map((i) => i.props('name')).slice(0, 2);
expect(iconNames).toEqual(['angle-down', 'folder-open']);
expect(folderName.classes('gl-font-weight-bold')).toBe(true);
expect(link.attributes('href')).toBe(nestedEnvironment.latest.folderPath);
});
it('displays all environments when opened', async () => {
await button.trigger('click');
const names = resolvedFolder.environments.map((e) =>
expect.stringMatching(e.nameWithoutType),
);
const environments = wrapper.findAllComponents(EnvironmentItem).wrappers.map((w) => w.text());
expect(environments).toEqual(expect.arrayContaining(names));
});
});
});

View File

@ -0,0 +1,284 @@
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { GlCollapse, GlIcon } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubTransition } from 'helpers/stub_transition';
import { __, s__ } from '~/locale';
import EnvironmentItem from '~/environments/components/new_environment_item.vue';
import { resolvedEnvironment } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/new_environment_item.vue', () => {
let wrapper;
const createApolloProvider = () => {
return createMockApollo();
};
const createWrapper = ({ propsData = {}, apolloProvider } = {}) =>
mountExtended(EnvironmentItem, {
apolloProvider,
propsData: { environment: resolvedEnvironment, ...propsData },
stubs: { transition: stubTransition() },
});
afterEach(() => {
wrapper?.destroy();
});
it('displays the name when not in a folder', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const name = wrapper.findByRole('link', { name: resolvedEnvironment.name });
expect(name.exists()).toBe(true);
});
it('displays the name minus the folder prefix when in a folder', () => {
wrapper = createWrapper({
propsData: { inFolder: true },
apolloProvider: createApolloProvider(),
});
const name = wrapper.findByRole('link', { name: resolvedEnvironment.nameWithoutType });
expect(name.exists()).toBe(true);
});
it('truncates the name if it is very long', () => {
const environment = {
...resolvedEnvironment,
name:
'this is a really long name that should be truncated because otherwise it would look strange in the UI',
};
wrapper = createWrapper({ propsData: { environment }, apolloProvider: createApolloProvider() });
const name = wrapper.findByRole('link', {
name: (text) => environment.name.startsWith(text.slice(0, -1)),
});
expect(name.exists()).toBe(true);
expect(name.text()).toHaveLength(80);
});
describe('url', () => {
it('shows a link for the url if one is present', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const url = wrapper.findByRole('link', { name: s__('Environments|Open live environment') });
expect(url.attributes('href')).toEqual(resolvedEnvironment.externalUrl);
});
it('does not show a link for the url if one is missing', () => {
wrapper = createWrapper({
propsData: { environment: { ...resolvedEnvironment, externalUrl: '' } },
apolloProvider: createApolloProvider(),
});
const url = wrapper.findByRole('link', { name: s__('Environments|Open live environment') });
expect(url.exists()).toBe(false);
});
});
describe('actions', () => {
it('shows a dropdown if there are actions to perform', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const actions = wrapper.findByRole('button', { name: __('Deploy to...') });
expect(actions.exists()).toBe(true);
});
it('does not show a dropdown if there are no actions to perform', () => {
wrapper = createWrapper({
propsData: {
environment: {
...resolvedEnvironment,
lastDeployment: null,
},
apolloProvider: createApolloProvider(),
},
});
const actions = wrapper.findByRole('button', { name: __('Deploy to...') });
expect(actions.exists()).toBe(false);
});
it('passes all the actions down to the action component', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const action = wrapper.findByRole('menuitem', { name: 'deploy-staging' });
expect(action.exists()).toBe(true);
});
});
describe('stop', () => {
it('shows a buton to stop the environment if the environment is available', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const stop = wrapper.findByRole('button', { name: s__('Environments|Stop environment') });
expect(stop.exists()).toBe(true);
});
it('does not show a buton to stop the environment if the environment is stopped', () => {
wrapper = createWrapper({
propsData: { environment: { ...resolvedEnvironment, canStop: false } },
apolloProvider: createApolloProvider(),
});
const stop = wrapper.findByRole('button', { name: s__('Environments|Stop environment') });
expect(stop.exists()).toBe(false);
});
});
describe('rollback', () => {
it('shows the option to rollback/re-deploy if available', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const rollback = wrapper.findByRole('menuitem', {
name: s__('Environments|Re-deploy to environment'),
});
expect(rollback.exists()).toBe(true);
});
it('does not show the option to rollback/re-deploy if not available', () => {
wrapper = createWrapper({
propsData: { environment: { ...resolvedEnvironment, lastDeployment: null } },
apolloProvider: createApolloProvider(),
});
const rollback = wrapper.findByRole('menuitem', {
name: s__('Environments|Re-deploy to environment'),
});
expect(rollback.exists()).toBe(false);
});
});
describe('pin', () => {
it('shows the option to pin the environment if there is an autostop date', () => {
wrapper = createWrapper({
propsData: {
environment: { ...resolvedEnvironment, autoStopAt: new Date(Date.now() + 100000) },
},
apolloProvider: createApolloProvider(),
});
const rollback = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
expect(rollback.exists()).toBe(true);
});
it('does not show the option to pin the environment if there is no autostop date', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const rollback = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
expect(rollback.exists()).toBe(false);
});
});
describe('monitoring', () => {
it('shows the link to monitoring if metrics are set up', () => {
wrapper = createWrapper({
propsData: { environment: { ...resolvedEnvironment, metricsPath: '/metrics' } },
apolloProvider: createApolloProvider(),
});
const rollback = wrapper.findByRole('menuitem', { name: __('Monitoring') });
expect(rollback.exists()).toBe(true);
});
it('does not show the link to monitoring if metrics are not set up', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const rollback = wrapper.findByRole('menuitem', { name: __('Monitoring') });
expect(rollback.exists()).toBe(false);
});
});
describe('terminal', () => {
it('shows the link to the terminal if set up', () => {
wrapper = createWrapper({
propsData: { environment: { ...resolvedEnvironment, terminalPath: '/terminal' } },
apolloProvider: createApolloProvider(),
});
const rollback = wrapper.findByRole('menuitem', { name: __('Terminal') });
expect(rollback.exists()).toBe(true);
});
it('does not show the link to the terminal if not set up', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const rollback = wrapper.findByRole('menuitem', { name: __('Terminal') });
expect(rollback.exists()).toBe(false);
});
});
describe('delete', () => {
it('shows the button to delete the environment if possible', () => {
wrapper = createWrapper({
propsData: {
environment: { ...resolvedEnvironment, canDelete: true, deletePath: '/terminal' },
},
apolloProvider: createApolloProvider(),
});
const rollback = wrapper.findByRole('menuitem', {
name: s__('Environments|Delete environment'),
});
expect(rollback.exists()).toBe(true);
});
it('does not show the button to delete the environment if not possible', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const rollback = wrapper.findByRole('menuitem', {
name: s__('Environments|Delete environment'),
});
expect(rollback.exists()).toBe(false);
});
});
describe('collapse', () => {
let icon;
let collapse;
let button;
let environmentName;
beforeEach(() => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
collapse = wrapper.findComponent(GlCollapse);
icon = wrapper.findComponent(GlIcon);
button = wrapper.findByRole('button', { name: __('Expand') });
environmentName = wrapper.findByText(resolvedEnvironment.name);
});
it('is collapsed by default', () => {
expect(collapse.attributes('visible')).toBeUndefined();
expect(icon.props('name')).toEqual('angle-right');
expect(environmentName.classes('gl-font-weight-bold')).toBe(false);
});
it('opens on click', async () => {
await button.trigger('click');
expect(button.attributes('aria-label')).toBe(__('Collapse'));
expect(collapse.attributes('visible')).toBe('visible');
expect(icon.props('name')).toEqual('angle-down');
expect(environmentName.classes('gl-font-weight-bold')).toBe(true);
});
});
});

View File

@ -8,6 +8,7 @@ import setWindowLocation from 'helpers/set_window_location_helper';
import { sprintf, __, s__ } from '~/locale';
import EnvironmentsApp from '~/environments/components/new_environments_app.vue';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import EnvironmentsItem from '~/environments/components/new_environment_item.vue';
import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data';
@ -93,6 +94,18 @@ describe('~/environments/components/new_environments_app.vue', () => {
expect(text).not.toContainEqual(expect.stringMatching('production'));
});
it('should show all the environments that are fetched', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
});
const text = wrapper.findAllComponents(EnvironmentsItem).wrappers.map((w) => w.text());
expect(text).not.toContainEqual(expect.stringMatching('review'));
expect(text).toContainEqual(expect.stringMatching('production'));
});
it('should show a button to create a new environment', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe HooksHelper do
let(:project) { create(:project) }
let(:project_hook) { create(:project_hook, project: project) }
let(:service_hook) { create(:service_hook, integration: create(:drone_ci_integration)) }
let(:system_hook) { create(:system_hook) }
describe '#link_to_test_hook' do
@ -31,6 +32,15 @@ RSpec.describe HooksHelper do
end
end
context 'with a service hook' do
let(:web_hook_log) { create(:web_hook_log, web_hook: service_hook) }
it 'returns project-namespaced link' do
expect(helper.hook_log_path(project_hook, web_hook_log))
.to eq(web_hook_log.present.details_path)
end
end
context 'with a system hook' do
let(:web_hook_log) { create(:web_hook_log, web_hook: system_hook) }