Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-30 09:09:36 +00:00
parent 0629b10324
commit 491c773c72
40 changed files with 854 additions and 354 deletions

View file

@ -1 +1 @@
8e731456707441f9e22bfb3b668885f0f983c449
a8e416c51179f80bb9fe213c657f14d950279c75

View file

@ -133,8 +133,10 @@ gem 'seed-fu', '~> 2.3.7'
gem 'elasticsearch-model', '~> 6.1'
gem 'elasticsearch-rails', '~> 6.1', require: 'elasticsearch/rails/instrumentation'
gem 'elasticsearch-api', '~> 6.8'
gem 'aws-sdk'
gem 'faraday_middleware-aws-signers-v4'
gem 'aws-sdk-core', '~> 3'
gem 'aws-sdk-cloudformation', '~> 1'
gem 'aws-sdk-s3', '~> 1'
gem 'faraday_middleware-aws-sigv4', '~>0.3.0'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.12'

View file

@ -93,16 +93,25 @@ GEM
encryptor (~> 3.0.0)
attr_required (1.0.1)
awesome_print (1.8.0)
aws-eventstream (1.0.3)
aws-sdk (2.11.374)
aws-sdk-resources (= 2.11.374)
aws-sdk-core (2.11.374)
aws-sigv4 (~> 1.0)
aws-eventstream (1.1.0)
aws-partitions (1.345.0)
aws-sdk-cloudformation (1.41.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1)
aws-sdk-core (3.104.3)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-resources (2.11.374)
aws-sdk-core (= 2.11.374)
aws-sigv4 (1.1.0)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-sdk-kms (1.36.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.75.0)
aws-sdk-core (~> 3, >= 3.104.1)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.2)
base32 (0.3.2)
batch-loader (1.4.0)
@ -306,9 +315,9 @@ GEM
faraday (~> 0.8)
faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.7)
aws-sdk-resources (~> 2)
faraday (~> 0.9)
faraday_middleware-aws-sigv4 (0.3.0)
aws-sigv4 (~> 1.0)
faraday (>= 0.15)
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
@ -1183,7 +1192,9 @@ DEPENDENCIES
atlassian-jwt (~> 0.2.0)
attr_encrypted (~> 3.1.0)
awesome_print
aws-sdk
aws-sdk-cloudformation (~> 1)
aws-sdk-core (~> 3)
aws-sdk-s3 (~> 1)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
batch-loader (~> 1.4.0)
@ -1230,7 +1241,7 @@ DEPENDENCIES
escape_utils (~> 1.1)
factory_bot_rails (~> 5.1.0)
faraday (~> 0.12)
faraday_middleware-aws-signers-v4
faraday_middleware-aws-sigv4 (~> 0.3.0)
fast_blank
ffaker (~> 2.10)
flipper (~> 0.17.1)

View file

@ -138,7 +138,7 @@ export default {
@input="updateCommitMessage"
@submit="commit"
/>
<div class="clearfix prepend-top-15">
<div class="clearfix gl-mt-5">
<actions />
<loading-button
:loading="submitCommitLoading"

View file

@ -0,0 +1,92 @@
<script>
import { mapActions, mapState } from 'vuex';
import { GlCard, GlForm, GlFormGroup, GlFormTextarea, GlButton, GlAlert } from '@gitlab/ui';
import DashboardPanel from './dashboard_panel.vue';
const initialYml = `title:
y_label:
type: area-chart
metrics:
- query_range:
label:
`;
export default {
components: {
GlCard,
GlForm,
GlFormGroup,
GlFormTextarea,
GlButton,
GlAlert,
DashboardPanel,
},
data() {
return {
yml: initialYml,
};
},
computed: {
...mapState('monitoringDashboard', [
'panelPreviewIsLoading',
'panelPreviewError',
'panelPreviewGraphData',
]),
},
methods: {
...mapActions('monitoringDashboard', ['fetchPanelPreview']),
onSubmit() {
this.fetchPanelPreview(this.yml);
},
},
};
</script>
<template>
<div>
<gl-card>
<template #header>
<h2 class="gl-font-size-h2 gl-my-3">{{ s__('Metrics|Define and preview panel') }}</h2>
</template>
<template #default>
<gl-form @submit.prevent="onSubmit">
<gl-form-group
:label="s__('Metrics|Panel YAML')"
:description="s__('Metrics|Define panel YAML to preview panel.')"
label-for="panel-yml-input"
>
<gl-form-textarea
id="panel-yml-input"
v-model="yml"
class="gl-h-200! gl-font-monospace! gl-font-size-monospace!"
/>
</gl-form-group>
<div class="gl-text-right">
<gl-button
ref="clipboardCopyBtn"
variant="success"
category="secondary"
:data-clipboard-text="yml"
@click="$toast.show(s__('Metrics|Panel YAML copied'))"
>
{{ s__('Metrics|Copy YAML') }}
</gl-button>
<gl-button
type="submit"
variant="success"
:disabled="panelPreviewIsLoading"
class="js-no-auto-disable"
>
{{ s__('Metrics|Preview panel') }}
</gl-button>
</div>
</gl-form>
</template>
</gl-card>
<gl-alert v-if="panelPreviewError" variant="warning" :dismissible="false">
{{ panelPreviewError }}
</gl-alert>
<dashboard-panel :graph-data="panelPreviewGraphData" />
</div>
</template>

View file

@ -1,33 +1,45 @@
<script>
import { mapState } from 'vuex';
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import routes from '../router/constants';
import { DASHBOARD_PAGE } from '../router/constants';
import DashboardPanelBuilder from '../components/dashboard_panel_builder.vue';
export default {
components: {
GlButton,
DashboardPanelBuilder,
},
directives: {
GlTooltip: GlTooltipDirective,
},
computed: {
...mapState('monitoringDashboard', ['panelPreviewYml']),
dashboardPageLocation() {
return {
...this.$route,
name: DASHBOARD_PAGE,
};
},
},
i18n: {
backToDashboard: s__('Metrics|Back to dashboard'),
},
routes,
};
</script>
<template>
<div class="gl-display-flex gl-align-items-baseline">
<gl-button
v-gl-tooltip
icon="go-back"
:to="{ name: $options.routes.DASHBOARD_PAGE, params: { dashboard: $route.params.dashboard } }"
:aria-label="$options.i18n.backToDashboard"
:title="$options.i18n.backToDashboard"
class="gl-mr-5"
/>
<h1 class="gl-mt-5 gl-font-size-h1">{{ s__('Metrics|Add panel') }}</h1>
<!-- TODO: Add components. See https://gitlab.com/groups/gitlab-org/-/epics/2882 -->
<div class="gl-mt-5">
<div class="gl-display-flex gl-align-items-baseline gl-mb-5">
<gl-button
v-gl-tooltip
icon="go-back"
:to="dashboardPageLocation"
:aria-label="$options.i18n.backToDashboard"
:title="$options.i18n.backToDashboard"
class="gl-mr-5"
/>
<h1 class="gl-font-size-h1 gl-my-0">{{ s__('Metrics|Add panel') }}</h1>
</div>
<dashboard-panel-builder />
</div>
</template>

View file

@ -41,3 +41,12 @@ export const getPrometheusQueryData = (prometheusEndpoint, params) =>
}
throw error;
});
// eslint-disable-next-line no-unused-vars
export function getPanelJson(panelPreviewEndpoint, panelPreviewYml) {
// TODO Use a real backend when it's available
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
// eslint-disable-next-line @gitlab/require-i18n-strings
return Promise.reject(new Error('API Not implemented.'));
}

