Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
557b3c487b
commit
333d6f857e
|
|
@ -1,15 +1,14 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlDropdownDivider, GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { GlDropdownDivider, GlDropdownItem, GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { convertEnvironmentScope } from '../utils';
|
||||
|
||||
export default {
|
||||
name: 'CiEnvironmentsDropdown',
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownDivider,
|
||||
GlSearchBoxByType,
|
||||
GlDropdownItem,
|
||||
GlCollapsibleListbox,
|
||||
},
|
||||
props: {
|
||||
environments: {
|
||||
|
|
@ -24,6 +23,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
selectedEnvironment: '',
|
||||
searchTerm: '',
|
||||
};
|
||||
},
|
||||
|
|
@ -33,9 +33,15 @@ export default {
|
|||
},
|
||||
filteredEnvironments() {
|
||||
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
|
||||
return this.environments.filter((environment) => {
|
||||
return environment.toLowerCase().includes(lowerCasedSearchTerm);
|
||||
});
|
||||
|
||||
return this.environments
|
||||
.filter((environment) => {
|
||||
return environment.toLowerCase().includes(lowerCasedSearchTerm);
|
||||
})
|
||||
.map((environment) => ({
|
||||
value: environment,
|
||||
text: environment,
|
||||
}));
|
||||
},
|
||||
shouldRenderCreateButton() {
|
||||
return this.searchTerm && !this.environments.includes(this.searchTerm);
|
||||
|
|
@ -47,44 +53,29 @@ export default {
|
|||
methods: {
|
||||
selectEnvironment(selected) {
|
||||
this.$emit('select-environment', selected);
|
||||
this.clearSearch();
|
||||
},
|
||||
convertEnvironmentScopeValue(scope) {
|
||||
return convertEnvironmentScope(scope);
|
||||
this.selectedEnvironment = selected;
|
||||
},
|
||||
createEnvironmentScope() {
|
||||
this.$emit('create-environment-scope', this.searchTerm);
|
||||
this.selectEnvironment(this.searchTerm);
|
||||
},
|
||||
isSelected(env) {
|
||||
return this.selectedEnvironmentScope === env;
|
||||
},
|
||||
clearSearch() {
|
||||
this.searchTerm = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-dropdown :text="environmentScopeLabel" @show="clearSearch">
|
||||
<gl-search-box-by-type v-model.trim="searchTerm" data-testid="ci-environment-search" />
|
||||
<gl-dropdown-item
|
||||
v-for="environment in filteredEnvironments"
|
||||
:key="environment"
|
||||
:is-checked="isSelected(environment)"
|
||||
is-check-item
|
||||
@click="selectEnvironment(environment)"
|
||||
>
|
||||
{{ convertEnvironmentScopeValue(environment) }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item v-if="!filteredEnvironments.length" ref="noMatchingResults">{{
|
||||
__('No matching results')
|
||||
}}</gl-dropdown-item>
|
||||
<template v-if="shouldRenderCreateButton">
|
||||
<gl-collapsible-listbox
|
||||
v-model="selectedEnvironment"
|
||||
searchable
|
||||
:items="filteredEnvironments"
|
||||
:toggle-text="environmentScopeLabel"
|
||||
@search="searchTerm = $event.trim()"
|
||||
@select="selectEnvironment"
|
||||
>
|
||||
<template v-if="shouldRenderCreateButton" #footer>
|
||||
<gl-dropdown-divider />
|
||||
<gl-dropdown-item data-testid="create-wildcard-button" @click="createEnvironmentScope">
|
||||
{{ composedCreateButtonLabel }}
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</gl-collapsible-listbox>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -352,7 +352,6 @@ export default {
|
|||
</template>
|
||||
<ci-environments-dropdown
|
||||
v-if="areScopedVariablesAvailable"
|
||||
class="gl-w-full"
|
||||
:selected-environment-scope="variable.environmentScope"
|
||||
:environments="joinedEnvironments"
|
||||
@select-environment="setEnvironmentScope"
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ export default {
|
|||
</div>
|
||||
</gl-form-group>
|
||||
<div>
|
||||
<gl-button variant="success" @click="createDeployToken">
|
||||
<gl-button variant="confirm" @click="createDeployToken">
|
||||
{{ $options.translations.addTokenButton }}
|
||||
</gl-button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { darkModeEnabled } from '~/lib/utils/color_utils';
|
||||
import { setUrlParams } from '~/lib/utils/url_utility';
|
||||
|
||||
import { MESSAGE_EVENT_TYPE, OBSERVABILITY_ROUTES, SKELETON_VARIANT } from '../constants';
|
||||
import { MESSAGE_EVENT_TYPE, SKELETON_VARIANTS_BY_ROUTE } from '../constants';
|
||||
import ObservabilitySkeleton from './skeleton/index.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -23,16 +23,16 @@ export default {
|
|||
);
|
||||
},
|
||||
getSkeletonVariant() {
|
||||
switch (this.$route.path) {
|
||||
case OBSERVABILITY_ROUTES.DASHBOARDS:
|
||||
return SKELETON_VARIANT.DASHBOARDS;
|
||||
case OBSERVABILITY_ROUTES.EXPLORE:
|
||||
return SKELETON_VARIANT.EXPLORE;
|
||||
case OBSERVABILITY_ROUTES.MANAGE:
|
||||
return SKELETON_VARIANT.MANAGE;
|
||||
default:
|
||||
return SKELETON_VARIANT.DASHBOARDS;
|
||||
}
|
||||
const [, variant] =
|
||||
Object.entries(SKELETON_VARIANTS_BY_ROUTE).find(([path]) =>
|
||||
this.$route.path.endsWith(path),
|
||||
) || [];
|
||||
|
||||
const DEFAULT_SKELETON = 'dashboards';
|
||||
|
||||
if (!variant) return DEFAULT_SKELETON;
|
||||
|
||||
return variant;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -51,7 +51,7 @@ export default {
|
|||
} = e;
|
||||
switch (type) {
|
||||
case MESSAGE_EVENT_TYPE.GOUI_LOADED:
|
||||
this.$refs.iframeSkeleton.handleSkeleton();
|
||||
this.$refs.observabilitySkeleton.onContentLoaded();
|
||||
break;
|
||||
case MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE:
|
||||
this.routeUpdateHandler(payload);
|
||||
|
|
@ -80,7 +80,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<observability-skeleton ref="iframeSkeleton" :variant="getSkeletonVariant">
|
||||
<observability-skeleton ref="observabilitySkeleton" :variant="getSkeletonVariant">
|
||||
<iframe
|
||||
id="observability-ui-iframe"
|
||||
data-testid="observability-ui-iframe"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,32 @@
|
|||
<script>
|
||||
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { SKELETON_VARIANT } from '../../constants';
|
||||
import { GlSkeletonLoader, GlAlert } from '@gitlab/ui';
|
||||
|
||||
import {
|
||||
SKELETON_VARIANTS_BY_ROUTE,
|
||||
SKELETON_STATE,
|
||||
DEFAULT_TIMERS,
|
||||
OBSERVABILITY_ROUTES,
|
||||
TIMEOUT_ERROR_LABEL,
|
||||
TIMEOUT_ERROR_MESSAGE,
|
||||
} from '../../constants';
|
||||
import DashboardsSkeleton from './dashboards.vue';
|
||||
import ExploreSkeleton from './explore.vue';
|
||||
import ManageSkeleton from './manage.vue';
|
||||
|
||||
export default {
|
||||
SKELETON_VARIANT,
|
||||
components: {
|
||||
GlSkeletonLoader,
|
||||
DashboardsSkeleton,
|
||||
ExploreSkeleton,
|
||||
ManageSkeleton,
|
||||
GlAlert,
|
||||
},
|
||||
SKELETON_VARIANTS_BY_ROUTE,
|
||||
SKELETON_STATE,
|
||||
OBSERVABILITY_ROUTES,
|
||||
i18n: {
|
||||
TIMEOUT_ERROR_LABEL,
|
||||
TIMEOUT_ERROR_MESSAGE,
|
||||
},
|
||||
props: {
|
||||
variant: {
|
||||
|
|
@ -22,65 +37,94 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
loading: null,
|
||||
timerId: null,
|
||||
state: null,
|
||||
loadingTimeout: null,
|
||||
errorTimeout: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.timerId = setTimeout(() => {
|
||||
/**
|
||||
* If observability UI is not loaded then this.loading would be null
|
||||
* we will show skeleton in that case
|
||||
*/
|
||||
if (this.loading !== false) {
|
||||
this.showSkeleton();
|
||||
}
|
||||
}, 500);
|
||||
this.setLoadingTimeout();
|
||||
this.setErrorTimeout();
|
||||
},
|
||||
destroyed() {
|
||||
clearTimeout(this.loadingTimeout);
|
||||
clearTimeout(this.errorTimeout);
|
||||
},
|
||||
methods: {
|
||||
handleSkeleton() {
|
||||
if (this.loading === null) {
|
||||
/**
|
||||
* If observability UI content loads with in 500ms
|
||||
* do not show skeleton.
|
||||
*/
|
||||
clearTimeout(this.timerId);
|
||||
return;
|
||||
}
|
||||
onContentLoaded() {
|
||||
clearTimeout(this.errorTimeout);
|
||||
clearTimeout(this.loadingTimeout);
|
||||
|
||||
/**
|
||||
* If observability UI content loads after 500ms
|
||||
* wait for 400ms to hide skeleton.
|
||||
* This is mostly to avoid the flashing effect If content loads imediately after skeleton
|
||||
*/
|
||||
setTimeout(this.hideSkeleton, 400);
|
||||
this.hideSkeleton();
|
||||
},
|
||||
setLoadingTimeout() {
|
||||
this.loadingTimeout = setTimeout(() => {
|
||||
/**
|
||||
* If content is not loaded within CONTENT_WAIT_MS,
|
||||
* show the skeleton
|
||||
*/
|
||||
if (this.state !== SKELETON_STATE.HIDDEN) {
|
||||
this.showSkeleton();
|
||||
}
|
||||
}, DEFAULT_TIMERS.CONTENT_WAIT_MS);
|
||||
},
|
||||
setErrorTimeout() {
|
||||
this.errorTimeout = setTimeout(() => {
|
||||
/**
|
||||
* If content is not loaded within TIMEOUT_MS,
|
||||
* show the error dialog
|
||||
*/
|
||||
if (this.state !== SKELETON_STATE.HIDDEN) {
|
||||
this.showError();
|
||||
}
|
||||
}, DEFAULT_TIMERS.TIMEOUT_MS);
|
||||
},
|
||||
hideSkeleton() {
|
||||
this.loading = false;
|
||||
this.state = SKELETON_STATE.HIDDEN;
|
||||
},
|
||||
showSkeleton() {
|
||||
this.loading = true;
|
||||
this.state = SKELETON_STATE.VISIBLE;
|
||||
},
|
||||
showError() {
|
||||
this.state = SKELETON_STATE.ERROR;
|
||||
},
|
||||
|
||||
isSkeletonShown(route) {
|
||||
return this.variant === SKELETON_VARIANTS_BY_ROUTE[route];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column gl-flex-align-items-stretch">
|
||||
<div v-show="loading" class="gl-px-5">
|
||||
<dashboards-skeleton v-if="variant === $options.SKELETON_VARIANT.DASHBOARDS" />
|
||||
<explore-skeleton v-else-if="variant === $options.SKELETON_VARIANT.EXPLORE" />
|
||||
<manage-skeleton v-else-if="variant === $options.SKELETON_VARIANT.MANAGE" />
|
||||
<transition name="fade">
|
||||
<div v-if="state === $options.SKELETON_STATE.VISIBLE" class="gl-px-5">
|
||||
<dashboards-skeleton v-if="isSkeletonShown($options.OBSERVABILITY_ROUTES.DASHBOARDS)" />
|
||||
<explore-skeleton v-else-if="isSkeletonShown($options.OBSERVABILITY_ROUTES.EXPLORE)" />
|
||||
<manage-skeleton v-else-if="isSkeletonShown($options.OBSERVABILITY_ROUTES.MANAGE)" />
|
||||
|
||||
<gl-skeleton-loader v-else>
|
||||
<rect y="2" width="10" height="8" />
|
||||
<rect y="2" x="15" width="15" height="8" />
|
||||
<rect y="2" x="35" width="15" height="8" />
|
||||
<rect y="15" width="400" height="30" />
|
||||
</gl-skeleton-loader>
|
||||
</div>
|
||||
<gl-skeleton-loader v-else>
|
||||
<rect y="2" width="10" height="8" />
|
||||
<rect y="2" x="15" width="15" height="8" />
|
||||
<rect y="2" x="35" width="15" height="8" />
|
||||
<rect y="15" width="400" height="30" />
|
||||
</gl-skeleton-loader>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<gl-alert
|
||||
v-if="state === $options.SKELETON_STATE.ERROR"
|
||||
:title="$options.i18n.TIMEOUT_ERROR_LABEL"
|
||||
variant="danger"
|
||||
:dismissible="false"
|
||||
class="gl-m-5"
|
||||
>
|
||||
{{ $options.i18n.TIMEOUT_ERROR_MESSAGE }}
|
||||
</gl-alert>
|
||||
|
||||
<div
|
||||
v-show="!loading"
|
||||
v-show="state === $options.SKELETON_STATE.HIDDEN"
|
||||
data-testid="observability-wrapper"
|
||||
class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column gl-flex-align-items-stretch"
|
||||
>
|
||||
<slot></slot>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,32 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export const MESSAGE_EVENT_TYPE = Object.freeze({
|
||||
GOUI_LOADED: 'GOUI_LOADED',
|
||||
GOUI_ROUTE_UPDATE: 'GOUI_ROUTE_UPDATE',
|
||||
});
|
||||
|
||||
export const OBSERVABILITY_ROUTES = Object.freeze({
|
||||
DASHBOARDS: '/groups/gitlab-org/-/observability/dashboards',
|
||||
EXPLORE: '/groups/gitlab-org/-/observability/explore',
|
||||
MANAGE: '/groups/gitlab-org/-/observability/manage',
|
||||
DASHBOARDS: 'observability/dashboards',
|
||||
EXPLORE: 'observability/explore',
|
||||
MANAGE: 'observability/manage',
|
||||
});
|
||||
|
||||
export const SKELETON_VARIANT = Object.freeze({
|
||||
DASHBOARDS: 'dashboards',
|
||||
EXPLORE: 'explore',
|
||||
MANAGE: 'manage',
|
||||
export const SKELETON_VARIANTS_BY_ROUTE = Object.freeze({
|
||||
[OBSERVABILITY_ROUTES.DASHBOARDS]: 'dashboards',
|
||||
[OBSERVABILITY_ROUTES.EXPLORE]: 'explore',
|
||||
[OBSERVABILITY_ROUTES.MANAGE]: 'manage',
|
||||
});
|
||||
|
||||
export const SKELETON_STATE = Object.freeze({
|
||||
ERROR: 'error',
|
||||
VISIBLE: 'visible',
|
||||
HIDDEN: 'hidden',
|
||||
});
|
||||
|
||||
export const DEFAULT_TIMERS = Object.freeze({
|
||||
TIMEOUT_MS: 20000,
|
||||
CONTENT_WAIT_MS: 500,
|
||||
});
|
||||
|
||||
export const TIMEOUT_ERROR_LABEL = __('Unable to load the page');
|
||||
export const TIMEOUT_ERROR_MESSAGE = __('Reload the page to try again.');
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@ module ApplicationHelper
|
|||
#
|
||||
# Returns an HTML-safe String
|
||||
def time_ago_with_tooltip(time, placement: 'top', html_class: '', short_format: false)
|
||||
return "" if time.nil?
|
||||
|
||||
css_classes = [short_format ? 'js-short-timeago' : 'js-timeago']
|
||||
css_classes << html_class unless html_class.blank?
|
||||
|
||||
|
|
|
|||
|
|
@ -919,8 +919,12 @@ module Ci
|
|||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||
next variables unless tag?
|
||||
|
||||
git_tag = project.repository.find_tag(ref)
|
||||
|
||||
next variables unless git_tag
|
||||
|
||||
variables.append(key: 'CI_COMMIT_TAG', value: ref)
|
||||
variables.append(key: 'CI_COMMIT_TAG_MESSAGE', value: project.repository.find_tag(ref).message)
|
||||
variables.append(key: 'CI_COMMIT_TAG_MESSAGE', value: git_tag.message)
|
||||
|
||||
# legacy variable
|
||||
variables.append(key: 'CI_BUILD_TAG', value: ref)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ month. Major API version changes, and removal of entire API versions, are done i
|
|||
with major GitLab releases.
|
||||
|
||||
All deprecations and changes between versions are in the documentation.
|
||||
For the changes between v3 and v4, see the [v3 to v4 documentation](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/11-0-stable/doc/api/v3_to_v4.md).
|
||||
|
||||
### Current status
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
---
|
||||
redirect_to: 'https://gitlab.com/gitlab-org/gitlab-foss/-/blob/11-0-stable/doc/api/v3_to_v4.md'
|
||||
remove_date: '2023-01-31'
|
||||
---
|
||||
|
||||
This document was moved to [another location](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/11-0-stable/doc/api/v3_to_v4.md).
|
||||
|
||||
<!-- This redirect file can be deleted after <2023-01-31>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
@ -148,26 +148,25 @@ In addition, we should add the following columns to `ci_runners`:
|
|||
future uses that may not be apparent.
|
||||
|
||||
```sql
|
||||
CREATE TABLE ci_runner (
|
||||
CREATE TABLE ci_runners (
|
||||
...
|
||||
creator_id bigint
|
||||
registration_type int8
|
||||
)
|
||||
```
|
||||
|
||||
The `ci_builds_runner_session` (or `ci_builds` or `ci_builds_metadata`) shall reference
|
||||
`ci_runner_machines`.
|
||||
The `ci_builds_metadata` table shall reference `ci_runner_machines`.
|
||||
We might consider a more efficient way to store `contacted_at` than updating the existing record.
|
||||
|
||||
```sql
|
||||
CREATE TABLE ci_builds_runner_session (
|
||||
CREATE TABLE ci_builds_metadata (
|
||||
...
|
||||
runner_machine_id bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE ci_runner_machines (
|
||||
id integer NOT NULL,
|
||||
machine_id character varying UNIQUE NOT NULL,
|
||||
id bigint NOT NULL,
|
||||
machine_xid character varying UNIQUE NOT NULL,
|
||||
contacted_at timestamp without time zone,
|
||||
version character varying,
|
||||
revision character varying,
|
||||
|
|
@ -241,7 +240,7 @@ future after the legacy registration system is removed, and runners have been up
|
|||
versions.
|
||||
|
||||
Job pings from such legacy runners results in a `ci_runner_machines` record containing a
|
||||
`<legacy>` `machine_id` field value.
|
||||
`<legacy>` `machine_xid` field value.
|
||||
|
||||
Not using the unique system ID means that all connected runners with the same token are
|
||||
notified, instead of just the runner matching the exact system identifier. While not ideal, this is
|
||||
|
|
@ -320,9 +319,9 @@ using PAT tokens for example - such that every runner is associated with an owne
|
|||
|------------------|----------:|---------|
|
||||
| GitLab Rails app | | Create database migration to add columns to `ci_runners` table. |
|
||||
| GitLab Rails app | | Create database migration to add `ci_runner_machines` table. |
|
||||
| GitLab Rails app | | Create database migration to add `ci_runner_machines.machine_id` foreign key to `ci_builds_runner_session` table. |
|
||||
| GitLab Rails app | | Create database migration to add `ci_runner_machines.id` foreign key to `ci_builds_metadata` table. |
|
||||
| GitLab Rails app | | Create database migrations to add `allow_runner_registration_token` setting to `application_settings` and `namespace_settings` tables (default: `true`). |
|
||||
| GitLab Runner | | Use runner token + `system_id` JSON parameters in `POST /jobs/request` request in the [heartbeat request](https://gitlab.com/gitlab-org/gitlab/blob/c73c96a8ffd515295842d72a3635a8ae873d688c/lib/api/ci/helpers/runner.rb#L14-20) to update the `ci_runner_machines` cache/table. |
|
||||
| GitLab Rails app | | Use runner token + `system_id` JSON parameters in `POST /jobs/request` request in the [heartbeat request](https://gitlab.com/gitlab-org/gitlab/blob/c73c96a8ffd515295842d72a3635a8ae873d688c/lib/api/ci/helpers/runner.rb#L14-20) to update the `ci_runner_machines` cache/table. |
|
||||
| GitLab Runner | | Start sending `system_id` value in `POST /jobs/request` request and other follow-up requests that require identifying the unique system. |
|
||||
| GitLab Rails app | | Create service similar to `StaleGroupRunnersPruneCronWorker` service to clean up `ci_runner_machines` records instead of `ci_runners` records.<br/>Existing service continues to exist but focuses only on legacy runners. |
|
||||
|
||||
|
|
|
|||
|
|
@ -534,10 +534,6 @@ Also in the example, `GIT_STRATEGY` is set to `none`. If the
|
|||
`stop_review_app` job is [automatically triggered](../environments/index.md#stop-an-environment),
|
||||
the runner won't try to check out the code after the branch is deleted.
|
||||
|
||||
The example also overwrites global variables. If your `stop` `environment` job depends
|
||||
on global variables, use [anchor variables](../yaml/yaml_optimization.md#yaml-anchors-for-variables) when you set the `GIT_STRATEGY`
|
||||
to change the job without overriding the global variables.
|
||||
|
||||
The `stop_review_app` job **must** have the following keywords defined:
|
||||
|
||||
- `when`, defined at either:
|
||||
|
|
|
|||
|
|
@ -4343,7 +4343,6 @@ deploy_review_job:
|
|||
|
||||
**Related topics**:
|
||||
|
||||
- You can use [YAML anchors for variables](yaml_optimization.md#yaml-anchors-for-variables).
|
||||
- [Predefined variables](../variables/predefined_variables.md) are variables the runner
|
||||
automatically creates and makes available in the job.
|
||||
- You can [configure runner behavior with variables](../runners/configure_runners.md#configure-runner-behavior-with-variables).
|
||||
|
|
|
|||
|
|
@ -189,30 +189,6 @@ job2:
|
|||
- *some-script-after
|
||||
```
|
||||
|
||||
### YAML anchors for variables
|
||||
|
||||
Use [YAML anchors](#anchors) with `variables` to repeat assignment
|
||||
of variables across multiple jobs. You can also use YAML anchors when a job
|
||||
requires a specific `variables` block that would otherwise override the global variables.
|
||||
|
||||
The following example shows how override the `GIT_STRATEGY` variable without affecting
|
||||
the use of the `SAMPLE_VARIABLE` variable:
|
||||
|
||||
```yaml
|
||||
# global variables
|
||||
variables: &global-variables
|
||||
SAMPLE_VARIABLE: sample_variable_value
|
||||
ANOTHER_SAMPLE_VARIABLE: another_sample_variable_value
|
||||
|
||||
# a job that must set the GIT_STRATEGY variable, yet depend on global variables
|
||||
job_no_git_strategy:
|
||||
stage: cleanup
|
||||
variables:
|
||||
<<: *global-variables
|
||||
GIT_STRATEGY: none
|
||||
script: echo $SAMPLE_VARIABLE
|
||||
```
|
||||
|
||||
## Use `extends` to reuse configuration sections
|
||||
|
||||
You can use the [`extends` keyword](index.md#extends) to reuse configuration in
|
||||
|
|
|
|||
|
|
@ -294,16 +294,14 @@ end
|
|||
|
||||
### Verify the MR was deployed and the index exists in production
|
||||
|
||||
You can verify if the post-deploy migration was executed on GitLab.com by:
|
||||
|
||||
- Executing `/chatops run auto_deploy status <merge_sha>`. If the output returns `db/gprd`,
|
||||
the post-deploy migration has been executed in the production database. More details in this
|
||||
[guide](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/post_deploy_migration/readme.md#how-to-determine-if-a-post-deploy-migration-has-been-executed-on-gitlabcom).
|
||||
- Use a meta-command in #database-lab, such as: `\d <index_name>`.
|
||||
- Ensure that the index is not [`invalid`](https://www.postgresql.org/docs/12/sql-createindex.html#:~:text=The%20psql%20%5Cd%20command%20will%20report%20such%20an%20index%20as%20INVALID).
|
||||
- Ask someone in #database to check if the index exists.
|
||||
- With proper access, you can also verify directly on production or in a
|
||||
production clone.
|
||||
1. Verify that the post-deploy migration was executed on GitLab.com using ChatOps with
|
||||
`/chatops run auto_deploy status <merge_sha>`. If the output returns `db/gprd`,
|
||||
the post-deploy migration has been executed in the production database. For more information, see
|
||||
[How to determine if a post-deploy migration has been executed on GitLab.com](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/post_deploy_migration/readme.md#how-to-determine-if-a-post-deploy-migration-has-been-executed-on-gitlabcom).
|
||||
1. In the case of an [index created asynchronously](#schedule-the-index-to-be-created), wait
|
||||
until the next week so that the index can be created over a weekend.
|
||||
1. Use [Database Lab](database_lab.md) to check [if creation was successful](database_lab.md#checking-indexes).
|
||||
Ensure the output does not indicate the index is `invalid`.
|
||||
|
||||
### Add a migration to create the index synchronously
|
||||
|
||||
|
|
@ -394,15 +392,15 @@ You must test the database index changes locally before creating a merge request
|
|||
|
||||
### Verify the MR was deployed and the index no longer exists in production
|
||||
|
||||
You can verify if the MR was deployed to GitLab.com with
|
||||
`/chatops run auto_deploy status <merge_sha>`. To verify the existence of
|
||||
the index, you can:
|
||||
|
||||
- Use a meta-command in `#database-lab`, for example: `\d <index_name>`.
|
||||
- Make sure the index no longer exists
|
||||
- Ask someone in `#database` to check if the index exists.
|
||||
- If you have access, you can verify directly on production or in a
|
||||
production clone.
|
||||
1. Verify that the post-deploy migration was executed on GitLab.com using ChatOps with
|
||||
`/chatops run auto_deploy status <merge_sha>`. If the output returns `db/gprd`,
|
||||
the post-deploy migration has been executed in the production database. For more information, see
|
||||
[How to determine if a post-deploy migration has been executed on GitLab.com](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/post_deploy_migration/readme.md#how-to-determine-if-a-post-deploy-migration-has-been-executed-on-gitlabcom).
|
||||
1. In the case of an [index removed asynchronously](#schedule-the-index-to-be-removed), wait
|
||||
until the next week so that the index can be created over a weekend.
|
||||
1. Use Database Lab [to check if removal was successful](database_lab.md#checking-indexes).
|
||||
[Database Lab](database_lab.md)
|
||||
should report an error when trying to find the removed index. If not, the index may still exist.
|
||||
|
||||
### Add a migration to destroy the index synchronously
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,45 @@ the new index. `exec` does not return any results, only the time required to exe
|
|||
After many changes, such as after a destructive query or an ineffective index,
|
||||
you must start over. To reset your designated clone, run `reset`.
|
||||
|
||||
#### Checking indexes
|
||||
|
||||
Use Database Lab to check the status of an index with the meta-command `\d <index_name>`.
|
||||
|
||||
Caveats:
|
||||
|
||||
- Indexes are created in both the `main` and `ci` databases, so be sure to use the instance
|
||||
that matches the table's `gitlab_schema`. For example, if the index is added to
|
||||
[`ci_builds`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/docs/ci_builds.yml#L14),
|
||||
use `gitlab-production-ci`.
|
||||
- Database Lab typically has a small delay of a few hours. If more up-to-date information
|
||||
is required, you can instead request access to a replica [via Teleport](https://gitlab.com/gitlab-com/runbooks/-/blob/master/docs/Teleport/Connect_to_Database_Console_via_Teleport.md)
|
||||
|
||||
For example: `\d index_design_management_designs_on_project_id` produces:
|
||||
|
||||
```plaintext
|
||||
Index "public.index_design_management_designs_on_project_id"
|
||||
Column | Type | Key? | Definition
|
||||
------------+---------+------+------------
|
||||
project_id | integer | yes | project_id
|
||||
btree, for table "public.design_management_designs"
|
||||
```
|
||||
|
||||
In the case of an invalid index, the output ends with `invalid`, like:
|
||||
|
||||
```plaintext
|
||||
Index "public.index_design_management_designs_on_project_id"
|
||||
Column | Type | Key? | Definition
|
||||
------------+---------+------+------------
|
||||
project_id | integer | yes | project_id
|
||||
btree, for table "public.design_management_designs", invalid
|
||||
```
|
||||
|
||||
If the index doesn't exist, JoeBot throws an error like:
|
||||
|
||||
```plaintext
|
||||
ERROR: psql error: psql:/tmp/psql-query-932227396:1: error: Did not find any relation named "no_index".
|
||||
```
|
||||
|
||||
### Migration testing
|
||||
|
||||
For information on testing migrations, review our
|
||||
|
|
|
|||
|
|
@ -66,16 +66,16 @@ In the hierarchy list, public groups with a private subgroup have an expand opti
|
|||
for all users that indicate there is a subgroup. When users who are not direct or inherited members of
|
||||
the private subgroup select expand (**{chevron-down}**), the nested subgroup does not display.
|
||||
|
||||
If you prefer to keep information about the presence of nested subgroups private, we advise that you only
|
||||
add private subgroups to private parent groups.
|
||||
If you prefer to keep information about the presence of nested subgroups private, we advise that you
|
||||
add private subgroups only to private parent groups.
|
||||
|
||||
## Create a subgroup
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must either:
|
||||
- Have at least the Maintainer role for a group to create subgroups for it.
|
||||
- Have the [role determined by a setting](#change-who-can-create-subgroups). These users can create
|
||||
- You must have either:
|
||||
- At least the Maintainer role for a group to create subgroups for it.
|
||||
- The [role determined by a setting](#change-who-can-create-subgroups). These users can create
|
||||
subgroups even if group creation is
|
||||
[disabled by an Administrator](../../admin_area/index.md#prevent-a-user-from-creating-groups) in the user's settings.
|
||||
|
||||
|
|
@ -92,8 +92,9 @@ To create a subgroup:
|
|||
|
||||
### Change who can create subgroups
|
||||
|
||||
To create a subgroup, you must have at least the Maintainer role on the group, depending on the group's setting. By
|
||||
default:
|
||||
Prerequisite:
|
||||
|
||||
- You must have at least the Maintainer role on the group, depending on the group's setting.
|
||||
|
||||
To change who can create subgroups on a group:
|
||||
|
||||
|
|
@ -120,11 +121,11 @@ There is a bug that causes some pages in the parent group to be accessible by su
|
|||
When you add a member to a group, that member is also added to all subgroups. The user's permissions are inherited from
|
||||
the group's parent.
|
||||
|
||||
Subgroup members can:
|
||||
Subgroup members can be:
|
||||
|
||||
1. Be [direct members](../../project/members/index.md#add-users-to-a-project) of the subgroup.
|
||||
1. [Inherit membership](../../project/members/index.md#inherited-membership) of the subgroup from the subgroup's parent group.
|
||||
1. Be a member of a group that was [shared with the subgroup's top-level group](../manage.md#share-a-group-with-another-group).
|
||||
1. [Direct members](../../project/members/index.md#add-users-to-a-project) of the subgroup.
|
||||
1. [Inherited members](../../project/members/index.md#inherited-membership) of the subgroup from the subgroup's parent group.
|
||||
1. Members of a group that was [shared with the subgroup's top-level group](../manage.md#share-a-group-with-another-group).
|
||||
|
||||
```mermaid
|
||||
flowchart RL
|
||||
|
|
|
|||
|
|
@ -1362,8 +1362,10 @@ Payload example:
|
|||
|
||||
## Job events
|
||||
|
||||
- Number of retries [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382046) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md)
|
||||
- Number of retries (`retries_count`) [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382046) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md)
|
||||
named `job_webhook_retries_count`. Disabled by default.
|
||||
- Pipeline name (`commit.name`) [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107963) in GitLab 15.8 [with a flag](../../../administration/feature_flags.md)
|
||||
named `pipeline_name`. Enabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available,
|
||||
|
|
@ -1415,6 +1417,7 @@ Payload example:
|
|||
},
|
||||
"commit": {
|
||||
"id": 2366,
|
||||
"name": "Build pipeline",
|
||||
"sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
|
||||
"message": "test\n",
|
||||
"author_name": "User",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ module Gitlab
|
|||
}
|
||||
|
||||
data[:retries_count] = build.retries_count if Feature.enabled?(:job_webhook_retries_count, project)
|
||||
data[:commit][:name] = commit.name if Feature.enabled?(:pipeline_name, project)
|
||||
|
||||
data
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34567,6 +34567,9 @@ msgstr ""
|
|||
msgid "Reload page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reload the page to try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Remediations"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -44255,6 +44258,9 @@ msgstr ""
|
|||
msgid "Unable to load the merge request widget. Try reloading the page."
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to load the page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to parse JSON"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/fonts": "^1.0.1",
|
||||
"@gitlab/svgs": "3.14.0",
|
||||
"@gitlab/ui": "52.7.0",
|
||||
"@gitlab/ui": "52.7.2",
|
||||
"@gitlab/visual-review-tools": "1.7.3",
|
||||
"@gitlab/web-ide": "0.0.1-dev-20230102181448",
|
||||
"@rails/actioncable": "6.1.4-7",
|
||||
|
|
|
|||
|
|
@ -24,8 +24,9 @@ RSpec.describe 'Project variables', :js, feature_category: :pipeline_authoring d
|
|||
page.within('#add-ci-variable') do
|
||||
fill_in 'Key', with: 'akey'
|
||||
find('#ci-variable-value').set('akey_value')
|
||||
find('[data-testid="environment-scope"]').click
|
||||
find('[data-testid="ci-environment-search"]').set('review/*')
|
||||
|
||||
click_button('All (default)')
|
||||
fill_in 'Search', with: 'review/*'
|
||||
find('[data-testid="create-wildcard-button"]').click
|
||||
|
||||
click_button('Add variable')
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { GlDropdown, GlDropdownItem, GlIcon, GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { GlListboxItem, GlCollapsibleListbox, GlDropdownItem, GlIcon } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { allEnvironments } from '~/ci_variable_list/constants';
|
||||
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
|
||||
|
||||
|
|
@ -10,11 +9,12 @@ describe('Ci environments dropdown', () => {
|
|||
const envs = ['dev', 'prod', 'staging'];
|
||||
const defaultProps = { environments: envs, selectedEnvironmentScope: '' };
|
||||
|
||||
const findDropdownText = () => wrapper.findComponent(GlDropdown).text();
|
||||
const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
|
||||
const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index);
|
||||
const findActiveIconByIndex = (index) => findDropdownItemByIndex(index).findComponent(GlIcon);
|
||||
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
|
||||
const findAllListboxItems = () => wrapper.findAllComponents(GlListboxItem);
|
||||
const findListboxItemByIndex = (index) => wrapper.findAllComponents(GlListboxItem).at(index);
|
||||
const findActiveIconByIndex = (index) => findListboxItemByIndex(index).findComponent(GlIcon);
|
||||
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
const findListboxText = () => findListbox().props('toggleText');
|
||||
const findCreateWildcardButton = () => wrapper.findComponent(GlDropdownItem);
|
||||
|
||||
const createComponent = ({ props = {}, searchTerm = '' } = {}) => {
|
||||
wrapper = mount(CiEnvironmentsDropdown, {
|
||||
|
|
@ -24,7 +24,7 @@ describe('Ci environments dropdown', () => {
|
|||
},
|
||||
});
|
||||
|
||||
findSearchBox().vm.$emit('input', searchTerm);
|
||||
findListbox().vm.$emit('search', searchTerm);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -37,12 +37,9 @@ describe('Ci environments dropdown', () => {
|
|||
});
|
||||
|
||||
it('renders create button with search term if environments do not contain search term', () => {
|
||||
expect(findAllDropdownItems()).toHaveLength(2);
|
||||
expect(findDropdownItemByIndex(1).text()).toBe('Create wildcard: stable');
|
||||
});
|
||||
|
||||
it('renders empty results message', () => {
|
||||
expect(findDropdownItemByIndex(0).text()).toBe('No matching results');
|
||||
const button = findCreateWildcardButton();
|
||||
expect(button.exists()).toBe(true);
|
||||
expect(button.text()).toBe('Create wildcard: stable');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -52,13 +49,12 @@ describe('Ci environments dropdown', () => {
|
|||
});
|
||||
|
||||
it('renders all environments when search term is empty', () => {
|
||||
expect(findAllDropdownItems()).toHaveLength(3);
|
||||
expect(findDropdownItemByIndex(0).text()).toBe(envs[0]);
|
||||
expect(findDropdownItemByIndex(1).text()).toBe(envs[1]);
|
||||
expect(findDropdownItemByIndex(2).text()).toBe(envs[2]);
|
||||
expect(findListboxItemByIndex(0).text()).toBe(envs[0]);
|
||||
expect(findListboxItemByIndex(1).text()).toBe(envs[1]);
|
||||
expect(findListboxItemByIndex(2).text()).toBe(envs[2]);
|
||||
});
|
||||
|
||||
it('should not display active checkmark on the inactive stage', () => {
|
||||
it('does not display active checkmark on the inactive stage', () => {
|
||||
expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -71,53 +67,37 @@ describe('Ci environments dropdown', () => {
|
|||
});
|
||||
|
||||
it('shows the `All environments` text and not the wildcard', () => {
|
||||
expect(findDropdownText()).toContain(allEnvironments.text);
|
||||
expect(findDropdownText()).not.toContain(wildcardScope);
|
||||
expect(findListboxText()).toContain(allEnvironments.text);
|
||||
expect(findListboxText()).not.toContain(wildcardScope);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Environments found', () => {
|
||||
const currentEnv = envs[2];
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ searchTerm: currentEnv });
|
||||
await nextTick();
|
||||
});
|
||||
|
||||
it('renders only the environment searched for', () => {
|
||||
expect(findAllDropdownItems()).toHaveLength(1);
|
||||
expect(findDropdownItemByIndex(0).text()).toBe(currentEnv);
|
||||
expect(findAllListboxItems()).toHaveLength(1);
|
||||
expect(findListboxItemByIndex(0).text()).toBe(currentEnv);
|
||||
});
|
||||
|
||||
it('should not display create button', () => {
|
||||
const environments = findAllDropdownItems().filter((env) => env.text().startsWith('Create'));
|
||||
expect(environments).toHaveLength(0);
|
||||
expect(findAllDropdownItems()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not display empty results message', () => {
|
||||
expect(wrapper.findComponent({ ref: 'noMatchingResults' }).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should clear the search term when showing the dropdown', () => {
|
||||
wrapper.findComponent(GlDropdown).trigger('click');
|
||||
|
||||
expect(findSearchBox().text()).toBe('');
|
||||
it('does not display create button', () => {
|
||||
expect(findCreateWildcardButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('Custom events', () => {
|
||||
describe('when clicking on an environment', () => {
|
||||
describe('when selecting an environment', () => {
|
||||
const itemIndex = 0;
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should emit `select-environment` if an environment is clicked', async () => {
|
||||
await nextTick();
|
||||
|
||||
await findDropdownItemByIndex(itemIndex).vm.$emit('click');
|
||||
|
||||
it('emits `select-environment` when an environment is clicked', () => {
|
||||
findListbox().vm.$emit('select', envs[itemIndex]);
|
||||
expect(wrapper.emitted('select-environment')).toEqual([[envs[itemIndex]]]);
|
||||
});
|
||||
});
|
||||
|
|
@ -128,9 +108,8 @@ describe('Ci environments dropdown', () => {
|
|||
createComponent({ searchTerm: search });
|
||||
});
|
||||
|
||||
it('should emit createClicked if an environment is clicked', async () => {
|
||||
await nextTick();
|
||||
findDropdownItemByIndex(1).vm.$emit('click');
|
||||
it('emits create-environment-scope', () => {
|
||||
findCreateWildcardButton().vm.$emit('click');
|
||||
expect(wrapper.emitted('create-environment-scope')).toEqual([[search]]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ exports[`Design management design version dropdown component renders design vers
|
|||
tabindex="-1"
|
||||
>
|
||||
<gl-listbox-item-stub
|
||||
data-testid="listbox-item-gid://gitlab/DesignManagement::Version/1"
|
||||
ischeckcentered="true"
|
||||
>
|
||||
<span
|
||||
|
|
@ -66,6 +67,7 @@ exports[`Design management design version dropdown component renders design vers
|
|||
</span>
|
||||
</gl-listbox-item-stub>
|
||||
<gl-listbox-item-stub
|
||||
data-testid="listbox-item-gid://gitlab/DesignManagement::Version/2"
|
||||
ischeckcentered="true"
|
||||
>
|
||||
<span
|
||||
|
|
@ -142,6 +144,7 @@ exports[`Design management design version dropdown component renders design vers
|
|||
tabindex="-1"
|
||||
>
|
||||
<gl-listbox-item-stub
|
||||
data-testid="listbox-item-gid://gitlab/DesignManagement::Version/1"
|
||||
ischeckcentered="true"
|
||||
>
|
||||
<span
|
||||
|
|
@ -184,6 +187,7 @@ exports[`Design management design version dropdown component renders design vers
|
|||
</span>
|
||||
</gl-listbox-item-stub>
|
||||
<gl-listbox-item-stub
|
||||
data-testid="listbox-item-gid://gitlab/DesignManagement::Version/2"
|
||||
ischeckcentered="true"
|
||||
>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
|||
import ObservabilityApp from '~/observability/components/observability_app.vue';
|
||||
import ObservabilitySkeleton from '~/observability/components/skeleton/index.vue';
|
||||
|
||||
import {
|
||||
MESSAGE_EVENT_TYPE,
|
||||
OBSERVABILITY_ROUTES,
|
||||
SKELETON_VARIANT,
|
||||
} from '~/observability/constants';
|
||||
import { MESSAGE_EVENT_TYPE, SKELETON_VARIANTS_BY_ROUTE } from '~/observability/constants';
|
||||
|
||||
import { darkModeEnabled } from '~/lib/utils/color_utils';
|
||||
|
||||
|
|
@ -20,6 +16,7 @@ describe('Observability root app', () => {
|
|||
};
|
||||
const $route = {
|
||||
pathname: 'https://gitlab.com/gitlab-org/',
|
||||
path: 'https://gitlab.com/gitlab-org/-/observability/dashboards',
|
||||
query: { otherQuery: 100 },
|
||||
};
|
||||
|
||||
|
|
@ -29,6 +26,10 @@ describe('Observability root app', () => {
|
|||
|
||||
const TEST_IFRAME_SRC = 'https://observe.gitlab.com/9970/?groupId=14485840';
|
||||
|
||||
const OBSERVABILITY_ROUTES = Object.keys(SKELETON_VARIANTS_BY_ROUTE);
|
||||
|
||||
const SKELETON_VARIANTS = Object.values(SKELETON_VARIANTS_BY_ROUTE);
|
||||
|
||||
const mountComponent = (route = $route) => {
|
||||
wrapper = shallowMountExtended(ObservabilityApp, {
|
||||
propsData: {
|
||||
|
|
@ -139,9 +140,9 @@ describe('Observability root app', () => {
|
|||
describe('on GOUI_LOADED', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
wrapper.vm.$refs.iframeSkeleton.handleSkeleton = mockHandleSkeleton;
|
||||
wrapper.vm.$refs.observabilitySkeleton.onContentLoaded = mockHandleSkeleton;
|
||||
});
|
||||
it('should call handleSkeleton method', () => {
|
||||
it('should call onContentLoaded method', () => {
|
||||
dispatchMessageEvent({
|
||||
data: { type: MESSAGE_EVENT_TYPE.GOUI_LOADED },
|
||||
origin: 'https://observe.gitlab.com',
|
||||
|
|
@ -149,7 +150,7 @@ describe('Observability root app', () => {
|
|||
expect(mockHandleSkeleton).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call handleSkeleton method if origin is different', () => {
|
||||
it('should not call onContentLoaded method if origin is different', () => {
|
||||
dispatchMessageEvent({
|
||||
data: { type: MESSAGE_EVENT_TYPE.GOUI_LOADED },
|
||||
origin: 'https://example.com',
|
||||
|
|
@ -157,7 +158,7 @@ describe('Observability root app', () => {
|
|||
expect(mockHandleSkeleton).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call handleSkeleton method if event type is different', () => {
|
||||
it('should not call onContentLoaded method if event type is different', () => {
|
||||
dispatchMessageEvent({
|
||||
data: { type: 'UNKNOWN_EVENT' },
|
||||
origin: 'https://observe.gitlab.com',
|
||||
|
|
@ -168,11 +169,11 @@ describe('Observability root app', () => {
|
|||
|
||||
describe('skeleton variant', () => {
|
||||
it.each`
|
||||
pathDescription | path | variant
|
||||
${'dashboards'} | ${OBSERVABILITY_ROUTES.DASHBOARDS} | ${SKELETON_VARIANT.DASHBOARDS}
|
||||
${'explore'} | ${OBSERVABILITY_ROUTES.EXPLORE} | ${SKELETON_VARIANT.EXPLORE}
|
||||
${'manage dashboards'} | ${OBSERVABILITY_ROUTES.MANAGE} | ${SKELETON_VARIANT.MANAGE}
|
||||
${'any other'} | ${'unknown/route'} | ${SKELETON_VARIANT.DASHBOARDS}
|
||||
pathDescription | path | variant
|
||||
${'dashboards'} | ${OBSERVABILITY_ROUTES[0]} | ${SKELETON_VARIANTS[0]}
|
||||
${'explore'} | ${OBSERVABILITY_ROUTES[1]} | ${SKELETON_VARIANTS[1]}
|
||||
${'manage dashboards'} | ${OBSERVABILITY_ROUTES[2]} | ${SKELETON_VARIANTS[2]}
|
||||
${'any other'} | ${'unknown/route'} | ${SKELETON_VARIANTS[0]}
|
||||
`('renders the $variant skeleton variant for $pathDescription path', ({ path, variant }) => {
|
||||
mountComponent({ ...$route, path });
|
||||
const props = wrapper.findComponent(ObservabilitySkeleton).props();
|
||||
|
|
|
|||
|
|
@ -1,96 +1,127 @@
|
|||
import { GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { GlSkeletonLoader, GlAlert } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import ObservabilitySkeleton from '~/observability/components/skeleton/index.vue';
|
||||
import Skeleton from '~/observability/components/skeleton/index.vue';
|
||||
import DashboardsSkeleton from '~/observability/components/skeleton/dashboards.vue';
|
||||
import ExploreSkeleton from '~/observability/components/skeleton/explore.vue';
|
||||
import ManageSkeleton from '~/observability/components/skeleton/manage.vue';
|
||||
|
||||
import { SKELETON_VARIANT } from '~/observability/constants';
|
||||
import { SKELETON_VARIANTS_BY_ROUTE, DEFAULT_TIMERS } from '~/observability/constants';
|
||||
|
||||
describe('ObservabilitySkeleton component', () => {
|
||||
describe('Skeleton component', () => {
|
||||
let wrapper;
|
||||
|
||||
const SKELETON_VARIANTS = Object.values(SKELETON_VARIANTS_BY_ROUTE);
|
||||
|
||||
const findContentWrapper = () => wrapper.findByTestId('observability-wrapper');
|
||||
|
||||
const findExploreSkeleton = () => wrapper.findComponent(ExploreSkeleton);
|
||||
|
||||
const findDashboardsSkeleton = () => wrapper.findComponent(DashboardsSkeleton);
|
||||
|
||||
const findManageSkeleton = () => wrapper.findComponent(ManageSkeleton);
|
||||
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
const mountComponent = ({ ...props } = {}) => {
|
||||
wrapper = shallowMountExtended(ObservabilitySkeleton, {
|
||||
wrapper = shallowMountExtended(Skeleton, {
|
||||
propsData: props,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('on mount', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(global, 'setTimeout');
|
||||
mountComponent();
|
||||
mountComponent({ variant: 'explore' });
|
||||
});
|
||||
|
||||
it('should call setTimeout on mount and show ObservabilitySkeleton if Observability UI is not loaded yet', () => {
|
||||
jest.runAllTimers();
|
||||
describe('loading timers', () => {
|
||||
it('show Skeleton if content is not loaded within CONTENT_WAIT_MS', async () => {
|
||||
expect(findExploreSkeleton().exists()).toBe(false);
|
||||
expect(findContentWrapper().isVisible()).toBe(false);
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 500);
|
||||
expect(wrapper.vm.loading).toBe(true);
|
||||
expect(wrapper.vm.timerId).not.toBeNull();
|
||||
jest.advanceTimersByTime(DEFAULT_TIMERS.CONTENT_WAIT_MS);
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findExploreSkeleton().exists()).toBe(true);
|
||||
expect(findContentWrapper().isVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not show the skeleton if content has loaded within CONTENT_WAIT_MS', async () => {
|
||||
expect(findExploreSkeleton().exists()).toBe(false);
|
||||
expect(findContentWrapper().isVisible()).toBe(false);
|
||||
|
||||
wrapper.vm.onContentLoaded();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findContentWrapper().isVisible()).toBe(true);
|
||||
expect(findExploreSkeleton().exists()).toBe(false);
|
||||
|
||||
jest.advanceTimersByTime(DEFAULT_TIMERS.CONTENT_WAIT_MS);
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findContentWrapper().isVisible()).toBe(true);
|
||||
expect(findExploreSkeleton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call setTimeout on mount and dont show ObservabilitySkeleton if Observability UI is loaded', () => {
|
||||
wrapper.vm.loading = false;
|
||||
jest.runAllTimers();
|
||||
describe('error timeout', () => {
|
||||
it('shows the error dialog if content has not loaded within TIMEOUT_MS', async () => {
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
jest.advanceTimersByTime(DEFAULT_TIMERS.TIMEOUT_MS);
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 500);
|
||||
expect(wrapper.vm.loading).toBe(false);
|
||||
expect(wrapper.vm.timerId).not.toBeNull();
|
||||
});
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
describe('handleSkeleton', () => {
|
||||
it('will not show the skeleton if Observability UI is loaded before', () => {
|
||||
jest.spyOn(global, 'clearTimeout');
|
||||
mountComponent();
|
||||
wrapper.vm.handleSkeleton();
|
||||
expect(clearTimeout).toHaveBeenCalledWith(wrapper.vm.timerId);
|
||||
});
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
expect(findContentWrapper().isVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it('will hide skeleton gracefully after 400ms if skeleton was present on screen before Observability UI', () => {
|
||||
jest.spyOn(global, 'setTimeout');
|
||||
mountComponent();
|
||||
jest.runAllTimers();
|
||||
wrapper.vm.handleSkeleton();
|
||||
jest.runAllTimers();
|
||||
it('does not show the error dialog if content has loaded within TIMEOUT_MS', async () => {
|
||||
wrapper.vm.onContentLoaded();
|
||||
jest.advanceTimersByTime(DEFAULT_TIMERS.TIMEOUT_MS);
|
||||
|
||||
expect(setTimeout).toHaveBeenCalledWith(wrapper.vm.hideSkeleton, 400);
|
||||
expect(wrapper.vm.loading).toBe(false);
|
||||
await nextTick();
|
||||
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
expect(findContentWrapper().isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('skeleton variant', () => {
|
||||
it.each`
|
||||
skeletonType | condition | variant
|
||||
${'dashboards'} | ${'variant is dashboards'} | ${SKELETON_VARIANT.DASHBOARDS}
|
||||
${'explore'} | ${'variant is explore'} | ${SKELETON_VARIANT.EXPLORE}
|
||||
${'manage'} | ${'variant is manage'} | ${SKELETON_VARIANT.MANAGE}
|
||||
${'dashboards'} | ${'variant is dashboards'} | ${SKELETON_VARIANTS[0]}
|
||||
${'explore'} | ${'variant is explore'} | ${SKELETON_VARIANTS[1]}
|
||||
${'manage'} | ${'variant is manage'} | ${SKELETON_VARIANTS[2]}
|
||||
${'default'} | ${'variant is not manage, dashboards or explore'} | ${'unknown'}
|
||||
`('should render $skeletonType skeleton if $condition', async ({ skeletonType, variant }) => {
|
||||
mountComponent({ variant });
|
||||
const showsDefaultSkeleton = ![
|
||||
SKELETON_VARIANT.DASHBOARDS,
|
||||
SKELETON_VARIANT.EXPLORE,
|
||||
SKELETON_VARIANT.MANAGE,
|
||||
].includes(variant);
|
||||
expect(wrapper.findComponent(DashboardsSkeleton).exists()).toBe(
|
||||
skeletonType === SKELETON_VARIANT.DASHBOARDS,
|
||||
);
|
||||
expect(wrapper.findComponent(ExploreSkeleton).exists()).toBe(
|
||||
skeletonType === SKELETON_VARIANT.EXPLORE,
|
||||
);
|
||||
expect(wrapper.findComponent(ManageSkeleton).exists()).toBe(
|
||||
skeletonType === SKELETON_VARIANT.MANAGE,
|
||||
);
|
||||
jest.advanceTimersByTime(DEFAULT_TIMERS.CONTENT_WAIT_MS);
|
||||
await nextTick();
|
||||
const showsDefaultSkeleton = !SKELETON_VARIANTS.includes(variant);
|
||||
|
||||
expect(findDashboardsSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[0]);
|
||||
expect(findExploreSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[1]);
|
||||
expect(findManageSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[2]);
|
||||
|
||||
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(showsDefaultSkeleton);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on destroy', () => {
|
||||
it('should clear init timer and timeout timer', () => {
|
||||
jest.spyOn(global, 'clearTimeout');
|
||||
mountComponent();
|
||||
wrapper.destroy();
|
||||
expect(clearTimeout).toHaveBeenCalledTimes(2);
|
||||
expect(clearTimeout.mock.calls).toEqual([
|
||||
[wrapper.vm.loadingTimeout], // First call
|
||||
[wrapper.vm.errorTimeout], // Second call
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,18 +57,23 @@ exports[`Code Coverage when fetching data is successful matches the snapshot 1`]
|
|||
tabindex="-1"
|
||||
>
|
||||
<gl-listbox-item-stub
|
||||
data-testid="listbox-item-0"
|
||||
isselected="true"
|
||||
>
|
||||
|
||||
rspec
|
||||
|
||||
</gl-listbox-item-stub>
|
||||
<gl-listbox-item-stub>
|
||||
<gl-listbox-item-stub
|
||||
data-testid="listbox-item-1"
|
||||
>
|
||||
|
||||
cypress
|
||||
|
||||
</gl-listbox-item-stub>
|
||||
<gl-listbox-item-stub>
|
||||
<gl-listbox-item-stub
|
||||
data-testid="listbox-item-2"
|
||||
>
|
||||
|
||||
karma
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,13 @@ RSpec.describe ApplicationHelper do
|
|||
expect(timeago_element.attr('class')).to eq 'js-short-timeago'
|
||||
expect(timeago_element.next_element).to eq nil
|
||||
end
|
||||
|
||||
it 'returns blank if time is nil' do
|
||||
el = helper.time_ago_with_tooltip(nil)
|
||||
|
||||
expect(el).to eq('')
|
||||
expect(el.html_safe).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#active_when' do
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::DataBuilder::Build do
|
||||
RSpec.describe Gitlab::DataBuilder::Build, feature_category: :integrations do
|
||||
let_it_be(:runner) { create(:ci_runner, :instance, :tagged_only) }
|
||||
let_it_be(:user) { create(:user, :public_email) }
|
||||
let_it_be(:ci_build) { create(:ci_build, :running, runner: runner, user: user) }
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, name: 'Build pipeline') }
|
||||
let_it_be(:ci_build) { create(:ci_build, :running, pipeline: pipeline, runner: runner, user: user) }
|
||||
|
||||
describe '.build' do
|
||||
around do |example|
|
||||
|
|
@ -33,6 +34,7 @@ RSpec.describe Gitlab::DataBuilder::Build do
|
|||
it { expect(data[:project_name]).to eq(ci_build.project.full_name) }
|
||||
it { expect(data[:pipeline_id]).to eq(ci_build.pipeline.id) }
|
||||
it { expect(data[:retries_count]).to eq(ci_build.retries_count) }
|
||||
it { expect(data[:commit][:name]).to eq(pipeline.name) }
|
||||
|
||||
it {
|
||||
expect(data[:user]).to eq(
|
||||
|
|
@ -61,10 +63,10 @@ RSpec.describe Gitlab::DataBuilder::Build do
|
|||
described_class.build(b) # Don't use ci_build variable here since it has all associations loaded into memory
|
||||
end
|
||||
|
||||
expect(control.count).to eq(13)
|
||||
expect(control.count).to eq(14)
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
context 'when job_webhook_retries_count feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(job_webhook_retries_count: false)
|
||||
end
|
||||
|
|
@ -79,7 +81,26 @@ RSpec.describe Gitlab::DataBuilder::Build do
|
|||
described_class.build(b) # Don't use ci_build variable here since it has all associations loaded into memory
|
||||
end
|
||||
|
||||
expect(control.count).to eq(12)
|
||||
expect(control.count).to eq(13)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline_name feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(pipeline_name: false)
|
||||
|
||||
ci_build # Make sure the Ci::Build model is created before recording.
|
||||
end
|
||||
|
||||
it { expect(data[:commit]).not_to have_key(:name) }
|
||||
|
||||
it 'does not exceed number of expected queries' do
|
||||
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
|
||||
b = Ci::Build.find(ci_build.id)
|
||||
described_class.build(b) # Don't use ci_build variable here since it has all associations loaded into memory
|
||||
end
|
||||
|
||||
expect(control.count).to eq(13)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1322,6 +1322,21 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when tag is not found' do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, project: project, ref: 'not_found_tag', tag: true)
|
||||
end
|
||||
|
||||
it 'does not expose tag variables' do
|
||||
expect(subject.to_hash.keys)
|
||||
.not_to include(
|
||||
'CI_COMMIT_TAG',
|
||||
'CI_COMMIT_TAG_MESSAGE',
|
||||
'CI_BUILD_TAG'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a commit' do
|
||||
let(:pipeline) { build(:ci_empty_pipeline, :created, sha: nil) }
|
||||
|
||||
|
|
|
|||
|
|
@ -1136,10 +1136,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.14.0.tgz#b32a673f08bbd5ba6d406bcf3abb6e7276271b6c"
|
||||
integrity sha512-mQYtW9eGHY7cF6elsWd76hUF7F3NznyzrJJy5eXBHjvRdYBtyHmwkVmh1Cwr3S/2Sl8fPC+qk41a+Nm6n+1mRQ==
|
||||
|
||||
"@gitlab/ui@52.7.0":
|
||||
version "52.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-52.7.0.tgz#431502f0b23324d57fa2f683925f17eb4c550f99"
|
||||
integrity sha512-ttTCUt/amTMG9YhHJypR3FIFVSBjYDGh56izAhjMts7r216saaD8hQ8m4Yzts+fqmR42bQzUdjdThwj3Dthtsw==
|
||||
"@gitlab/ui@52.7.2":
|
||||
version "52.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-52.7.2.tgz#91d5709dbd964c3bc9af48f2a4bad6b69e37ecb0"
|
||||
integrity sha512-YGg6UoaqNJ2OG5BdJewb5ov58GHaQjjCoNuRpwKuiXu+gE90yt8brRl2jYQTthU0lxS2UtHveU22KcP6ZbZJ6w==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.11.2"
|
||||
bootstrap-vue "2.20.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue