Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-30 12:09:36 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-30 12:09:36 +0300
commit491c773c7296a6e989d021c139d1bbce447bf709 (patch)
treed22f8b0f3aa11a61dab456aaf184b7be9be2e714
parent0629b103246d6c8b24e753f62ccfc8649df2c315 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock39
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue92
-rw-r--r--app/assets/javascripts/monitoring/pages/panel_new_page.vue40
-rw-r--r--app/assets/javascripts/monitoring/requests/index.js9
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js29
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js5
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js24
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js7
-rw-r--r--app/assets/javascripts/notes/components/discussion_actions.vue11
-rw-r--r--app/assets/javascripts/packages/details/components/app.vue15
-rw-r--r--app/assets/stylesheets/fontawesome_custom.scss4
-rw-r--r--app/assets/stylesheets/framework/ci_variable_list.scss1
-rw-r--r--app/assets/stylesheets/utilities.scss4
-rw-r--r--app/models/service.rb1
-rw-r--r--app/services/admin/propagate_integration_service.rb6
-rw-r--r--app/services/projects/propagate_service_template.rb6
-rw-r--r--app/views/ci/variables/_url_query_variable_row.html.haml4
-rw-r--r--app/views/ci/variables/_variable_row.html.haml4
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml2
-rw-r--r--app/views/groups/settings/ci_cd/_auto_devops_form.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml2
-rw-r--r--changelogs/unreleased/225652-update-file-table-in-package-details-ui.yml5
-rw-r--r--doc/api/README.md2
-rw-r--r--doc/api/job_artifacts.md265
-rw-r--r--doc/api/jobs.md272
-rw-r--r--doc/ci/pipelines/job_artifacts.md4
-rw-r--r--doc/ci/quick_start/README.md10
-rw-r--r--doc/ci/triggers/README.md2
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_builder_spec.js146
-rw-r--r--spec/frontend/monitoring/pages/panel_new_page_spec.js53
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js54
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js38
-rw-r--r--spec/frontend/notes/components/discussion_actions_spec.js14
-rw-r--r--spec/services/admin/propagate_integration_service_spec.rb2
-rw-r--r--spec/services/projects/propagate_service_template_spec.rb4
40 files changed, 854 insertions, 354 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 4c1cd9518f4..416385915b6 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-8e731456707441f9e22bfb3b668885f0f983c449
+a8e416c51179f80bb9fe213c657f14d950279c75
diff --git a/Gemfile b/Gemfile
index 8f8bde4fb06..2fdcfebc563 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
diff --git a/Gemfile.lock b/Gemfile.lock
index 15b0faa02ee..3cce9f9a1e0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index 3bba4fbc906..77b319aa2de 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -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"
diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue b/app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue
new file mode 100644
index 00000000000..7e6ebfe26c0
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/dashboard_panel_builder.vue
@@ -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>
diff --git a/app/assets/javascripts/monitoring/pages/panel_new_page.vue b/app/assets/javascripts/monitoring/pages/panel_new_page.vue
index d2ca0bf02d4..8ff6adb47ca 100644
--- a/app/assets/javascripts/monitoring/pages/panel_new_page.vue
+++ b/app/assets/javascripts/monitoring/pages/panel_new_page.vue
@@ -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>
diff --git a/app/assets/javascripts/monitoring/requests/index.js b/app/assets/javascripts/monitoring/requests/index.js
index 2be8048327f..962620862e6 100644
--- a/app/assets/javascripts/monitoring/requests/index.js
+++ b/app/assets/javascripts/monitoring/requests/index.js
@@ -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.'));
+}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 466d791f9b2..0c8f97f2e29 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -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');
+};
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index d408628fc4d..657ceea8644 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -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';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 744441c8935..d71b9eaa928 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -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;
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 89738756ffe..dbd4a68ed1d 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -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: [],
diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue
index 251199f1778..878a748e99a 100644
--- a/app/assets/javascripts/notes/components/discussion_actions.vue
+++ b/app/assets/javascripts/notes/components/discussion_actions.vue
@@ -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" />
diff --git a/app/assets/javascripts/packages/details/components/app.vue b/app/assets/javascripts/packages/details/components/app.vue
index 8d1d3a786dc..d2b2610892f 100644
--- a/app/assets/javascripts/packages/details/components/app.vue
+++ b/app/assets/javascripts/packages/details/components/app.vue
@@ -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>
diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss
index 1fadc618bd1..6070ab92974 100644
--- a/app/assets/stylesheets/fontawesome_custom.scss
+++ b/app/assets/stylesheets/fontawesome_custom.scss
@@ -260,10 +260,6 @@
content: '\f0a3';
}
-.fa-minus-circle::before {
- content: '\f056';
-}
-
.fa-bitbucket::before {
content: '\f171';
}
diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss
index cae7b9b5e46..b9c8395dd37 100644
--- a/app/assets/stylesheets/framework/ci_variable_list.scss
+++ b/app/assets/stylesheets/framework/ci_variable_list.scss
@@ -86,7 +86,6 @@
height: $input-height;
padding: 0;
background: transparent;
- border: 0;
color: $gl-text-color-secondary;
&:hover,
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 38842ec167e..f99efa743c0 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -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;
diff --git a/app/models/service.rb b/app/models/service.rb
index 1edf1cb5830..f6e9761b8c2 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -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
diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb
index e21bb03ed68..9a5ce58ee2c 100644
--- a/app/services/admin/propagate_integration_service.rb
+++ b/app/services/admin/propagate_integration_service.rb
@@ -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
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index b6465810fde..54d09b354a1 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -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
diff --git a/app/views/ci/variables/_url_query_variable_row.html.haml b/app/views/ci/variables/_url_query_variable_row.html.haml
index 6672a8e5ea0..4c6eeb17c07 100644
--- a/app/views/ci/variables/_url_query_variable_row.html.haml
+++ b/app/views/ci/variables/_url_query_variable_row.html.haml
@@ -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')
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
index c69a3adb0e9..542a41c2f7d 100644
--- a/app/views/ci/variables/_variable_row.html.haml
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -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')
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index e99d0ac1105..6cf48f89876 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -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
diff --git a/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
index e7efc0237c8..2b5019222f8 100644
--- a/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
+++ b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
@@ -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'
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 2786621352f..b4c9e51f53a 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -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' }
diff --git a/changelogs/unreleased/225652-update-file-table-in-package-details-ui.yml b/changelogs/unreleased/225652-update-file-table-in-package-details-ui.yml
new file mode 100644
index 00000000000..94efe16e08f
--- /dev/null
+++ b/changelogs/unreleased/225652-update-file-table-in-package-details-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Updates to file table in package details UI
+merge_request: 36723
+author: Adam Alvis (@adamalvis)
+type: fixed
diff --git a/doc/api/README.md b/doc/api/README.md
index a7f549a3f42..f640149b248 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -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)
diff --git a/doc/api/job_artifacts.md b/doc/api/job_artifacts.md
new file mode 100644
index 00000000000..5df7915ad5c
--- /dev/null
+++ b/doc/api/job_artifacts.md
@@ -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.
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index ef192dae329..3b23c6a7917 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -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.
diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md
index a099dc371d2..3d034da58a6 100644
--- a/doc/ci/pipelines/job_artifacts.md
+++ b/doc/ci/pipelines/job_artifacts.md
@@ -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
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 8be43d77a0e..0d237a1f530 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -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"
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index c88c55d8556..df0516e5fd2 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -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
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 7108b76b5f8..1d18d2e7c28 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -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:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 5cc034c0e04..42a49d4afc4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
new file mode 100644
index 00000000000..cf7a1673a10
--- /dev/null
+++ b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
@@ -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);
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/pages/panel_new_page_spec.js b/spec/frontend/monitoring/pages/panel_new_page_spec.js
index 665040a0bfd..83365b754d9 100644
--- a/spec/frontend/monitoring/pages/panel_new_page_spec.js
+++ b/spec/frontend/monitoring/pages/panel_new_page_spec.js
@@ -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();
});
});
});
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index a6d6accbe23..4b7b733720c 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -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 },
+ ],
+ [],
+ );
+ });
+ });
});
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index 14b38d79aa2..8ffd23641a4 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -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!');
+ });
+ });
});
diff --git a/spec/frontend/notes/components/discussion_actions_spec.js b/spec/frontend/notes/components/discussion_actions_spec.js
index 44dc148933c..3e1e43d0c6a 100644
--- a/spec/frontend/notes/components/discussion_actions_spec.js
+++ b/spec/frontend/notes/components/discussion_actions_spec.js
@@ -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);
diff --git a/spec/services/admin/propagate_integration_service_spec.rb b/spec/services/admin/propagate_integration_service_spec.rb
index 843b78a41e9..2e879cf06d1 100644
--- a/spec/services/admin/propagate_integration_service_spec.rb
+++ b/spec/services/admin/propagate_integration_service_spec.rb
@@ -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!(
diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index 266bf2cc213..df69e5a29fb 100644
--- a/spec/services/projects/propagate_service_template_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -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)