View file

@ -15,7 +15,7 @@ import getAnnotations from '../queries/getAnnotations.query.graphql';
import getDashboardValidationWarnings from '../queries/getDashboardValidationWarnings.query.graphql';
import { convertObjectPropsToCamelCase } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale';
import { getDashboard, getPrometheusQueryData } from '../requests';
import { getDashboard, getPrometheusQueryData, getPanelJson } from '../requests';
import { ENVIRONMENT_AVAILABLE_STATE, DEFAULT_DASHBOARD_PATH, VARIABLE_TYPES } from '../constants';
@ -473,3 +473,30 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery
return Promise.all(optionsRequests);
};
// Panel Builder
export const fetchPanelPreview = ({ state, commit, dispatch }, panelPreviewYml) => {
if (!panelPreviewYml) {
return null;
}
commit(types.REQUEST_PANEL_PREVIEW, panelPreviewYml);
return getPanelJson(state.panelPreviewEndpoint, panelPreviewYml)
.then(data => {
commit(types.RECEIVE_PANEL_PREVIEW_SUCCESS, data);
dispatch('fetchPanelPreviewMetrics');
})
.catch(error => {
commit(types.RECEIVE_PANEL_PREVIEW_FAILURE, error);
});
};
export const fetchPanelPreviewMetrics = () => {
// TODO Use a axios mock instead of spy when backend is implemented
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
// eslint-disable-next-line @gitlab/require-i18n-strings
throw new Error('Not implemented');
};

View file

@ -46,3 +46,8 @@ export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER';
export const SET_EXPANDED_PANEL = 'SET_EXPANDED_PANEL';
// Panel preview
export const REQUEST_PANEL_PREVIEW = 'REQUEST_PANEL_PREVIEW';
export const RECEIVE_PANEL_PREVIEW_SUCCESS = 'RECEIVE_PANEL_PREVIEW_SUCCESS';
export const RECEIVE_PANEL_PREVIEW_FAILURE = 'RECEIVE_PANEL_PREVIEW_FAILURE';

View file

@ -1,9 +1,9 @@
import Vue from 'vue';
import { pick } from 'lodash';
import * as types from './mutation_types';
import { mapToDashboardViewModel, normalizeQueryResponseData } from './utils';
import { mapToDashboardViewModel, mapPanelToViewModel, normalizeQueryResponseData } from './utils';
import httpStatusCodes from '~/lib/utils/http_status';
import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
import { BACKOFF_TIMEOUT } from '~/lib/utils/common_utils';
import { dashboardEmptyStates, endpointKeys, initialStateKeys, metricStates } from '../constants';
import { optionsFromSeriesData } from './variable_mapping';
@ -218,4 +218,24 @@ export default {
// Add new options with assign to ensure Vue reactivity
Object.assign(variable.options, { values });
},
[types.REQUEST_PANEL_PREVIEW](state, panelPreviewYml) {
state.panelPreviewIsLoading = true;
state.panelPreviewYml = panelPreviewYml;
state.panelPreviewGraphData = null;
state.panelPreviewError = null;
},
[types.RECEIVE_PANEL_PREVIEW_SUCCESS](state, payload) {
state.panelPreviewIsLoading = false;
state.panelPreviewGraphData = mapPanelToViewModel(payload);
state.panelPreviewError = null;
},
[types.RECEIVE_PANEL_PREVIEW_FAILURE](state, error) {
state.panelPreviewIsLoading = false;
state.panelPreviewGraphData = null;
state.panelPreviewError = error;
},
};

