summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-11 00:13:53 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-11 00:13:53 +0000
commitd9c0b200e192c340f50fa165a66e4b6ff805f9fc (patch)
tree570199824b4576359360bf1fb4bc18e53e87415f
parentfbb529e46cfd031fae6f6f650d81bef331ef8fcd (diff)
downloadgitlab-ce-d9c0b200e192c340f50fa165a66e4b6ff805f9fc.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/environments/components/confirm_rollback_modal.vue3
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue25
-rw-r--r--app/assets/javascripts/environments/components/new_environment_folder.vue24
-rw-r--r--app/assets/javascripts/environments/components/new_environment_item.vue239
-rw-r--r--app/assets/javascripts/environments/components/new_environments_app.vue31
-rw-r--r--app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql5
-rw-r--r--app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/resolvers.js11
-rw-r--r--app/assets/javascripts/environments/graphql/typedefs.graphql3
-rw-r--r--app/helpers/hooks_helper.rb2
-rw-r--r--doc/administration/packages/dependency_proxy.md82
-rw-r--r--doc/operations/tracing.md9
-rw-r--r--doc/ssh/index.md4
-rw-r--r--doc/topics/git/how_to_install_git/index.md111
-rw-r--r--doc/user/admin_area/license.md91
-rw-r--r--doc/user/group/saml_sso/index.md4
-rw-r--r--doc/user/packages/dependency_proxy/index.md13
-rw-r--r--spec/frontend/environments/confirm_rollback_modal_spec.js8
-rw-r--r--spec/frontend/environments/environment_actions_spec.js35
-rw-r--r--spec/frontend/environments/graphql/mock_data.js136
-rw-r--r--spec/frontend/environments/graphql/resolvers_spec.js18
-rw-r--r--spec/frontend/environments/new_environment_folder_spec.js34
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js284
-rw-r--r--spec/frontend/environments/new_environments_app_spec.js13
-rw-r--r--spec/helpers/hooks_helper_spec.rb10
25 files changed, 1017 insertions, 181 deletions
diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
index 0e556f093e2..ce919f73858 100644
--- a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
+++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
@@ -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: {
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 2d98f00433a..98c95507168 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -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"
diff --git a/app/assets/javascripts/environments/components/new_environment_folder.vue b/app/assets/javascripts/environments/components/new_environment_folder.vue
index fe3d6f1e8ca..0d8651b1d09 100644
--- a/app/assets/javascripts/environments/components/new_environment_folder.vue
+++ b/app/assets/javascripts/environments/components/new_environment_folder.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue
new file mode 100644
index 00000000000..883e887c1ae
--- /dev/null
+++ b/app/assets/javascripts/environments/components/new_environment_item.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/new_environments_app.vue b/app/assets/javascripts/environments/components/new_environments_app.vue
index 02ccdb53456..c93caa4b4ab 100644
--- a/app/assets/javascripts/environments/components/new_environments_app.vue
+++ b/app/assets/javascripts/environments/components/new_environments_app.vue
@@ -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"
diff --git a/app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql
new file mode 100644
index 00000000000..bc2c9b33367
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql
@@ -0,0 +1,5 @@
+mutation action($action: LocalAction) {
+ action(action: $action) @client {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql b/app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql
new file mode 100644
index 00000000000..5eda2f18567
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql
@@ -0,0 +1,3 @@
+query isLastDeployment($environment: LocalEnvironment) {
+ isLastDeployment(environment: $environment) @client
+}
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index a2b3bda05fa..812fa0c81f0 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -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,
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index 64cab480c98..c02f6b2838a 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -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
}
diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb
index c1dfd2b2cda..1e50033e0e0 100644
--- a/app/helpers/hooks_helper.rb
+++ b/app/helpers/hooks_helper.rb
@@ -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)
diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md
index a394a32fc18..b3dc6ffc2b2 100644
--- a/doc/administration/packages/dependency_proxy.md
+++ b/doc/administration/packages/dependency_proxy.md
@@ -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.
+
+### Multi-node GitLab installations
-Since Puma is already the default web server for installations from source as of GitLab 12.9,
-no further changes are needed.
+Follow the steps for [Omnibus GitLab installations](#omnibus-gitlab-installations)
+for each Web and Sidekiq node.
-**Multi-node GitLab installations**
+## Turn on the Dependency Proxy
-Follow the steps for **Omnibus GitLab installation** for each Web and Sidekiq nodes.
+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).
diff --git a/doc/operations/tracing.md b/doc/operations/tracing.md
index 1aa6484157c..09a31c12bf4 100644
--- a/doc/operations/tracing.md
+++ b/doc/operations/tracing.md
@@ -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
diff --git a/doc/ssh/index.md b/doc/ssh/index.md
index fe3b2cacbff..6196ee5465b 100644
--- a/doc/ssh/index.md
+++ b/doc/ssh/index.md
@@ -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,
diff --git a/doc/topics/git/how_to_install_git/index.md b/doc/topics/git/how_to_install_git/index.md
index fc9c0e0ec63..422919ea46c 100644
--- a/doc/topics/git/how_to_install_git/index.md
+++ b/doc/topics/git/how_to_install_git/index.md
@@ -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
-
-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.
-
-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.
-
-### Installing Xcode
-
-To build dependencies, Homebrew needs the XCode Command Line Tools. Install
-it by running in your terminal:
+## Supported operating systems
-```shell
-xcode-select --install
-```
+Git is available for the following operating systems:
-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.
+- [macOS](#macos)
+- [Ubuntu Linux](#ubuntu-linux)
+- [Microsoft Windows](#windows)
-### Installing Homebrew
+### macOS
-With Xcode installed, browse to the [Homebrew website](https://brew.sh/index.html)
-for the official Homebrew installation instructions.
+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.
-### Installing Git via Homebrew
+Prerequisites:
-With Homebrew installed, you are now ready to install Git.
-Open a terminal and enter the following command:
+- 15 GB of available disk space for Homebrew and Xcode.
+- Extra disk space for any additional development libraries.
-```shell
-brew install git
-```
+To install Git on macOS:
-Congratulations! You should now have Git installed via Homebrew.
+1. Open a terminal and install the XCode Command Line Tools:
-To verify that Git works on your system, run:
+ ```shell
+ xcode-select --install
+ ```
-```shell
-git --version
-```
+ Alternatively, you can install the entire [XCode](https://developer.apple.com/xcode/)
+ package through the macOS App Store.
-Next, read our article on [adding an SSH key to GitLab](../../../ssh/index.md).
+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:
-## Install Git on Ubuntu Linux
+ ```shell
+ git --version
+ ```
-On Ubuntu and other Linux operating systems
-it is recommended to use the built-in package manager to install Git.
+### Ubuntu Linux
-Open a terminal and enter the following commands
-to install the latest Git from the official Git maintained package archives:
+On Ubuntu and other Linux operating systems, use the built-in package manager
+to install Git:
-```shell
-sudo apt-add-repository ppa:git-core/ppa
-sudo apt-get update
-sudo apt-get install git
-```
+1. Open a terminal and run these commands to install the latest Git
+from the officially
+ maintained package archives:
-Congratulations! You should now have Git installed via the Ubuntu package manager.
+ ```shell
+ sudo apt-add-repository ppa:git-core/ppa
+ sudo apt-get update
+ sudo apt-get install git
+ ```
-To verify that Git works on your system, run:
+1. To verify that Git works on your computer, run:
-```shell
-git --version
-```
+ ```shell
+ git --version
+ ```
-Next, read our article on [adding an SSH key to GitLab](../../../ssh/index.md).
+### Windows
-## Installing Git on Windows from the Git website
+Go to the [Git website](https://git-scm.com/), and then download and install Git for Windows.
-Open the [Git website](https://git-scm.com/) and download and install Git for Windows.
+## After you install Git
-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
diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
index 9b5e3775d78..e4a5ae8f03c 100644
--- a/doc/user/admin_area/license.md
+++ b/doc/user/admin_area/license.md
@@ -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.
+
+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/).
-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.
+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 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,
+- 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).
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 5913b486ee4..7029e601f71 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -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)
diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md
index 502901036aa..4e296f5e21c 100644
--- a/doc/user/packages/dependency_proxy/index.md
+++ b/doc/user/packages/dependency_proxy/index.md
@@ -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
diff --git a/spec/frontend/environments/confirm_rollback_modal_spec.js b/spec/frontend/environments/confirm_rollback_modal_spec.js
index b699f953945..b8dcb7c0d08 100644
--- a/spec/frontend/environments/confirm_rollback_modal_spec.js
+++ b/spec/frontend/environments/confirm_rollback_modal_spec.js
@@ -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,
diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js
index db78a6b0cdd..1b68a692db8 100644
--- a/spec/frontend/environments/environment_actions_spec.js
+++ b/spec/frontend/environments/environment_actions_spec.js
@@ -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 },
+ });
+ });
+ });
});
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index e75d3ac0321..fce30973547 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -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',
diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js
index 9238f92dc95..6b53dc24f0f 100644
--- a/spec/frontend/environments/graphql/resolvers_spec.js
+++ b/spec/frontend/environments/graphql/resolvers_spec.js
@@ -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.')],
+ });
+ });
+ });
});
diff --git a/spec/frontend/environments/new_environment_folder_spec.js b/spec/frontend/environments/new_environment_folder_spec.js
index 27d27d5869a..6823c88a5a1 100644
--- a/spec/frontend/environments/new_environment_folder_spec.js
+++ b/spec/frontend/environments/new_environment_folder_spec.js
@@ -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));
+ });
});
});
diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js
new file mode 100644
index 00000000000..dce7cae8587
--- /dev/null
+++ b/spec/frontend/environments/new_environment_item_spec.js
@@ -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);
+ });
+ });
+});
diff --git a/spec/frontend/environments/new_environments_app_spec.js b/spec/frontend/environments/new_environments_app_spec.js
index 368645b8046..dc9e9c23ba8 100644
--- a/spec/frontend/environments/new_environments_app_spec.js
+++ b/spec/frontend/environments/new_environments_app_spec.js
@@ -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,
diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb
index 3b23d705790..bac73db5dd4 100644
--- a/spec/helpers/hooks_helper_spec.rb
+++ b/spec/helpers/hooks_helper_spec.rb
@@ -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) }