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:
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js4
-rw-r--r--app/assets/javascripts/lib/utils/forms.js12
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue31
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue213
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js8
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js1
-rw-r--r--app/assets/javascripts/pages/projects/project.js23
-rw-r--r--app/assets/javascripts/pages/search/show/search.js3
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue6
-rw-r--r--app/assets/stylesheets/pages/builds.scss5
-rw-r--r--app/assets/stylesheets/pages/prometheus.scss22
-rw-r--r--app/controllers/projects/environments_controller.rb3
-rw-r--r--app/presenters/blob_presenter.rb19
-rw-r--r--app/presenters/blobs/unfold_presenter.rb23
-rw-r--r--app/services/members/base_service.rb4
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/search/_form.html.haml1
-rw-r--r--app/views/search/_results.html.haml16
-rw-r--r--app/views/shared/_ref_switcher.html.haml15
-rw-r--r--changelogs/unreleased/59712-resolve-the-search-problem-issue.yml5
-rw-r--r--changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml5
-rw-r--r--changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml5
-rw-r--r--changelogs/unreleased/65152-selective-highlight.yml5
-rw-r--r--changelogs/unreleased/fix-job-log-formatting.yml5
-rw-r--r--changelogs/unreleased/issue_58494.yml5
-rw-r--r--changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/tr-remove-embed-metrics-flag.yml5
-rw-r--r--db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb10
-rw-r--r--db/schema.rb4
-rw-r--r--lib/api/project_import.rb1
-rw-r--r--lib/banzai/filter/inline_embeds_filter.rb2
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb2
-rw-r--r--lib/gitlab/gfm/uploads_rewriter.rb14
-rw-r--r--lib/gitlab/gon_helper.rb5
-rw-r--r--lib/gitlab/highlight.rb11
-rw-r--r--lib/gitlab/project_search_results.rb4
-rw-r--r--lib/rouge/formatters/html_gitlab.rb4
-rw-r--r--locale/gitlab.pot15
-rw-r--r--scripts/utils.sh126
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb33
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb69
-rw-r--r--spec/frontend/lib/utils/forms_spec.js74
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js36
-rw-r--r--spec/lib/banzai/filter/inline_metrics_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb10
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb11
-rw-r--r--spec/lib/gitlab/highlight_spec.rb8
-rw-r--r--spec/presenters/blob_presenter_spec.rb52
-rw-r--r--spec/support/shared_examples/relative_positioning_shared_examples.rb6
53 files changed, 525 insertions, 440 deletions
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index df5119ec64e..3b6825376ad 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.7.0
+8.8.0
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index 789a057caf8..137cc7b4669 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -18,9 +18,7 @@ $.fn.renderGFM = function renderGFM() {
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.gfm-project_member').get());
initMRPopovers(this.find('.gfm-merge_request').get());
- if (gon.features && gon.features.gfmEmbeddedMetrics) {
- renderMetrics(this.find('.js-render-metrics').get());
- }
+ renderMetrics(this.find('.js-render-metrics').get());
return this;
};
diff --git a/app/assets/javascripts/lib/utils/forms.js b/app/assets/javascripts/lib/utils/forms.js
new file mode 100644
index 00000000000..106209a2f3a
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/forms.js
@@ -0,0 +1,12 @@
+export const serializeFormEntries = entries =>
+ entries.reduce((acc, { name, value }) => Object.assign(acc, { [name]: value }), {});
+
+export const serializeForm = form => {
+ const fdata = new FormData(form);
+ const entries = Array.from(fdata.keys()).map(key => {
+ const val = fdata.getAll(key);
+ return { name: key, value: val.length === 1 ? val[0] : val };
+ });
+
+ return serializeFormEntries(entries);
+};
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index edf9423c74c..5b950f8c966 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -1,6 +1,7 @@
<script>
import { __ } from '~/locale';
-import { GlLink } from '@gitlab/ui';
+import { mapState } from 'vuex';
+import { GlLink, GlButton } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
@@ -15,6 +16,7 @@ let debouncedResize;
export default {
components: {
GlAreaChart,
+ GlButton,
GlChartSeriesLabel,
GlLink,
Icon,
@@ -67,6 +69,7 @@ export default {
};
},
computed: {
+ ...mapState('monitoringDashboard', ['exportMetricsToCsvEnabled']),
chartData() {
// Transforms & supplements query data to render appropriate labels & styles
// Input: [{ queryAttributes1 }, { queryAttributes2 }]
@@ -176,6 +179,18 @@ export default {
yAxisLabel() {
return `${this.graphData.y_label}`;
},
+ csvText() {
+ const chartData = this.chartData[0].data;
+ const header = `timestamp,${this.graphData.y_label}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
+ return chartData.reduce((csv, data) => {
+ const row = data.join(',');
+ return `${csv}${row}\r\n`;
+ }, header);
+ },
+ downloadLink() {
+ const data = new Blob([this.csvText], { type: 'text/plain' });
+ return window.URL.createObjectURL(data);
+ },
},
watch: {
containerWidth: 'onResize',
@@ -240,10 +255,20 @@ export default {
</script>
<template>
- <div class="col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
- <div class="prometheus-graph" :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
+ <div class="prometheus-graph col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
+ <div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
+ <gl-button
+ v-if="exportMetricsToCsvEnabled"
+ :href="downloadLink"
+ :title="__('Download CSV')"
+ :aria-label="__('Download CSV')"
+ style="margin-left: 200px;"
+ download="chart_metrics.csv"
+ >
+ {{ __('Download CSV') }}
+ </gl-button>
<div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 5892c18ac91..782e4310f3e 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -3,6 +3,7 @@ import {
GlButton,
GlDropdown,
GlDropdownItem,
+ GlFormGroup,
GlModal,
GlModalDirective,
GlTooltipDirective,
@@ -34,6 +35,7 @@ export default {
GlButton,
GlDropdown,
GlDropdownItem,
+ GlFormGroup,
GlModal,
},
directives: {
@@ -278,107 +280,136 @@ export default {
<template>
<div class="prometheus-graphs">
- <div class="gl-p-3 border-bottom bg-gray-light d-flex justify-content-between">
- <div
- v-if="environmentsEndpoint"
- class="dropdowns d-flex align-items-center justify-content-between"
- >
- <div v-if="multipleDashboardsEnabled" class="d-flex align-items-center">
- <label class="mb-0">{{ __('Dashboard') }}</label>
- <gl-dropdown
- class="ml-2 mr-3 js-dashboards-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="selectedDashboardText"
+ <div class="gl-p-3 pb-0 border-bottom bg-gray-light">
+ <div class="row">
+ <template v-if="environmentsEndpoint">
+ <gl-form-group
+ v-if="multipleDashboardsEnabled"
+ :label="__('Dashboard')"
+ label-size="sm"
+ label-for="monitor-dashboards-dropdown"
+ class="col-sm-12 col-md-4 col-lg-2"
>
- <gl-dropdown-item
- v-for="dashboard in allDashboards"
- :key="dashboard.path"
- :active="dashboard.path === currentDashboard"
- active-class="is-active"
- :href="`?dashboard=${dashboard.path}`"
+ <gl-dropdown
+ id="monitor-dashboards-dropdown"
+ class="mb-0 d-flex js-dashboards-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="selectedDashboardText"
>
- {{ dashboard.display_name || dashboard.path }}
- </gl-dropdown-item>
- </gl-dropdown>
- </div>
- <div class="d-flex align-items-center">
- <strong>{{ s__('Metrics|Environment') }}</strong>
- <gl-dropdown
- class="prepend-left-10 js-environments-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="currentEnvironmentName"
- :disabled="environments.length === 0"
+ <gl-dropdown-item
+ v-for="dashboard in allDashboards"
+ :key="dashboard.path"
+ :active="dashboard.path === currentDashboard"
+ active-class="is-active"
+ :href="`?dashboard=${dashboard.path}`"
+ >{{ dashboard.display_name || dashboard.path }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </gl-form-group>
+
+ <gl-form-group
+ :label="s__('Metrics|Environment')"
+ label-size="sm"
+ label-for="monitor-environments-dropdown"
+ class="col-sm-6 col-md-4 col-lg-2"
>
- <gl-dropdown-item
- v-for="environment in environments"
- :key="environment.id"
- :active="environment.name === currentEnvironmentName"
- active-class="is-active"
- :href="environment.metrics_path"
- >{{ environment.name }}</gl-dropdown-item
+ <gl-dropdown
+ id="monitor-environments-dropdown"
+ class="mb-0 d-flex js-environments-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="currentEnvironmentName"
+ :disabled="environments.length === 0"
>
- </gl-dropdown>
- </div>
- <div v-if="!showEmptyState" class="d-flex align-items-center prepend-left-8">
- <strong>{{ s__('Metrics|Show last') }}</strong>
- <gl-dropdown
- class="prepend-left-10 js-time-window-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="selectedTimeWindow"
+ <gl-dropdown-item
+ v-for="environment in environments"
+ :key="environment.id"
+ :active="environment.name === currentEnvironmentName"
+ active-class="is-active"
+ :href="environment.metrics_path"
+ >{{ environment.name }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </gl-form-group>
+
+ <gl-form-group
+ v-if="!showEmptyState"
+ :label="s__('Metrics|Show last')"
+ label-size="sm"
+ label-for="monitor-time-window-dropdown"
+ class="col-sm-6 col-md-4 col-lg-2"
>
- <gl-dropdown-item
- v-for="(value, key) in timeWindows"
- :key="key"
- :active="activeTimeWindow(key)"
- :href="setTimeWindowParameter(key)"
- active-class="active"
- >{{ value }}</gl-dropdown-item
+ <gl-dropdown
+ id="monitor-time-window-dropdown"
+ class="mb-0 d-flex js-time-window-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="selectedTimeWindow"
>
- </gl-dropdown>
- </div>
- </div>
- <div class="d-flex">
- <div v-if="addingMetricsAvailable">
- <gl-button
- v-gl-modal="$options.addMetric.modalId"
- class="js-add-metric-button text-success border-success"
- >{{ $options.addMetric.title }}</gl-button
- >
- <gl-modal
- ref="addMetricModal"
- :modal-id="$options.addMetric.modalId"
- :title="$options.addMetric.title"
- >
- <form ref="customMetricsForm" :action="customMetricsPath" method="post">
- <custom-metrics-form-fields
- :validate-query-path="validateQueryPath"
- form-operation="post"
- @formValidation="setFormValidity"
- />
- </form>
- <div slot="modal-footer">
- <gl-button @click="hideAddMetricModal">{{ __('Cancel') }}</gl-button>
- <gl-button
- :disabled="!formIsValid"
- variant="success"
- @click="submitCustomMetricsForm"
- >{{ __('Save changes') }}</gl-button
+ <gl-dropdown-item
+ v-for="(value, key) in timeWindows"
+ :key="key"
+ :active="activeTimeWindow(key)"
+ :href="setTimeWindowParameter(key)"
+ active-class="active"
+ >{{ value }}</gl-dropdown-item
>
- </div>
- </gl-modal>
- </div>
- <gl-button
- v-if="externalDashboardUrl.length"
- class="js-external-dashboard-link prepend-left-8"
- variant="primary"
- :href="externalDashboardUrl"
- target="_blank"
+ </gl-dropdown>
+ </gl-form-group>
+ </template>
+
+ <gl-form-group
+ v-if="addingMetricsAvailable || externalDashboardUrl.length"
+ label-for="prometheus-graphs-dropdown-buttons"
+ class="dropdown-buttons col-lg d-lg-flex align-items-end"
>
- {{ __('View full dashboard') }}
- <icon name="external-link" />
- </gl-button>
+ <div id="prometheus-graphs-dropdown-buttons">
+ <gl-button
+ v-if="addingMetricsAvailable"
+ v-gl-modal="$options.addMetric.modalId"
+ class="mr-2 mt-1 js-add-metric-button text-success border-success"
+ >
+ {{ $options.addMetric.title }}
+ </gl-button>
+ <gl-modal
+ v-if="addingMetricsAvailable"
+ ref="addMetricModal"
+ :modal-id="$options.addMetric.modalId"
+ :title="$options.addMetric.title"
+ >
+ <form ref="customMetricsForm" :action="customMetricsPath" method="post">
+ <custom-metrics-form-fields
+ :validate-query-path="validateQueryPath"
+ form-operation="post"
+ @formValidation="setFormValidity"
+ />
+ </form>
+ <div slot="modal-footer">
+ <gl-button @click="hideAddMetricModal">{{ __('Cancel') }}</gl-button>
+ <gl-button
+ :disabled="!formIsValid"
+ variant="success"
+ @click="submitCustomMetricsForm"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ </div>
+ </gl-modal>
+
+ <gl-button
+ v-if="externalDashboardUrl.length"
+ class="mt-1 js-external-dashboard-link"
+ variant="primary"
+ :href="externalDashboardUrl"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('View full dashboard') }}
+ <icon name="external-link" />
+ </gl-button>
+ </div>
+ </gl-form-group>
</div>
</div>
+
<div v-if="!showEmptyState">
<graph-group
v-for="(groupData, index) in groups"
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index c0fee1ebb99..366034becd0 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -13,6 +13,7 @@ export default (props = {}) => {
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
+ exportMetricsToCsvEnabled: gon.features.exportMetricsToCsvEnabled,
});
}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 0cbad179f17..a9c491c7c6c 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -37,11 +37,17 @@ export const setEndpoints = ({ commit }, endpoints) => {
export const setFeatureFlags = (
{ commit },
- { prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
+ {
+ prometheusEndpointEnabled,
+ multipleDashboardsEnabled,
+ additionalPanelTypesEnabled,
+ exportMetricsToCsvEnabled,
+ },
) => {
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled);
commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
+ commit(types.SET_EXPORT_METRICS_TO_CSV_ENABLED, exportMetricsToCsvEnabled);
};
export const setShowErrorBanner = ({ commit }, enabled) => {
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 4b1aadbcf05..9ec8214b167 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -17,3 +17,4 @@ export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
+export const SET_EXPORT_METRICS_TO_CSV_ENABLED = 'SET_EXPORT_METRICS_TO_CSV_ENABLED';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index b19520d6638..a2dceb21fc0 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -99,4 +99,7 @@ export default {
[types.SET_SHOW_ERROR_BANNER](state, enabled) {
state.showErrorBanner = enabled;
},
+ [types.SET_EXPORT_METRICS_TO_CSV_ENABLED](state, enabled) {
+ state.exportMetricsToCsvEnabled = enabled;
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 440bdc951e0..a14a25e3a20 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -10,6 +10,7 @@ export default () => ({
useDashboardEndpoint: false,
multipleDashboardsEnabled: false,
additionalPanelTypesEnabled: false,
+ exportMetricsToCsvEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index f0d529758d5..332b6811af6 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,9 +1,10 @@
-/* eslint-disable func-names, no-var, no-return-assign, one-var, object-shorthand, vars-on-top */
+/* eslint-disable func-names, no-var, no-return-assign, object-shorthand, vars-on-top */
import $ from 'jquery';
import Cookies from 'js-cookie';
import { __ } from '~/locale';
-import { visitUrl } from '~/lib/utils/url_utility';
+import { visitUrl, mergeUrlParams } from '~/lib/utils/url_utility';
+import { serializeForm } from '~/lib/utils/forms';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import projectSelect from '../../project_select';
@@ -107,9 +108,10 @@ export default class Project {
refLink.href = '#';
return $('.js-project-refs-dropdown').each(function() {
- var $dropdown, selected;
- $dropdown = $(this);
- selected = $dropdown.data('selected');
+ var $dropdown = $(this);
+ var selected = $dropdown.data('selected');
+ var fieldName = $dropdown.data('fieldName');
+ var shouldVisit = Boolean($dropdown.data('visit'));
return $dropdown.glDropdown({
data(term, callback) {
axios
@@ -127,7 +129,7 @@ export default class Project {
filterRemote: true,
filterByText: true,
inputFieldName: $dropdown.data('inputFieldName'),
- fieldName: $dropdown.data('fieldName'),
+ fieldName,
renderRow: function(ref) {
var li = refListItem.cloneNode(false);
@@ -158,15 +160,12 @@ export default class Project {
clicked: function(options) {
const { e } = options;
e.preventDefault();
- if ($('input[name="ref"]').length) {
+ if ($(`input[name="${fieldName}"]`).length) {
var $form = $dropdown.closest('form');
-
- var $visit = $dropdown.data('visit');
- var shouldVisit = $visit ? true : $visit;
var action = $form.attr('action');
- var divider = action.indexOf('?') === -1 ? '?' : '&';
+
if (shouldVisit) {
- visitUrl(`${action}${divider}${$form.serialize()}`);
+ visitUrl(mergeUrlParams(serializeForm($form[0]), action));
}
}
},
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index 7743e05e748..81b6225cb18 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import Flash from '~/flash';
import Api from '~/api';
import { __ } from '~/locale';
+import Project from '~/pages/projects/project';
export default class Search {
constructor() {
@@ -69,6 +70,8 @@ export default class Search {
},
clicked: () => Search.submitSearch(),
});
+
+ Project.initRefSwitcher();
}
eventListeners() {
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 3f021a26ec5..e01080b04d6 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -102,7 +102,11 @@ export default {
<span
v-if="pipeline.flags.detached_merge_request_pipeline"
v-gl-tooltip
- :title="__('This pipeline is run on the source branch')"
+ :title="
+ __(
+ 'The code of a detached pipeline is tested against the source branch instead of merged results',
+ )
+ "
class="js-pipeline-url-detached badge badge-info"
>
{{ __('detached') }}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 262c0bf5ed2..6fc742871e7 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -124,11 +124,6 @@
float: left;
padding-left: $gl-padding-8;
}
-
- .section-header ~ .section.line {
- margin-left: $gl-padding;
- display: block;
- }
}
.build-header {
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss
index 05a4cc168a8..72f1b5307ec 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/pages/prometheus.scss
@@ -1,17 +1,17 @@
.prometheus-graphs {
- .dropdowns {
- .dropdown-menu-toggle {
- svg {
- position: absolute;
- right: 5%;
- top: 25%;
- }
+ .dropdown-buttons {
+ > div {
+ margin-left: auto;
}
+ }
- .dropdown-menu-toggle,
- .dropdown-menu {
- width: 240px;
- }
+ .col-form-label {
+ line-height: 1;
+ padding-top: 0;
+ }
+
+ .form-group {
+ margin-bottom: map-get($spacing-scale, 3);
}
}
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index b709ac85e39..df9e55fda2a 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -15,6 +15,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
+ push_frontend_feature_flag(:export_metrics_to_csv_enabled)
end
def index
@@ -160,7 +161,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics_dashboard
- if Feature.enabled?(:gfm_embedded_metrics, project) && params[:embedded]
+ if params[:embedded]
result = dashboard_finder.find(
project,
current_user,
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index f85c1a237a6..2cf3278d240 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -3,13 +3,12 @@
class BlobPresenter < Gitlab::View::Presenter::Delegated
presents :blob
- def highlight(since: nil, to: nil, plain: nil)
+ def highlight(plain: nil)
load_all_blob_data
Gitlab::Highlight.highlight(
blob.path,
- limited_blob_data(since: since, to: to),
- since: since,
+ blob.data,
language: blob.language_from_gitattributes,
plain: plain
)
@@ -24,18 +23,4 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def load_all_blob_data
blob.load_all_data! if blob.respond_to?(:load_all_data!)
end
-
- def limited_blob_data(since: nil, to: nil)
- return blob.data if since.blank? || to.blank?
-
- limited_blob_lines(since, to).join
- end
-
- def limited_blob_lines(since, to)
- all_lines[since - 1..to - 1]
- end
-
- def all_lines
- @all_lines ||= blob.data.lines
- end
end
diff --git a/app/presenters/blobs/unfold_presenter.rb b/app/presenters/blobs/unfold_presenter.rb
index f4672d22fc9..21a1e1309e0 100644
--- a/app/presenters/blobs/unfold_presenter.rb
+++ b/app/presenters/blobs/unfold_presenter.rb
@@ -21,19 +21,20 @@ module Blobs
load_all_blob_data
@subject = blob
+ @all_lines = blob.data.lines
super(params)
if full?
- self.attributes = { since: 1, to: all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 }
+ self.attributes = { since: 1, to: @all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 }
end
end
# Returns an array of Gitlab::Diff::Line with match line added
def diff_lines
- diff_lines = limited_blob_lines(since, to).map.with_index do |line, index|
- full_line = line.delete("\n")
+ diff_lines = lines.map.with_index do |line, index|
+ full_line = limited_blob_lines[index].delete("\n")
- Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: lines[index])
+ Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: line)
end
add_match_line(diff_lines)
@@ -42,7 +43,7 @@ module Blobs
end
def lines
- @lines ||= highlight(since: since, to: to).lines.map(&:html_safe)
+ @lines ||= limit(highlight.lines).map(&:html_safe)
end
def match_line_text
@@ -58,7 +59,7 @@ module Blobs
def add_match_line(diff_lines)
return unless unfold?
- if bottom? && to < all_lines.size
+ if bottom? && to < @all_lines.size
old_pos = to - offset
new_pos = to
elsif since != 1
@@ -72,5 +73,15 @@ module Blobs
bottom? ? diff_lines.push(match_line) : diff_lines.unshift(match_line)
end
+
+ def limited_blob_lines
+ @limited_blob_lines ||= limit(@all_lines)
+ end
+
+ def limit(lines)
+ return lines if full?
+
+ lines[since - 1..to - 1]
+ end
end
end
diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb
index e78affff797..5d69418fb7d 100644
--- a/app/services/members/base_service.rb
+++ b/app/services/members/base_service.rb
@@ -51,7 +51,9 @@ module Members
def enqueue_delete_todos(member)
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
# don't enqueue immediately to prevent todos removal in case of a mistake
- TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
+ member.run_after_commit_or_now do
+ TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
+ end
end
end
end
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 5d307d6a70d..2b56ada8b73 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -43,7 +43,7 @@
} }
Auto DevOps
- if @pipeline.detached_merge_request_pipeline?
- %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run on the source branch" }
+ %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "The code of a detached pipeline is tested against the source branch instead of merged results" }
detached
- if @pipeline.stuck?
%span.js-pipeline-url-stuck.badge.badge-warning
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 4c4f3e0298b..464db94b7f4 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -1,6 +1,7 @@
= form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f|
= hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope]
+ = hidden_field_tag :repository_ref, params[:repository_ref]
.d-lg-flex.align-items-end
.search-field-holder.form-group.mr-lg-1.mb-lg-0
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index cb8a8a24be8..de9947528cf 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -2,17 +2,25 @@
= render partial: "search/results/empty"
= render_if_exists 'shared/promotions/promote_advanced_search'
- else
- .row-content-block
+ .row-content-block.d-md-flex.text-left.align-items-center
- unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
- - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project])
- = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
+ - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project], class: 'ml-md-1')
+ - if @scope == 'blobs'
+ - repository_ref = params[:repository_ref].to_s.presence || @project.default_branch
+ = s_("SearchCodeResults|in")
+ .mx-md-1
+ = render partial: "shared/ref_switcher", locals: { ref: repository_ref, form_path: request.fullpath, field_name: 'repository_ref' }
+ = s_('SearchCodeResults|of %{link_to_project}').html_safe % { link_to_project: link_to_project }
+ - else
+ = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- elsif @group
- - link_to_group = link_to(@group.name, @group)
+ - link_to_group = link_to(@group.name, @group, class: 'ml-md-1')
= _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
= render_if_exists 'shared/promotions/promote_advanced_search'
+
.results.prepend-top-10
- if @scope == 'commits'
%ul.content-list.commit-list
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 7cbc5810c10..74e0a088656 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,12 +1,19 @@
-- dropdown_toggle_text = @ref || @project.default_branch
-= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
- = hidden_field_tag :destination, destination
+- return unless @project
+
+- ref = local_assigns.fetch(:ref, @ref)
+- form_path = local_assigns.fetch(:form_path, switch_project_refs_path(@project))
+- dropdown_toggle_text = ref || @project.default_branch
+- field_name = local_assigns.fetch(:field_name, 'ref')
+
+= form_tag form_path, method: :get, class: "project-refs-form" do
+ - if defined?(destination)
+ = hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
- = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-right" if local_assigns[:align_right]) }
.dropdown-page-one
= dropdown_title _("Switch branch/tag")
diff --git a/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml b/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml
new file mode 100644
index 00000000000..964962cb817
--- /dev/null
+++ b/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Add branch/tags/commits dropdown filter on the search page for searching codes
+merge_request: 28282
+author: minghuan lei
+type: changed
diff --git a/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml b/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml
new file mode 100644
index 00000000000..ccc3195e6ae
--- /dev/null
+++ b/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Updated the detached pipeline badge tooltip text to offer a better explanation
+merge_request: 31626
+author:
+type: other
diff --git a/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml b/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml
new file mode 100644
index 00000000000..c564b98bb41
--- /dev/null
+++ b/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml
@@ -0,0 +1,5 @@
+---
+title: Improve layout of dropdowns in the metrics dashboard page
+merge_request: 31239
+author:
+type: fixed
diff --git a/changelogs/unreleased/65152-selective-highlight.yml b/changelogs/unreleased/65152-selective-highlight.yml
deleted file mode 100644
index 371dbbd5924..00000000000
--- a/changelogs/unreleased/65152-selective-highlight.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support selective highlighting of lines
-merge_request: 31361
-author:
-type: performance
diff --git a/changelogs/unreleased/fix-job-log-formatting.yml b/changelogs/unreleased/fix-job-log-formatting.yml
new file mode 100644
index 00000000000..0dd545aaecc
--- /dev/null
+++ b/changelogs/unreleased/fix-job-log-formatting.yml
@@ -0,0 +1,5 @@
+---
+title: Fix job logs where style changes were broken down into separate lines
+merge_request: 31674
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_58494.yml b/changelogs/unreleased/issue_58494.yml
new file mode 100644
index 00000000000..c74768fc020
--- /dev/null
+++ b/changelogs/unreleased/issue_58494.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent turning plain links into embedded when moving issues
+merge_request: 31489
+author:
+type: fixed
diff --git a/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml b/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml
new file mode 100644
index 00000000000..59f12fca1f1
--- /dev/null
+++ b/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Export and download CSV from metrics charts
+merge_request: 30760
+author:
+type: added
diff --git a/changelogs/unreleased/tr-remove-embed-metrics-flag.yml b/changelogs/unreleased/tr-remove-embed-metrics-flag.yml
new file mode 100644
index 00000000000..a327a6868d3
--- /dev/null
+++ b/changelogs/unreleased/tr-remove-embed-metrics-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Link and embed metrics in GitLab Flavored Markdown
+merge_request: 31106
+author:
+type: added
diff --git a/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb b/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb
new file mode 100644
index 00000000000..f6db90f6637
--- /dev/null
+++ b/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class RemoveEpicIssuesDefaultRelativePosition < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ change_column_null :epic_issues, :relative_position, true
+ change_column_default :epic_issues, :relative_position, from: 1073741823, to: nil
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0e4a3d07fdd..6674f412140 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_08_02_235445) do
+ActiveRecord::Schema.define(version: 2019_08_06_071559) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -1194,7 +1194,7 @@ ActiveRecord::Schema.define(version: 2019_08_02_235445) do
create_table "epic_issues", id: :serial, force: :cascade do |t|
t.integer "epic_id", null: false
t.integer "issue_id", null: false
- t.integer "relative_position", default: 1073741823, null: false
+ t.integer "relative_position"
t.index ["epic_id"], name: "index_epic_issues_on_epic_id"
t.index ["issue_id"], name: "index_epic_issues_on_issue_id", unique: true
end
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 71891e43dcc..bb1b037c08f 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -59,6 +59,7 @@ module API
}
override_params = import_params.delete(:override_params)
+ filter_attributes_using_license!(override_params) if override_params
project = ::Projects::GitlabProjectsImportService.new(
current_user, project_params, override_params
diff --git a/lib/banzai/filter/inline_embeds_filter.rb b/lib/banzai/filter/inline_embeds_filter.rb
index 97394fd8f82..9f1ef0796f0 100644
--- a/lib/banzai/filter/inline_embeds_filter.rb
+++ b/lib/banzai/filter/inline_embeds_filter.rb
@@ -10,8 +10,6 @@ module Banzai
# the link, and insert this node after any html content
# surrounding the link.
def call
- return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
-
doc.xpath(xpath_search).each do |node|
next unless element = element_to_embed(node)
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
index ff91be2cbb7..4d8a5028898 100644
--- a/lib/banzai/filter/inline_metrics_redactor_filter.rb
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -13,8 +13,6 @@ module Banzai
# uses to identify the embedded content, removing
# only unnecessary nodes.
def call
- return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
-
nodes.each do |node|
path = paths_by_node[node]
user_has_access = user_access_by_path[path]
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
index 2d1c9ac40ae..6b52d6e88e5 100644
--- a/lib/gitlab/gfm/uploads_rewriter.rb
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -27,7 +27,15 @@ module Gitlab
klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
moved = klass.copy_to(file, target_parent)
- moved.markdown_link
+
+ moved_markdown = moved.markdown_link
+
+ # Prevents rewrite of plain links as embedded
+ if was_embedded?(markdown)
+ moved_markdown
+ else
+ moved_markdown.sub(/\A!/, "")
+ end
end
end
@@ -43,6 +51,10 @@ module Gitlab
referenced_files.compact.select(&:exists?)
end
+ def was_embedded?(markdown)
+ markdown.starts_with?("!")
+ end
+
private
def find_file(project, secret, file)
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 41ec8741eb1..92917028851 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -38,11 +38,6 @@ module Gitlab
gon.current_user_fullname = current_user.name
gon.current_user_avatar_url = current_user.avatar_url
end
-
- # Flag controls a GFM feature used across many routes.
- # Pushing the flag from one place simplifies control
- # and facilitates easy removal.
- push_frontend_feature_flag(:gfm_embedded_metrics)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 1f49a26f0a2..381f1dd4e55 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -6,16 +6,15 @@ module Gitlab
TIMEOUT_FOREGROUND = 3.seconds
MAXIMUM_TEXT_HIGHLIGHT_SIZE = 1.megabyte
- def self.highlight(blob_name, blob_content, since: nil, language: nil, plain: false)
- new(blob_name, blob_content, since: since, language: language)
+ def self.highlight(blob_name, blob_content, language: nil, plain: false)
+ new(blob_name, blob_content, language: language)
.highlight(blob_content, continue: false, plain: plain)
end
attr_reader :blob_name
- def initialize(blob_name, blob_content, since: nil, language: nil)
+ def initialize(blob_name, blob_content, language: nil)
@formatter = Rouge::Formatters::HTMLGitlab
- @since = since
@language = language
@blob_name = blob_name
@blob_content = blob_content
@@ -54,13 +53,13 @@ module Gitlab
end
def highlight_plain(text)
- @formatter.format(Rouge::Lexers::PlainText.lex(text), since: @since).html_safe
+ @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
end
def highlight_rich(text, continue: true)
tag = lexer.tag
tokens = lexer.lex(text, continue: continue)
- Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag, since: @since).html_safe }
+ Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag).html_safe }
rescue Timeout::Error => e
Gitlab::Sentry.track_exception(e)
highlight_plain(text)
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 827f4f77f36..5e77d31760d 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -134,9 +134,11 @@ module Gitlab
project.repository.commit(key) if Commit.valid_hash?(key)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def project_ids_relation
- project
+ Project.where(id: project).select(:id).reorder(nil)
end
+ # rubocop: enabled CodeReuse/ActiveRecord
def filter_milestones_by_project(milestones)
return Milestone.none unless Ability.allowed?(@current_user, :read_milestone, @project)
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index 0d4ac504428..e2a7d3ef5ba 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -8,8 +8,8 @@ module Rouge
# Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance.
#
# [+tag+] The tag (language) of the lexer used to generate the formatted tokens
- def initialize(tag: nil, since: nil)
- @line_number = since || 1
+ def initialize(tag: nil)
+ @line_number = 1
@tag = tag
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9bc2fd1b8f4..50103c226d4 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4007,6 +4007,9 @@ msgstr ""
msgid "Download"
msgstr ""
+msgid "Download CSV"
+msgstr ""
+
msgid "Download artifacts"
msgstr ""
@@ -9666,6 +9669,12 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchCodeResults|in"
+msgstr ""
+
+msgid "SearchCodeResults|of %{link_to_project}"
+msgstr ""
+
msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
msgstr ""
@@ -10888,6 +10897,9 @@ msgstr ""
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
+msgid "The code of a detached pipeline is tested against the source branch instead of merged results"
+msgstr ""
+
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr ""
@@ -11383,9 +11395,6 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run on the source branch"
-msgstr ""
-
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
diff --git a/scripts/utils.sh b/scripts/utils.sh
index c1bdef5b847..f0f08e2e1c5 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -158,129 +158,3 @@ function wait_for_job_to_be_done() {
echoinfo "The '${job_name}' passed."
fi
}
-
-function install_api_client_dependencies_with_apk() {
- apk add --update openssl curl jq
-}
-
-function install_api_client_dependencies_with_apt() {
- apt update && apt install jq -y
-}
-
-function install_gitlab_gem() {
- gem install gitlab --no-document
-}
-
-function echoerr() {
- local header="${2}"
-
- if [ -n "${header}" ]; then
- printf "\n\033[0;31m** %s **\n\033[0m" "${1}" >&2;
- else
- printf "\033[0;31m%s\n\033[0m" "${1}" >&2;
- fi
-}
-
-function echoinfo() {
- local header="${2}"
-
- if [ -n "${header}" ]; then
- printf "\n\033[0;33m** %s **\n\033[0m" "${1}" >&2;
- else
- printf "\033[0;33m%s\n\033[0m" "${1}" >&2;
- fi
-}
-
-function get_job_id() {
- local job_name="${1}"
- local query_string="${2:+&${2}}"
- local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
- if [ -z "${api_token}" ]; then
- echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
- return
- fi
-
- local max_page=3
- local page=1
-
- while true; do
- local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
- echoinfo "GET ${url}"
-
- local job_id
- job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
- [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
-
- let "page++"
- done
-
- if [[ "${job_id}" == "" ]]; then
- echoerr "The '${job_name}' job ID couldn't be retrieved!"
- else
- echoinfo "The '${job_name}' job ID is ${job_id}"
- echo "${job_id}"
- fi
-}
-
-function play_job() {
- local job_name="${1}"
- local job_id
- job_id=$(get_job_id "${job_name}" "scope=manual");
- if [ -z "${job_id}" ]; then return; fi
-
- local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
- if [ -z "${api_token}" ]; then
- echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
- return
- fi
-
- local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}/play"
- echoinfo "POST ${url}"
-
- local job_url
- job_url=$(curl --silent --show-error --request POST --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".web_url")
- echoinfo "Manual job '${job_name}' started at: ${job_url}"
-}
-
-function wait_for_job_to_be_done() {
- local job_name="${1}"
- local query_string="${2}"
- local job_id
- job_id=$(get_job_id "${job_name}" "${query_string}")
- if [ -z "${job_id}" ]; then return; fi
-
- local api_token="${API_TOKEN-${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}}"
- if [ -z "${api_token}" ]; then
- echoerr "Please provide an API token with \$API_TOKEN or \$GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN."
- return
- fi
-
- echoinfo "Waiting for the '${job_name}' job to finish..."
-
- local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/jobs/${job_id}"
- echoinfo "GET ${url}"
-
- # In case the job hasn't finished yet. Keep trying until the job times out.
- local interval=30
- local elapsed_seconds=0
- while true; do
- local job_status
- job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${api_token}" "${url}" | jq ".status" | sed -e s/\"//g)
- [[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break
-
- printf "."
- let "elapsed_seconds+=interval"
- sleep ${interval}
- done
-
- local elapsed_minutes=$((elapsed_seconds / 60))
- echoinfo "Waited '${job_name}' for ${elapsed_minutes} minutes."
-
- if [[ "${job_status}" == "failed" ]]; then
- echoerr "The '${job_name}' failed."
- elif [[ "${job_status}" == "manual" ]]; then
- echoinfo "The '${job_name}' is manual."
- else
- echoinfo "The '${job_name}' passed."
- fi
-}
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index b3852355d77..71ee1fd03bf 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -613,31 +613,13 @@ describe Projects::EnvironmentsController do
end
end
- shared_examples_for 'dashboard cannot be embedded' do
- context 'when the embedded flag is included' do
- let(:dashboard_params) { { format: :json, embedded: true } }
-
- it_behaves_like 'the default dashboard'
- end
- end
-
let(:dashboard_params) { { format: :json } }
it_behaves_like 'the default dashboard'
it_behaves_like 'dashboard can be specified'
it_behaves_like 'dashboard can be embedded'
- context 'when multiple dashboards is enabled and embedding metrics is disabled' do
- before do
- stub_feature_flags(gfm_embedded_metrics: false)
- end
-
- it_behaves_like 'the default dashboard'
- it_behaves_like 'dashboard can be specified'
- it_behaves_like 'dashboard cannot be embedded'
- end
-
- context 'when multiple dashboards is disabled and embedding metrics is enabled' do
+ context 'when multiple dashboards is disabled' do
before do
stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
end
@@ -646,19 +628,6 @@ describe Projects::EnvironmentsController do
it_behaves_like 'dashboard cannot be specified'
it_behaves_like 'dashboard can be embedded'
end
-
- context 'when multiple dashboards and embedding metrics are disabled' do
- before do
- stub_feature_flags(
- environment_metrics_show_multiple_dashboards: false,
- gfm_embedded_metrics: false
- )
- end
-
- it_behaves_like 'the default dashboard'
- it_behaves_like 'dashboard cannot be specified'
- it_behaves_like 'dashboard cannot be embedded'
- end
end
describe 'GET #search' do
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 71af75640de..5a60991c1bf 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -6,6 +6,21 @@ describe 'User searches for code' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
+ def submit_search(search, with_send_keys: false)
+ page.within('.search') do
+ field = find_field('search')
+ field.fill_in(with: search)
+
+ if with_send_keys
+ field.send_keys(:enter)
+ else
+ click_button("Go")
+ end
+ end
+
+ click_link('Code')
+ end
+
context 'when signed in' do
before do
project.add_maintainer(user)
@@ -15,12 +30,7 @@ describe 'User searches for code' do
it 'finds a file' do
visit(project_path(project))
- page.within('.search') do
- fill_in('search', with: 'application.js')
- click_button('Go')
- end
-
- click_link('Code')
+ submit_search('application.js')
expect(page).to have_selector('.file-content .code')
expect(page).to have_selector("span.line[lang='javascript']")
@@ -48,6 +58,50 @@ describe 'User searches for code' do
end
end
end
+
+ context 'search code within refs', :js do
+ let(:ref_name) { 'v1.0.0' }
+
+ before do
+ visit(project_tree_path(project, ref_name))
+ submit_search('gitlab-grack', with_send_keys: true)
+ end
+
+ it 'shows ref switcher in code result summary' do
+ expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
+ end
+ it 'persists branch name across search' do
+ find('.btn-search').click
+ expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
+ end
+
+ # this example is use to test the desgine that the refs is not
+ # only repersent the branch as well as the tags.
+ it 'ref swither list all the branchs and tags' do
+ find('.js-project-refs-dropdown').click
+ expect(find('.dropdown-page-one .dropdown-content')).to have_link('sha-starting-with-large-number')
+ expect(find('.dropdown-page-one .dropdown-content')).to have_link('v1.0.0')
+ end
+
+ it 'search result changes when refs switched' do
+ expect(find('.search-results')).not_to have_content('path = gitlab-grack')
+ find('.js-project-refs-dropdown').click
+ find('.dropdown-page-one .dropdown-content').click_link('master')
+ expect(find('.search-results')).to have_content('path = gitlab-grack')
+ end
+ end
+
+ it 'no ref switcher shown in issue result summary', :js do
+ issue = create(:issue, title: 'test', project: project)
+ visit(project_tree_path(project))
+ submit_search('test', with_send_keys: true)
+ expect(page).to have_selector('.js-project-refs-dropdown')
+ page.within('.search-filter') do
+ click_link('Issues')
+ end
+ expect(find(:css, '.search-results')).to have_link(issue.title)
+ expect(page).not_to have_selector('.js-project-refs-dropdown')
+ end
end
context 'when signed out' do
@@ -58,8 +112,7 @@ describe 'User searches for code' do
end
it 'finds code' do
- fill_in('search', with: 'rspec')
- click_button('Go')
+ submit_search('rspec')
page.within('.results') do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
diff --git a/spec/frontend/lib/utils/forms_spec.js b/spec/frontend/lib/utils/forms_spec.js
new file mode 100644
index 00000000000..cac17235f0d
--- /dev/null
+++ b/spec/frontend/lib/utils/forms_spec.js
@@ -0,0 +1,74 @@
+import { serializeForm } from '~/lib/utils/forms';
+
+describe('lib/utils/forms', () => {
+ const createDummyForm = inputs => {
+ const form = document.createElement('form');
+
+ form.innerHTML = inputs
+ .map(({ type, name, value }) => {
+ let str = ``;
+ if (type === 'select') {
+ str = `<select name="${name}">`;
+ value.forEach(v => {
+ if (v.length > 0) {
+ str += `<option value="${v}"></option> `;
+ }
+ });
+ str += `</select>`;
+ } else {
+ str = `<input type="${type}" name="${name}" value="${value}" checked/>`;
+ }
+ return str;
+ })
+ .join('');
+
+ return form;
+ };
+
+ describe('serializeForm', () => {
+ it('returns an object of key values from inputs', () => {
+ const form = createDummyForm([
+ { type: 'text', name: 'foo', value: 'foo-value' },
+ { type: 'text', name: 'bar', value: 'bar-value' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: 'foo-value',
+ bar: 'bar-value',
+ });
+ });
+
+ it('works with select', () => {
+ const form = createDummyForm([
+ { type: 'select', name: 'foo', value: ['foo-value1', 'foo-value2'] },
+ { type: 'text', name: 'bar', value: 'bar-value1' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: 'foo-value1',
+ bar: 'bar-value1',
+ });
+ });
+
+ it('works with multiple inputs of the same name', () => {
+ const form = createDummyForm([
+ { type: 'checkbox', name: 'foo', value: 'foo-value3' },
+ { type: 'checkbox', name: 'foo', value: 'foo-value2' },
+ { type: 'checkbox', name: 'foo', value: 'foo-value1' },
+ { type: 'text', name: 'bar', value: 'bar-value2' },
+ { type: 'text', name: 'bar', value: 'bar-value1' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: ['foo-value3', 'foo-value2', 'foo-value1'],
+ bar: ['bar-value2', 'bar-value1'],
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index d3a76f33679..4541119dd2e 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
-import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import { TEST_HOST } from 'spec/test_constants';
import MonitoringMock, { deploymentData } from '../mock_data';
@@ -17,13 +17,14 @@ describe('Area component', () => {
let mockGraphData;
let areaChart;
let spriteSpy;
+ let store;
beforeEach(() => {
- const store = createStore();
-
+ store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+ store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: true });
[mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
areaChart = shallowMount(Area, {
@@ -36,6 +37,7 @@ describe('Area component', () => {
slots: {
default: mockWidgets,
},
+ store,
});
spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
@@ -107,6 +109,16 @@ describe('Area component', () => {
});
});
+ describe('when exportMetricsToCsvEnabled is disabled', () => {
+ beforeEach(() => {
+ store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: false });
+ });
+
+ it('does not render the Download CSV button', () => {
+ expect(areaChart.contains('glbutton-stub')).toBe(false);
+ });
+ });
+
describe('methods', () => {
describe('formatTooltipText', () => {
const mockDate = deploymentData[0].created_at;
@@ -252,5 +264,23 @@ describe('Area component', () => {
expect(areaChart.vm.yAxisLabel).toBe('CPU');
});
});
+
+ describe('csvText', () => {
+ it('converts data from json to csv', () => {
+ const header = `timestamp,${mockGraphData.y_label}`;
+ const data = mockGraphData.queries[0].result[0].values;
+ const firstRow = `${data[0][0]},${data[0][1]}`;
+
+ expect(areaChart.vm.csvText).toMatch(`^${header}\r\n${firstRow}`);
+ });
+ });
+
+ describe('downloadLink', () => {
+ it('produces a link to download metrics as csv', () => {
+ const link = areaChart.vm.downloadLink;
+
+ expect(link).toContain('blob:');
+ });
+ });
});
});
diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
index 772c94e3180..542a9ced6d7 100644
--- a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
@@ -40,16 +40,6 @@ describe Banzai::Filter::InlineMetricsFilter do
expect(doc.at_css('p').to_s).to include paragraph
expect(doc.at_css('.js-render-metrics')).to be_present
end
-
- context 'when the feature is disabled' do
- before do
- stub_feature_flags(gfm_embedded_metrics: false)
- end
-
- it 'does nothing' do
- expect(doc.to_s).to eq input
- end
- end
end
end
end
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
index fb2186e9d12..a99cd7d6076 100644
--- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -11,16 +11,6 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do
let(:input) { %(<a href="#{url}">example</a>) }
let(:doc) { filter(input) }
- context 'when the feature is disabled' do
- before do
- stub_feature_flags(gfm_embedded_metrics: false)
- end
-
- it 'does nothing' do
- expect(doc.to_s).to eq input
- end
- end
-
context 'without a metrics charts placeholder' do
it 'leaves regular non-metrics links unchanged' do
expect(doc.to_s).to eq input
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index ef52a25f47e..d24f5c45107 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -55,6 +55,17 @@ describe Gitlab::Gfm::UploadsRewriter do
end
end
+ it 'does not rewrite plain links as embedded' do
+ embedded_link = image_uploader.markdown_link
+ plain_image_link = embedded_link.sub(/\A!/, "")
+ text = "#{plain_image_link} and #{embedded_link}"
+
+ moved_text = described_class.new(text, old_project, user).rewrite(new_project)
+
+ expect(moved_text.scan(/!\[.*?\]/).count).to eq(1)
+ expect(moved_text.scan(/\A\[.*?\]/).count).to eq(1)
+ end
+
context "file are stored locally" do
include_examples "files are accessible"
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index a410e4eab45..4676db6b8d8 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -62,14 +62,6 @@ describe Gitlab::Highlight do
expect(lines[2].text).to eq(' """')
end
- context 'since param is present' do
- it 'highlights with the LC starting from "since" param' do
- lines = described_class.highlight(file_name, content, since: 2).lines
-
- expect(lines[0]).to include('LC2')
- end
- end
-
context 'diff highlighting' do
let(:file_name) { 'test.diff' }
let(:content) { "+aaa\n+bbb\n- ccc\n ddd\n"}
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index 95db2ba6a0d..eacf383be7d 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -28,70 +28,24 @@ describe BlobPresenter, :seed_helper do
subject { described_class.new(blob) }
it 'returns highlighted content' do
- expect(Gitlab::Highlight)
- .to receive(:highlight)
- .with(
- 'files/ruby/regex.rb',
- git_blob.data,
- since: nil,
- plain: nil,
- language: nil
- )
+ expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: nil)
subject.highlight
end
it 'returns plain content when :plain is true' do
- expect(Gitlab::Highlight)
- .to receive(:highlight)
- .with(
- 'files/ruby/regex.rb',
- git_blob.data,
- since: nil,
- plain: true,
- language: nil
- )
+ expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: true, language: nil)
subject.highlight(plain: true)
end
- context '"since" and "to" are present' do
- before do
- allow(git_blob)
- .to receive(:data)
- .and_return("line one\nline two\nline 3\nline 4")
- end
-
- it 'returns limited highlighted content' do
- expect(Gitlab::Highlight)
- .to receive(:highlight)
- .with(
- 'files/ruby/regex.rb',
- "line two\nline 3\n",
- since: 2,
- language: nil,
- plain: nil
- )
-
- subject.highlight(since: 2, to: 3)
- end
- end
-
context 'gitlab-language contains a match' do
before do
allow(blob).to receive(:language_from_gitattributes).and_return('ruby')
end
it 'passes language to inner call' do
- expect(Gitlab::Highlight)
- .to receive(:highlight)
- .with(
- 'files/ruby/regex.rb',
- git_blob.data,
- since: nil,
- plain: nil,
- language: 'ruby'
- )
+ expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: 'ruby')
subject.highlight
end
diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb
index 9837ba806db..b7382cea93c 100644
--- a/spec/support/shared_examples/relative_positioning_shared_examples.rb
+++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb
@@ -17,8 +17,8 @@ RSpec.shared_examples 'a class that supports relative positioning' do
describe '.move_nulls_to_end' do
it 'moves items with null relative_position to the end' do
- skip("#{item1} has a default relative position") if item1.relative_position
- skip("#{item2} has a default relative position") if item2.relative_position
+ item1.update!(relative_position: nil)
+ item2.update!(relative_position: nil)
described_class.move_nulls_to_end([item1, item2])
@@ -28,7 +28,7 @@ RSpec.shared_examples 'a class that supports relative positioning' do
end
it 'moves the item near the start position when there are no existing positions' do
- skip("#{item1} has a default relative position") if item1.relative_position
+ item1.update!(relative_position: nil)
described_class.move_nulls_to_end([item1])