View file

@ -59,6 +59,13 @@ export default () => ({
* via the dashboard yml file.
*/
links: [],
// Panel editor / builder
panelPreviewYml: '',
panelPreviewIsLoading: false,
panelPreviewGraphData: null,
panelPreviewError: null,
// Other project data
dashboardTimezone: timezones.LOCAL,
annotations: [],

View file

@ -3,6 +3,7 @@ import ReplyPlaceholder from './discussion_reply_placeholder.vue';
import ResolveDiscussionButton from './discussion_resolve_button.vue';
import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
import JumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'DiscussionActions',
@ -12,6 +13,7 @@ export default {
ResolveWithIssueButton,
JumpToNextDiscussionButton,
},
mixins: [glFeatureFlagsMixin()],
props: {
discussion: {
type: Object,
@ -36,6 +38,9 @@ export default {
},
},
computed: {
hideJumpToNextUnresolvedInThreads() {
return this.glFeatures.hideJumpToNextUnresolvedInThreads;
},
resolvableNotes() {
return this.discussion.notes.filter(x => x.resolvable);
},
@ -70,7 +75,11 @@ export default {
/>
</div>
<div
v-if="discussion.resolvable && shouldShowJumpToNextDiscussion"
v-if="
!hideJumpToNextUnresolvedInThreads &&
discussion.resolvable &&
shouldShowJumpToNextDiscussion
"
class="btn-group discussion-actions ml-sm-2"
>
<jump-to-next-discussion-button :from-discussion-id="discussion.id" />

View file

@ -2,7 +2,6 @@
import {
GlBadge,
GlButton,
GlIcon,
GlModal,
GlModalDirective,
GlTooltipDirective,
@ -27,6 +26,7 @@ import PackageListRow from '../../shared/components/package_list_row.vue';
import DependencyRow from './dependency_row.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { generatePackageInfo } from '../utils';
import { __, s__ } from '~/locale';
import { PackageType, TrackingActions } from '../../shared/constants';
@ -44,7 +44,7 @@ export default {
GlTab,
GlTabs,
GlTable,
GlIcon,
FileIcon,
GlSprintf,
PackageActivity,
PackageInformation,
@ -243,19 +243,24 @@ export default {
<package-activity />
<h3 class="gl-font-lg">{{ __('Files') }}</h3>
<gl-table
:fields="$options.filesTableHeaderFields"
:items="filesTableRows"
tbody-tr-class="js-file-row"
>
<template #cell(name)="items">
<gl-icon name="doc-code" class="space-right" />
<gl-link
:href="items.item.downloadPath"
class="js-file-download"
class="js-file-download gl-relative"
@click="track($options.trackingActions.PULL_PACKAGE)"
>
{{ items.item.name }}
<file-icon
:file-name="items.item.name"
css-classes="gl-relative file-icon"
class="gl-mr-1 gl-relative"
/>
<span class="gl-relative">{{ items.item.name }}</span>
</gl-link>
</template>

View file

@ -260,10 +260,6 @@
content: '\f0a3';
}
.fa-minus-circle::before {
content: '\f056';
}
.fa-bitbucket::before {
content: '\f171';
}

View file

@ -86,7 +86,6 @@
height: $input-height;
padding: 0;
background: transparent;
border: 0;
color: $gl-text-color-secondary;
&:hover,

View file

@ -82,6 +82,10 @@
.gl-h-32 { height: px-to-rem($grid-size * 4); }
.gl-h-64 { height: px-to-rem($grid-size * 8); }
// Migrate this to Gitlab UI when FF is removed
// https://gitlab.com/groups/gitlab-org/-/epics/2882
.gl-h-200\! { height: px-to-rem($grid-size * 25) !important; }
.d-sm-table-column {
@include media-breakpoint-up(sm) {
display: table-column !important;

View file

@ -10,6 +10,7 @@ class Service < ApplicationRecord
include IgnorableColumns
ignore_columns %i[title description], remove_with: '13.4', remove_after: '2020-09-22'
ignore_columns %i[default], remove_with: '13.5', remove_after: '2020-10-22'
SERVICE_NAMES = %w[
alerts asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker discord

View file

@ -96,7 +96,7 @@ module Admin
# rubocop: disable CodeReuse/ActiveRecord
def run_callbacks(batch)
if active_external_issue_tracker?
if integration.issue_tracker?
Project.where(id: batch).update_all(has_external_issue_tracker: true)
end
@ -106,10 +106,6 @@ module Admin
end
# rubocop: enable CodeReuse/ActiveRecord
def active_external_issue_tracker?
integration.issue_tracker? && !integration.default
end
def active_external_wiki?
integration.type == 'ExternalWikiService'
end

View file

@ -66,7 +66,7 @@ module Projects
# rubocop: disable CodeReuse/ActiveRecord
def run_callbacks(batch)
if active_external_issue_tracker?
if template.issue_tracker?
Project.where(id: batch).update_all(has_external_issue_tracker: true)
end
@ -76,10 +76,6 @@ module Projects
end
# rubocop: enable CodeReuse/ActiveRecord
def active_external_issue_tracker?
template.issue_tracker? && !template.default
end
def active_external_wiki?
template.type == 'ExternalWikiService'
end

View file

@ -24,5 +24,5 @@
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
= value
%button.js-row-remove-button.ci-variable-row-remove-button.table-section.section-5.border-top-0{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= icon('minus-circle')
%button.btn.btn-svg.btn-item-remove.js-row-remove-button.ci-variable-row-remove-button.table-section{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= sprite_icon('close')

View file

@ -60,5 +60,5 @@
value: is_masked,
data: { default: is_masked_default.to_s } }
= render_if_exists 'ci/variables/environment_scope', form_field: form_field, variable: variable
%button.js-row-remove-button.ci-variable-row-remove-button.table-section.section-5.border-top-0{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= icon('minus-circle')
%button.btn.btn-svg.js-row-remove-button.ci-variable-row-remove-button.table-section{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= sprite_icon('close')

View file

@ -1,6 +1,6 @@
- hide_remember_me = local_assigns.fetch(:hide_remember_me, false)
.omniauth-container.prepend-top-15
.omniauth-container.gl-mt-5
%label.label-bold.d-block
Sign in with
- providers = enabled_button_based_providers

View file

@ -12,4 +12,4 @@
.form-text.text-muted
= s_('GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
= f.submit _('Save changes'), class: 'btn btn-success prepend-top-15'
= f.submit _('Save changes'), class: 'btn btn-success gl-mt-5'

View file

@ -54,4 +54,4 @@
= s_('CICD|Automatic deployment to staging, manual deployment to production')
= link_to icon('question-circle'), help_page_path('topics/autodevops/customize.md', anchor: 'incremental-rollout-to-production-premium'), target: '_blank'
= f.submit _('Save changes'), class: "btn btn-success prepend-top-15", data: { qa_selector: 'save_changes_button' }
= f.submit _('Save changes'), class: "btn btn-success gl-mt-5", data: { qa_selector: 'save_changes_button' }

View file

@ -0,0 +1,5 @@
---
title: Updates to file table in package details UI
merge_request: 36723
author: Adam Alvis (@adamalvis)
type: fixed

View file

@ -156,7 +156,7 @@ for example, without needing to explicitly pass an access token.
With a few API endpoints you can use a [GitLab CI/CD job token](../user/project/new_ci_build_permissions_model.md#job-token)
to authenticate with the API:
- [Get job artifacts](jobs.md#get-job-artifacts)
- [Get job artifacts](job_artifacts.md#get-job-artifacts)
- [Pipeline triggers](pipeline_triggers.md)
- [Release creation](releases/index.md#create-a-release)

265
doc/api/job_artifacts.md Normal file
View file

@ -0,0 +1,265 @@
# Job Artifacts API
## Get job artifacts
> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2346) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.5.
Get the job's artifacts zipped archive of a project.
```plaintext
GET /projects/:id/jobs/:job_id/artifacts
```
| Attribute | Type | Required | Description |
|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline-premium) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
Example request using the `PRIVATE-TOKEN` header:
```shell
curl --output artifacts.zip --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"
```
To use this in a [`script` definition](../ci/yaml/README.md#script) inside
`.gitlab-ci.yml` **(PREMIUM)**, you can use either:
- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the job with ID
`42`. Note that the command is wrapped into single quotes since it contains a
colon (`:`):
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"'
```
- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the job with ID `42`:
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts?job_token=$CI_JOB_TOKEN"'
```
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
| 200 | Serves the artifacts file. |
| 404 | Build not found or no artifacts.|
## Download the artifacts archive
> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2346) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.5.
Download the artifacts zipped archive from the latest successful pipeline for
the given reference name and job, provided the job finished successfully. This
is the same as [getting the job's artifacts](#get-job-artifacts), but by
defining the job's name instead of its ID.
```plaintext
GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
```
Parameters
| Attribute | Type | Required | Description |
|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
| `job` | string | yes | The name of the job. |
| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline-premium) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
Example request using the `PRIVATE-TOKEN` header:
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
To use this in a [`script` definition](../ci/yaml/README.md#script) inside
`.gitlab-ci.yml` **(PREMIUM)**, you can use either:
- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the `test` job
of the `master` branch. Note that the command is wrapped into single quotes
since it contains a colon (`:`):
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test"'
```
- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the `test` job
of the `master` branch:
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"'
```
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
| 200 | Serves the artifacts file. |
| 404 | Build not found or no artifacts.|
## Download a single artifact file by job ID
> Introduced in GitLab 10.0
Download a single artifact file from a job with a specified ID from within
the job's artifacts zipped archive. The file is extracted from the archive and
streamed to the client.
```plaintext
GET /projects/:id/jobs/:job_id/artifacts/*artifact_path
```
Parameters
| Attribute | Type | Required | Description |
|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | The unique job identifier. |
| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
Example request:
```shell
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf"
```
Possible response status codes:
| Status | Description |
|-----------|--------------------------------------|
| 200 | Sends a single artifact file |
| 400 | Invalid path provided |
| 404 | Build not found or no file/artifacts |
## Download a single artifact file from specific tag or branch
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23538) in GitLab 11.5.
Download a single artifact file for a specific job of the latest successful
pipeline for the given reference name from within the job's artifacts archive.
The file is extracted from the archive and streamed to the client.
```plaintext
GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name
```
Parameters:
| Attribute | Type | Required | Description |
|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
| `job` | string | yes | The name of the job. |
Example request:
```shell
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
```
Possible response status codes:
| Status | Description |
|-----------|--------------------------------------|
| 200 | Sends a single artifact file |
| 400 | Invalid path provided |
| 404 | Build not found or no file/artifacts |
## Keep artifacts
Prevents artifacts from being deleted when expiration is set.
```plaintext
POST /projects/:id/jobs/:job_id/artifacts/keep
```
Parameters
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
Example request:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts/keep"
```
Example response:
```json
{
"commit": {
"author_email": "admin@example.com",
"author_name": "Administrator",
"created_at": "2015-12-24T16:51:14.000+01:00",
"id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
"message": "Test the CI integration.",
"short_id": "0ff3ae19",
"title": "Test the CI integration."
},
"coverage": null,
"allow_failure": false,
"download_url": null,
"id": 42,
"name": "rubocop",
"ref": "master",
"artifacts": [],
"runner": null,
"stage": "test",
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": "2016-01-11T10:13:33.506Z",
"finished_at": "2016-01-11T10:15:10.506Z",
"duration": 97.0,
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null
}
```
## Delete artifacts
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25522) in GitLab 11.9.
Delete artifacts of a job.
```plaintext
DELETE /projects/:id/jobs/:job_id/artifacts
```
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `job_id` | integer | yes | ID of a job. |
Example request:
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts"
```
NOTE: **Note:**
At least Maintainer role is required to delete artifacts.
If the artifacts were deleted successfully, a response with status `204 No Content` is returned.

View file

@ -428,198 +428,6 @@ Example of response
}
```
## Get job artifacts
> **Notes**:
>
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2893) in GitLab 8.5.
> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2346)
> in [GitLab Premium](https://about.gitlab.com/pricing/) 9.5.
Get the job's artifacts zipped archive of a project.
```plaintext
GET /projects/:id/jobs/:job_id/artifacts
```
| Attribute | Type | Required | Description |
|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline-premium) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
Example request using the `PRIVATE-TOKEN` header:
```shell
curl --output artifacts.zip --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"
```
To use this in a [`script` definition](../ci/yaml/README.md#script) inside
`.gitlab-ci.yml` **(PREMIUM)**, you can use either:
- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the job with ID
`42`. Note that the command is wrapped into single quotes since it contains a
colon (`:`):
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts"'
```
- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the job with ID `42`:
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/42/artifacts?job_token=$CI_JOB_TOKEN"'
```
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
| 200 | Serves the artifacts file. |
| 404 | Build not found or no artifacts.|
## Download the artifacts archive
> **Notes**:
>
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5347) in GitLab 8.10.
> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/2346)
> in [GitLab Premium](https://about.gitlab.com/pricing/) 9.5.
Download the artifacts zipped archive from the latest successful pipeline for
the given reference name and job, provided the job finished successfully. This
is the same as [getting the job's artifacts](#get-job-artifacts), but by
defining the job's name instead of its ID.
```plaintext
GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
```
Parameters
| Attribute | Type | Required | Description |
|-------------|----------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
| `job` | string | yes | The name of the job. |
| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/triggers/README.md#when-a-pipeline-depends-on-the-artifacts-of-another-pipeline-premium) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. |
Example request using the `PRIVATE-TOKEN` header:
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
To use this in a [`script` definition](../ci/yaml/README.md#script) inside
`.gitlab-ci.yml` **(PREMIUM)**, you can use either:
- The `JOB-TOKEN` header with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the `test` job
of the `master` branch. Note that the command is wrapped into single quotes
since it contains a colon (`:`):
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip --header "JOB-TOKEN: $CI_JOB_TOKEN" "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test"'
```
- Or the `job_token` attribute with the GitLab-provided `CI_JOB_TOKEN` variable.
For example, the following job will download the artifacts of the `test` job
of the `master` branch:
```yaml
artifact_download:
stage: test
script:
- 'curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/$CI_PROJECT_ID/jobs/artifacts/master/download?job=test&job_token=$CI_JOB_TOKEN"'
```
Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
| 200 | Serves the artifacts file. |
| 404 | Build not found or no artifacts.|
## Download a single artifact file by job ID
> Introduced in GitLab 10.0
Download a single artifact file from a job with a specified ID from within
the job's artifacts zipped archive. The file is extracted from the archive and
streamed to the client.
```plaintext
GET /projects/:id/jobs/:job_id/artifacts/*artifact_path
```
Parameters
| Attribute | Type | Required | Description |
|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | The unique job identifier. |
| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
Example request:
```shell
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf"
```
Possible response status codes:
| Status | Description |
|-----------|--------------------------------------|
| 200 | Sends a single artifact file |
| 400 | Invalid path provided |
| 404 | Build not found or no file/artifacts |
## Download a single artifact file from specific tag or branch
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23538) in GitLab 11.5.
Download a single artifact file for a specific job of the latest successful
pipeline for the given reference name from within the job's artifacts archive.
The file is extracted from the archive and streamed to the client.
```plaintext
GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name
```
Parameters:
| Attribute | Type | Required | Description |
|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
| `job` | string | yes | The name of the job. |
Example request:
```shell
curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
```
Possible response status codes:
| Status | Description |
|-----------|--------------------------------------|
| 200 | Sends a single artifact file |
| 400 | Invalid path provided |
| 404 | Build not found or no file/artifacts |
## Get a log file
Get a log (trace) of a specific job of a project:
@ -796,86 +604,6 @@ Example of response
}
```
## Keep artifacts
Prevents artifacts from being deleted when expiration is set.
```plaintext
POST /projects/:id/jobs/:job_id/artifacts/keep
```
Parameters
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `job_id` | integer | yes | ID of a job. |
Example request:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts/keep"
```
Example response:
```json
{
"commit": {
"author_email": "admin@example.com",
"author_name": "Administrator",
"created_at": "2015-12-24T16:51:14.000+01:00",
"id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
"message": "Test the CI integration.",
"short_id": "0ff3ae19",
"title": "Test the CI integration."
},
"coverage": null,
"allow_failure": false,
"download_url": null,
"id": 42,
"name": "rubocop",
"ref": "master",
"artifacts": [],
"runner": null,
"stage": "test",
"created_at": "2016-01-11T10:13:33.506Z",
"started_at": "2016-01-11T10:13:33.506Z",
"finished_at": "2016-01-11T10:15:10.506Z",
"duration": 97.0,
"status": "failed",
"tag": false,
"web_url": "https://example.com/foo/bar/-/jobs/42",
"user": null
}
```
## Delete artifacts
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25522) in GitLab 11.9.
Delete artifacts of a job.
```plaintext
DELETE /projects/:id/jobs/:job_id/artifacts
```
| Attribute | Type | Required | Description |
|-----------|----------------|----------|------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `job_id` | integer | yes | ID of a job. |
Example request:
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts"
```
NOTE: **Note:**
At least Maintainer role is required to delete artifacts.
If the artifacts were deleted successfully, a response with status `204 No Content` is returned.
## Play a job
Triggers a manual action to start a job.

View file

@ -17,7 +17,7 @@ Job artifacts are a list of files and directories created by a job
once it finishes. This feature is [enabled by default](../../administration/job_artifacts.md) in all
GitLab installations.
Job artifacts created by GitLab Runner are uploaded to GitLab and are downloadable as a single archive using the GitLab UI or the [GitLab API](../../api/jobs.md#get-job-artifacts).
Job artifacts created by GitLab Runner are uploaded to GitLab and are downloadable as a single archive using the GitLab UI or the [GitLab API](../../api/job_artifacts.md#get-job-artifacts).
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, watch the video [GitLab CI Pipeline, Artifacts, and Environments](https://www.youtube.com/watch?v=PCKDICEe10s).
@ -439,7 +439,7 @@ To erase a job:
## Retrieve artifacts of private projects when using GitLab CI
In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../api/jobs.md#get-job-artifacts) the artifacts.
In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../api/job_artifacts.md#get-job-artifacts) the artifacts.
<!-- ## Troubleshooting

View file

@ -69,11 +69,15 @@ blog about it](https://about.gitlab.com/blog/2015/05/06/why-were-replacing-gitla
### Creating a simple `.gitlab-ci.yml` file
NOTE: **Note:**
`.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
so you have to pay extra attention to indentation. Always use spaces, not tabs.
A GitLab team member has made an [unofficial visual pipeline editor](https://unofficial.gitlab.tools/visual-pipelines/).
There is a [plan to make it an official part of GitLab](https://gitlab.com/groups/gitlab-org/-/epics/4069)
in the future, but it's available for anyone who wants to try it at the above link.
You need to create a file named `.gitlab-ci.yml` in the root directory of your
repository. Below is an example for a Ruby on Rails project.
repository. This is a [YAML](https://en.wikipedia.org/wiki/YAML) file
so you have to pay extra attention to indentation. Always use spaces, not tabs.
Below is an example for a Ruby on Rails project:
```yaml
image: "ruby:2.5"

View file

@ -97,7 +97,7 @@ This allows you to use that for multi-project pipelines and download artifacts
from any project to which you have access as this follows the same principles
with the [permission model](../../user/permissions.md#job-permissions).
Read more about the [jobs API](../../api/jobs.md#download-the-artifacts-archive).
Read more about the [jobs API](../../api/job_artifacts.md#download-the-artifacts-archive).
## Adding a new trigger

View file

@ -1365,7 +1365,7 @@ check the value of `$CI_COMMIT_BEFORE_SHA`. It has a value of
`0000000000000000000000000000000000000000`:
- In branches with no commits.
- Tag pipelines and scheduled pipelines. You should define rules very
- In tag pipelines and scheduled pipelines. You should define rules very
narrowly if you don't want to skip these.
To skip pipelines on all empty branches, but also tags and schedules:

View file

@ -14999,6 +14999,9 @@ msgstr ""
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr ""
msgid "Metrics|Copy YAML"
msgstr ""
msgid "Metrics|Create custom dashboard %{fileName}"
msgstr ""
@ -15017,6 +15020,12 @@ msgstr ""
msgid "Metrics|Current"
msgstr ""
msgid "Metrics|Define and preview panel"
msgstr ""
msgid "Metrics|Define panel YAML to preview panel."
msgstr ""
msgid "Metrics|Delete metric"
msgstr ""
@ -15085,6 +15094,15 @@ msgstr ""
msgid "Metrics|Open repository"
msgstr ""
msgid "Metrics|Panel YAML"
msgstr ""
msgid "Metrics|Panel YAML copied"
msgstr ""
msgid "Metrics|Preview panel"
msgstr ""
msgid "Metrics|PromQL query is valid"
msgstr ""

View file

@ -0,0 +1,146 @@
import { shallowMount } from '@vue/test-utils';
import { GlCard, GlForm, GlFormTextarea, GlAlert } from '@gitlab/ui';
import { createStore } from '~/monitoring/stores';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import * as types from '~/monitoring/stores/mutation_types';
import { metricsDashboardResponse } from '../fixture_data';
import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
const mockPanel = metricsDashboardResponse.dashboard.panel_groups[0].panels[0];
describe('dashboard invalid url parameters', () => {
let store;
let wrapper;
let mockShowToast;
const createComponent = (props = {}, options = {}) => {
wrapper = shallowMount(DashboardPanelBuilder, {
propsData: { ...props },
store,
stubs: {
GlCard,
},
mocks: {
$toast: {
show: mockShowToast,
},
},
options,
});
};
const findForm = () => wrapper.find(GlForm);
const findTxtArea = () => findForm().find(GlFormTextarea);
const findSubmitBtn = () => findForm().find('[type="submit"]');
const findClipboardCopyBtn = () => wrapper.find({ ref: 'clipboardCopyBtn' });
const findPanel = () => wrapper.find(DashboardPanel);
beforeEach(() => {
mockShowToast = jest.fn();
store = createStore();
createComponent();
jest.spyOn(store, 'dispatch').mockResolvedValue();
});
afterEach(() => {});
it('is mounted', () => {
expect(wrapper.exists()).toBe(true);
});
it('displays an empty dashboard panel', () => {
expect(findPanel().exists()).toBe(true);
expect(findPanel().props('graphData')).toBe(null);
});
it('does not fetch initial data by default', () => {
expect(store.dispatch).not.toHaveBeenCalled();
});
describe('yml form', () => {
it('form exists and can be submitted', () => {
expect(findForm().exists()).toBe(true);
expect(findSubmitBtn().exists()).toBe(true);
expect(findSubmitBtn().is('[disabled]')).toBe(false);
});
it('form has a text area with a default value', () => {
expect(findTxtArea().exists()).toBe(true);
const value = findTxtArea().attributes('value');
// Panel definition should contain a title and a type
expect(value).toContain('title:');
expect(value).toContain('type:');
});
it('"copy to clipboard" button works', () => {
findClipboardCopyBtn().vm.$emit('click');
const clipboardText = findClipboardCopyBtn().attributes('data-clipboard-text');
expect(clipboardText).toContain('title:');
expect(clipboardText).toContain('type:');
expect(mockShowToast).toHaveBeenCalledTimes(1);
});
it('on submit fetches a panel preview', () => {
findForm().vm.$emit('submit', new Event('submit'));
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/fetchPanelPreview',
expect.stringContaining('title:'),
);
});
});
describe('when form is submitted', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.REQUEST_PANEL_PREVIEW}`, 'mock yml content');
return wrapper.vm.$nextTick();
});
it('submit button is disabled', () => {
expect(findSubmitBtn().is('[disabled]')).toBe(true);
});
});
});
describe('when there is an error', () => {
const mockError = 'an error ocurred!';
beforeEach(() => {
store.commit(`monitoringDashboard/${types.RECEIVE_PANEL_PREVIEW_FAILURE}`, mockError);
return wrapper.vm.$nextTick();
});
it('displays an alert', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true);
expect(wrapper.find(GlAlert).text()).toBe(mockError);
});
it('displays an empty dashboard panel', () => {
expect(findPanel().props('graphData')).toBe(null);
});
});
describe('when panel data is available', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.RECEIVE_PANEL_PREVIEW_SUCCESS}`, mockPanel);
return wrapper.vm.$nextTick();
});
it('displays no alert', () => {
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
it('displays panel with data', () => {
const { title, type } = wrapper.find(DashboardPanel).props('graphData');
expect(title).toBe(mockPanel.title);
expect(type).toBe(mockPanel.type);
});
});
});

View file

@ -1,6 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import { DASHBOARD_PAGE } from '~/monitoring/router/constants';
import { DASHBOARD_PAGE, PANEL_NEW_PAGE } from '~/monitoring/router/constants';
import { createStore } from '~/monitoring/stores';
import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
import PanelNewPage from '~/monitoring/pages/panel_new_page.vue';
const dashboard = 'dashboard.yml';
@ -15,26 +18,37 @@ const GlButtonStub = {
};
describe('monitoring/pages/panel_new_page', () => {
let store;
let wrapper;
let $route;
let $router;
const mountComponent = (propsData = {}, routeParams = { dashboard }) => {
$route = {
params: routeParams,
const mountComponent = (propsData = {}, route) => {
$route = route ?? { name: PANEL_NEW_PAGE, params: { dashboard } };
$router = {
push: jest.fn(),
};
wrapper = shallowMount(PanelNewPage, {
propsData,
store,
stubs: {
GlButton: GlButtonStub,
},
mocks: {
$router,
$route,
},
});
};
const findBackButton = () => wrapper.find(GlButtonStub);
const findPanelBuilder = () => wrapper.find(DashboardPanelBuilder);
beforeEach(() => {
store = createStore();
mountComponent();
});
afterEach(() => {
wrapper.destroy();
@ -42,18 +56,43 @@ describe('monitoring/pages/panel_new_page', () => {
describe('back to dashboard button', () => {
it('is rendered', () => {
mountComponent();
expect(findBackButton().exists()).toBe(true);
expect(findBackButton().props('icon')).toBe('go-back');
});
it('links back to the dashboard', () => {
const dashboardLocation = {
expect(findBackButton().props('to')).toEqual({
name: DASHBOARD_PAGE,
params: { dashboard },
});
});
it('links back to the dashboard while preserving query params', () => {
$route = {
name: PANEL_NEW_PAGE,
params: { dashboard },
query: { another: 'param' },
};
expect(findBackButton().props('to')).toEqual(dashboardLocation);
mountComponent({}, $route);
expect(findBackButton().props('to')).toEqual({
name: DASHBOARD_PAGE,
params: { dashboard },
query: { another: 'param' },
});
});
});
describe('dashboard panel builder', () => {
it('is rendered', () => {
expect(findPanelBuilder().exists()).toBe(true);
});
});
describe('page routing', () => {
it('route is not updated by default', () => {
expect($router.push).not.toHaveBeenCalled();
});
});
});

View file

@ -9,6 +9,7 @@ import { defaultTimeRange } from '~/vue_shared/constants';
import * as getters from '~/monitoring/stores/getters';
import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
import { backoffMockImplementation } from 'jest/helpers/backoff_helper';
import * as requests from '~/monitoring/requests';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
@ -31,6 +32,7 @@ import {
duplicateSystemDashboard,
updateVariablesAndFetchData,
fetchVariableMetricLabelValues,
fetchPanelPreview,
} from '~/monitoring/stores/actions';
import {
gqClient,
@ -1154,4 +1156,56 @@ describe('Monitoring store actions', () => {
);
});
});
describe('fetchPanelPreview', () => {
const mockYmlContent = 'mock yml content';
it('should not commit or dispatch if payload is empty', () => {
testAction(fetchPanelPreview, '', state, [], []);
});
it('should store the yml content and panel in the store and fetch corresponding metrics', () => {
const mockPanel = {
title: 'title',
type: 'area-chart',
};
// TODO Use a axios mock instead of spy when backend is implemented
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
jest.spyOn(requests, 'getPanelJson').mockResolvedValue(mockPanel);
testAction(
fetchPanelPreview,
'mock yml content',
state,
[
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
{ type: types.RECEIVE_PANEL_PREVIEW_SUCCESS, payload: mockPanel },
],
[
{
type: 'fetchPanelPreviewMetrics',
},
],
);
});
it('should commit a failure when backend fails', () => {
const mockError = 'error';
// TODO Use a axios mock instead of spy when backend is implemented
// https://gitlab.com/gitlab-org/gitlab/-/issues/228758
jest.spyOn(requests, 'getPanelJson').mockRejectedValue(mockError);
testAction(
fetchPanelPreview,
mockYmlContent,
state,
[
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
{ type: types.RECEIVE_PANEL_PREVIEW_FAILURE, payload: mockError },
],
[],
);
});
});
});

View file

@ -488,4 +488,42 @@ describe('Monitoring mutations', () => {
});
});
});
describe('REQUEST_PANEL_PREVIEW', () => {
it('saves yml content and resets other preview data', () => {
const mockYmlContent = 'mock yml content';
mutations[types.REQUEST_PANEL_PREVIEW](stateCopy, mockYmlContent);
expect(stateCopy.panelPreviewIsLoading).toBe(true);
expect(stateCopy.panelPreviewYml).toBe(mockYmlContent);
expect(stateCopy.panelPreviewGraphData).toBe(null);
expect(stateCopy.panelPreviewError).toBe(null);
});
});
describe('RECEIVE_PANEL_PREVIEW_SUCCESS', () => {
it('saves graph data', () => {
mutations[types.RECEIVE_PANEL_PREVIEW_SUCCESS](stateCopy, {
title: 'My Title',
type: 'area-chart',
});
expect(stateCopy.panelPreviewIsLoading).toBe(false);
expect(stateCopy.panelPreviewGraphData).toMatchObject({
title: 'My Title',
type: 'area-chart',
});
expect(stateCopy.panelPreviewError).toBe(null);
});
});
describe('RECEIVE_PANEL_PREVIEW_FAILURE', () => {
it('saves graph data', () => {
mutations[types.RECEIVE_PANEL_PREVIEW_FAILURE](stateCopy, 'Error!');
expect(stateCopy.panelPreviewIsLoading).toBe(false);
expect(stateCopy.panelPreviewGraphData).toBe(null);
expect(stateCopy.panelPreviewError).toBe('Error!');
});
});
});

View file

@ -21,7 +21,7 @@ const createUnallowedNote = () =>
describe('DiscussionActions', () => {
let wrapper;
const createComponentFactory = (shallow = true) => props => {
const createComponentFactory = (shallow = true) => (props, options) => {
const store = createStore();
const mountFn = shallow ? shallowMount : mount;
@ -35,6 +35,11 @@ describe('DiscussionActions', () => {
shouldShowJumpToNextDiscussion: true,
...props,
},
provide: {
glFeatures: {
hideJumpToNextUnresolvedInThreads: options?.hideJumpToNextUnresolvedInThreads,
},
},
});
};
@ -96,6 +101,13 @@ describe('DiscussionActions', () => {
});
});
it('does not render jump to next discussion button if feature flag is enabled', () => {
const createComponent = createComponentFactory();
createComponent({}, { hideJumpToNextUnresolvedInThreads: true });
expect(wrapper.find(JumpToNextDiscussionButton).exists()).toBe(false);
});
describe('events handling', () => {
const createComponent = createComponentFactory(false);

View file

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Admin::PropagateIntegrationService do
describe '.propagate' do
let(:excluded_attributes) { %w[id project_id inherit_from_id instance created_at updated_at title description] }
let(:excluded_attributes) { %w[id project_id inherit_from_id instance created_at updated_at default] }
let!(:project) { create(:project) }
let!(:instance_integration) do
JiraService.create!(

View file

@ -20,7 +20,7 @@ RSpec.describe Projects::PropagateServiceTemplate do
end
let!(:project) { create(:project) }
let(:excluded_attributes) { %w[id project_id template created_at updated_at title description] }
let(:excluded_attributes) { %w[id project_id template created_at updated_at default] }
it 'creates services for projects' do
expect(project.pushover_service).to be_nil
@ -120,7 +120,7 @@ RSpec.describe Projects::PropagateServiceTemplate do
describe 'external tracker' do
it 'updates the project external tracker' do
service_template.update!(category: 'issue_tracker', default: false)
service_template.update!(category: 'issue_tracker')
expect { described_class.propagate(service_template) }
.to change { project.reload.has_external_issue_tracker }.to(true)