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-12-17 14:59:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-17 14:59:07 +0300
commit8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch)
tree544930fb309b30317ae9797a9683768705d664c4 /app/assets/javascripts/projects
parent4b1de649d0168371549608993deac953eb692019 (diff)
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects')
-rw-r--r--app/assets/javascripts/projects/default_project_templates.js4
-rw-r--r--app/assets/javascripts/projects/default_sample_data_templates.js12
-rw-r--r--app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue6
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/app.vue234
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue151
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue7
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/constants.js6
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql14
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql17
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/index.js63
-rw-r--r--app/assets/javascripts/projects/project_new.js7
-rw-r--r--app/assets/javascripts/projects/settings/access_dropdown.js7
-rw-r--r--app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue79
-rw-r--r--app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js21
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue5
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue6
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/index.js2
17 files changed, 572 insertions, 69 deletions
diff --git a/app/assets/javascripts/projects/default_project_templates.js b/app/assets/javascripts/projects/default_project_templates.js
index a6019e9c01b..bc3b29cde0a 100644
--- a/app/assets/javascripts/projects/default_project_templates.js
+++ b/app/assets/javascripts/projects/default_project_templates.js
@@ -1,6 +1,10 @@
import { s__ } from '~/locale';
export default {
+ sample: {
+ text: s__('ProjectTemplates|Sample GitLab Project'),
+ icon: '.template-option .icon-sample',
+ },
rails: {
text: s__('ProjectTemplates|Ruby on Rails'),
icon: '.template-option .icon-rails',
diff --git a/app/assets/javascripts/projects/default_sample_data_templates.js b/app/assets/javascripts/projects/default_sample_data_templates.js
deleted file mode 100644
index 7c45e7ac62f..00000000000
--- a/app/assets/javascripts/projects/default_sample_data_templates.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { s__ } from '~/locale';
-
-export default {
- basic: {
- text: s__('ProjectTemplates|Basic'),
- icon: '.template-option .icon-basic',
- },
- serenity_valley: {
- text: s__('ProjectTemplates|Serenity Valley'),
- icon: '.template-option .icon-serenity_valley',
- },
-};
diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue
index f404e6030f4..2e16071e563 100644
--- a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue
+++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue
@@ -12,6 +12,7 @@ import ciCdProjectIllustration from '../illustrations/ci-cd-project.svg';
const BLANK_PANEL = 'blank_project';
const CI_CD_PANEL = 'cicd_for_external_repo';
+const LAST_ACTIVE_TAB_KEY = 'new_project_last_active_tab';
const PANELS = [
{
name: BLANK_PANEL,
@@ -105,7 +106,7 @@ export default {
this.handleLocationHashChange();
if (this.hasErrors) {
- this.activeTab = BLANK_PANEL;
+ this.activeTab = localStorage.getItem(LAST_ACTIVE_TAB_KEY) || BLANK_PANEL;
}
window.addEventListener('hashchange', () => {
@@ -127,6 +128,9 @@ export default {
handleLocationHashChange() {
this.activeTab = window.location.hash.substring(1) || null;
+ if (this.activeTab) {
+ localStorage.setItem(LAST_ACTIVE_TAB_KEY, this.activeTab);
+ }
},
},
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
index c6e2b2e1140..4bf837faed1 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
@@ -1,66 +1,203 @@
<script>
import dateFormat from 'dateformat';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { __, sprintf } from '~/locale';
+import { GlAlert, GlSkeletonLoader } from '@gitlab/ui';
+import { __, s__, sprintf } from '~/locale';
import { getDateInPast } from '~/lib/utils/datetime_utility';
+import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql';
+import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql';
import StatisticsList from './statistics_list.vue';
import PipelinesAreaChart from './pipelines_area_chart.vue';
import {
CHART_CONTAINER_HEIGHT,
- INNER_CHART_HEIGHT,
- X_AXIS_LABEL_ROTATION,
- X_AXIS_TITLE_OFFSET,
CHART_DATE_FORMAT,
+ DEFAULT,
+ INNER_CHART_HEIGHT,
+ LOAD_ANALYTICS_FAILURE,
+ LOAD_PIPELINES_FAILURE,
ONE_WEEK_AGO_DAYS,
ONE_MONTH_AGO_DAYS,
+ PARSE_FAILURE,
+ UNSUPPORTED_DATA,
+ X_AXIS_LABEL_ROTATION,
+ X_AXIS_TITLE_OFFSET,
} from '../constants';
+const defaultCountValues = {
+ totalPipelines: {
+ count: 0,
+ },
+ successfulPipelines: {
+ count: 0,
+ },
+};
+
+const defaultAnalyticsValues = {
+ weekPipelinesTotals: [],
+ weekPipelinesLabels: [],
+ weekPipelinesSuccessful: [],
+ monthPipelinesLabels: [],
+ monthPipelinesTotals: [],
+ monthPipelinesSuccessful: [],
+ yearPipelinesLabels: [],
+ yearPipelinesTotals: [],
+ yearPipelinesSuccessful: [],
+ pipelineTimesLabels: [],
+ pipelineTimesValues: [],
+};
+
export default {
components: {
- StatisticsList,
+ GlAlert,
GlColumnChart,
+ GlSkeletonLoader,
+ StatisticsList,
PipelinesAreaChart,
},
- props: {
- counts: {
- type: Object,
- required: true,
- },
- timesChartData: {
- type: Object,
- required: true,
- },
- lastWeekChartData: {
- type: Object,
- required: true,
- },
- lastMonthChartData: {
- type: Object,
- required: true,
- },
- lastYearChartData: {
- type: Object,
- required: true,
+ inject: {
+ projectPath: {
+ type: String,
+ default: '',
},
},
data() {
return {
- timesChartTransformedData: [
- {
- name: 'full',
- data: this.mergeLabelsAndValues(this.timesChartData.labels, this.timesChartData.values),
- },
- ],
+ counts: {
+ ...defaultCountValues,
+ },
+ analytics: {
+ ...defaultAnalyticsValues,
+ },
+ showFailureAlert: false,
+ failureType: null,
};
},
+ apollo: {
+ counts: {
+ query: getPipelineCountByStatus,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data?.project;
+ },
+ error() {
+ this.reportFailure(LOAD_PIPELINES_FAILURE);
+ },
+ },
+ analytics: {
+ query: getProjectPipelineStatistics,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data?.project?.pipelineAnalytics;
+ },
+ error() {
+ this.reportFailure(LOAD_ANALYTICS_FAILURE);
+ },
+ },
+ },
computed: {
+ failure() {
+ switch (this.failureType) {
+ case LOAD_ANALYTICS_FAILURE:
+ return {
+ text: this.$options.errorTexts[LOAD_ANALYTICS_FAILURE],
+ variant: 'danger',
+ };
+ case PARSE_FAILURE:
+ return {
+ text: this.$options.errorTexts[PARSE_FAILURE],
+ variant: 'danger',
+ };
+ case UNSUPPORTED_DATA:
+ return {
+ text: this.$options.errorTexts[UNSUPPORTED_DATA],
+ variant: 'info',
+ };
+ default:
+ return {
+ text: this.$options.errorTexts[DEFAULT],
+ variant: 'danger',
+ };
+ }
+ },
+ successRatio() {
+ const { successfulPipelines, failedPipelines } = this.counts;
+ const successfulCount = successfulPipelines?.count;
+ const failedCount = failedPipelines?.count;
+ const ratio = (successfulCount / (successfulCount + failedCount)) * 100;
+
+ return failedCount === 0 ? 100 : ratio;
+ },
+ formattedCounts() {
+ const {
+ totalPipelines,
+ successfulPipelines,
+ failedPipelines,
+ totalPipelineDuration,
+ } = this.counts;
+
+ return {
+ total: totalPipelines?.count,
+ success: successfulPipelines?.count,
+ failed: failedPipelines?.count,
+ successRatio: this.successRatio,
+ totalDuration: totalPipelineDuration,
+ };
+ },
areaCharts() {
const { lastWeek, lastMonth, lastYear } = this.$options.chartTitles;
+ let areaChartsData = [];
+ try {
+ areaChartsData = [
+ this.buildAreaChartData(lastWeek, this.lastWeekChartData),
+ this.buildAreaChartData(lastMonth, this.lastMonthChartData),
+ this.buildAreaChartData(lastYear, this.lastYearChartData),
+ ];
+ } catch {
+ areaChartsData = [];
+ this.reportFailure(PARSE_FAILURE);
+ }
+
+ return areaChartsData;
+ },
+ lastWeekChartData() {
+ return {
+ labels: this.analytics.weekPipelinesLabels,
+ totals: this.analytics.weekPipelinesTotals,
+ success: this.analytics.weekPipelinesSuccessful,
+ };
+ },
+ lastMonthChartData() {
+ return {
+ labels: this.analytics.monthPipelinesLabels,
+ totals: this.analytics.monthPipelinesTotals,
+ success: this.analytics.monthPipelinesSuccessful,
+ };
+ },
+ lastYearChartData() {
+ return {
+ labels: this.analytics.yearPipelinesLabels,
+ totals: this.analytics.yearPipelinesTotals,
+ success: this.analytics.yearPipelinesSuccessful,
+ };
+ },
+ timesChartTransformedData() {
return [
- this.buildAreaChartData(lastWeek, this.lastWeekChartData),
- this.buildAreaChartData(lastMonth, this.lastMonthChartData),
- this.buildAreaChartData(lastYear, this.lastYearChartData),
+ {
+ name: 'full',
+ data: this.mergeLabelsAndValues(
+ this.analytics.pipelineTimesLabels,
+ this.analytics.pipelineTimesValues,
+ ),
+ },
];
},
},
@@ -85,6 +222,13 @@ export default {
],
};
},
+ hideAlert() {
+ this.showFailureAlert = false;
+ },
+ reportFailure(type) {
+ this.showFailureAlert = true;
+ this.failureType = type;
+ },
},
chartContainerHeight: CHART_CONTAINER_HEIGHT,
timesChartOptions: {
@@ -96,6 +240,16 @@ export default {
nameGap: X_AXIS_TITLE_OFFSET,
},
},
+ errorTexts: {
+ [LOAD_ANALYTICS_FAILURE]: s__(
+ 'PipelineCharts|An error has ocurred when retrieving the analytics data',
+ ),
+ [LOAD_PIPELINES_FAILURE]: s__(
+ 'PipelineCharts|An error has ocurred when retrieving the pipelines data',
+ ),
+ [PARSE_FAILURE]: s__('PipelineCharts|There was an error parsing the data for the charts.'),
+ [DEFAULT]: s__('PipelineCharts|An unknown error occurred while processing CI/CD analytics.'),
+ },
get chartTitles() {
const today = dateFormat(new Date(), CHART_DATE_FORMAT);
const pastDate = timeScale =>
@@ -116,13 +270,17 @@ export default {
</script>
<template>
<div>
- <div class="mb-3">
+ <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert">
+ {{ failure.text }}
+ </gl-alert>
+ <div class="gl-mb-3">
<h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3>
</div>
- <h4 class="my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
+ <h4 class="gl-my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
<div class="row">
<div class="col-md-6">
- <statistics-list :counts="counts" />
+ <gl-skeleton-loader v-if="$apollo.queries.counts.loading" :lines="5" />
+ <statistics-list v-else :counts="formattedCounts" />
</div>
<div class="col-md-6">
<strong>
@@ -139,7 +297,7 @@ export default {
</div>
</div>
<hr />
- <h4 class="my-4">{{ __('Pipelines charts') }}</h4>
+ <h4 class="gl-my-4">{{ __('Pipelines charts') }}</h4>
<pipelines-area-chart
v-for="(chart, index) in areaCharts"
:key="index"
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue b/app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue
new file mode 100644
index 00000000000..c6e2b2e1140
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/components/app_legacy.vue
@@ -0,0 +1,151 @@
+<script>
+import dateFormat from 'dateformat';
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import { __, sprintf } from '~/locale';
+import { getDateInPast } from '~/lib/utils/datetime_utility';
+import StatisticsList from './statistics_list.vue';
+import PipelinesAreaChart from './pipelines_area_chart.vue';
+import {
+ CHART_CONTAINER_HEIGHT,
+ INNER_CHART_HEIGHT,
+ X_AXIS_LABEL_ROTATION,
+ X_AXIS_TITLE_OFFSET,
+ CHART_DATE_FORMAT,
+ ONE_WEEK_AGO_DAYS,
+ ONE_MONTH_AGO_DAYS,
+} from '../constants';
+
+export default {
+ components: {
+ StatisticsList,
+ GlColumnChart,
+ PipelinesAreaChart,
+ },
+ props: {
+ counts: {
+ type: Object,
+ required: true,
+ },
+ timesChartData: {
+ type: Object,
+ required: true,
+ },
+ lastWeekChartData: {
+ type: Object,
+ required: true,
+ },
+ lastMonthChartData: {
+ type: Object,
+ required: true,
+ },
+ lastYearChartData: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ timesChartTransformedData: [
+ {
+ name: 'full',
+ data: this.mergeLabelsAndValues(this.timesChartData.labels, this.timesChartData.values),
+ },
+ ],
+ };
+ },
+ computed: {
+ areaCharts() {
+ const { lastWeek, lastMonth, lastYear } = this.$options.chartTitles;
+
+ return [
+ this.buildAreaChartData(lastWeek, this.lastWeekChartData),
+ this.buildAreaChartData(lastMonth, this.lastMonthChartData),
+ this.buildAreaChartData(lastYear, this.lastYearChartData),
+ ];
+ },
+ },
+ methods: {
+ mergeLabelsAndValues(labels, values) {
+ return labels.map((label, index) => [label, values[index]]);
+ },
+ buildAreaChartData(title, data) {
+ const { labels, totals, success } = data;
+
+ return {
+ title,
+ data: [
+ {
+ name: 'all',
+ data: this.mergeLabelsAndValues(labels, totals),
+ },
+ {
+ name: 'success',
+ data: this.mergeLabelsAndValues(labels, success),
+ },
+ ],
+ };
+ },
+ },
+ chartContainerHeight: CHART_CONTAINER_HEIGHT,
+ timesChartOptions: {
+ height: INNER_CHART_HEIGHT,
+ xAxis: {
+ axisLabel: {
+ rotate: X_AXIS_LABEL_ROTATION,
+ },
+ nameGap: X_AXIS_TITLE_OFFSET,
+ },
+ },
+ get chartTitles() {
+ const today = dateFormat(new Date(), CHART_DATE_FORMAT);
+ const pastDate = timeScale =>
+ dateFormat(getDateInPast(new Date(), timeScale), CHART_DATE_FORMAT);
+ return {
+ lastWeek: sprintf(__('Pipelines for last week (%{oneWeekAgo} - %{today})'), {
+ oneWeekAgo: pastDate(ONE_WEEK_AGO_DAYS),
+ today,
+ }),
+ lastMonth: sprintf(__('Pipelines for last month (%{oneMonthAgo} - %{today})'), {
+ oneMonthAgo: pastDate(ONE_MONTH_AGO_DAYS),
+ today,
+ }),
+ lastYear: __('Pipelines for last year'),
+ };
+ },
+};
+</script>
+<template>
+ <div>
+ <div class="mb-3">
+ <h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3>
+ </div>
+ <h4 class="my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
+ <div class="row">
+ <div class="col-md-6">
+ <statistics-list :counts="counts" />
+ </div>
+ <div class="col-md-6">
+ <strong>
+ {{ __('Duration for the last 30 commits') }}
+ </strong>
+ <gl-column-chart
+ :height="$options.chartContainerHeight"
+ :option="$options.timesChartOptions"
+ :bars="timesChartTransformedData"
+ :y-axis-title="__('Minutes')"
+ :x-axis-title="__('Commit')"
+ x-axis-type="category"
+ />
+ </div>
+ </div>
+ <hr />
+ <h4 class="my-4">{{ __('Pipelines charts') }}</h4>
+ <pipelines-area-chart
+ v-for="(chart, index) in areaCharts"
+ :key="index"
+ :chart-data="chart.data"
+ >
+ {{ chart.title }}
+ </pipelines-area-chart>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue b/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue
index aa59717ddcd..94cecd2e479 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue
@@ -1,7 +1,10 @@
<script>
import { formatTime } from '~/lib/utils/datetime_utility';
+import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import { s__, n__ } from '~/locale';
+const defaultPrecision = 2;
+
export default {
props: {
counts: {
@@ -14,6 +17,8 @@ export default {
return formatTime(this.counts.totalDuration);
},
statistics() {
+ const formatter = getFormatter(SUPPORTED_FORMATS.percentHundred);
+
return [
{
title: s__('PipelineCharts|Total:'),
@@ -29,7 +34,7 @@ export default {
},
{
title: s__('PipelineCharts|Success ratio:'),
- value: `${this.counts.successRatio}%`,
+ value: formatter(this.counts.successRatio, defaultPrecision),
},
{
title: s__('PipelineCharts|Total duration:'),
diff --git a/app/assets/javascripts/projects/pipelines/charts/constants.js b/app/assets/javascripts/projects/pipelines/charts/constants.js
index 5dbe3c01100..079e23943c1 100644
--- a/app/assets/javascripts/projects/pipelines/charts/constants.js
+++ b/app/assets/javascripts/projects/pipelines/charts/constants.js
@@ -11,3 +11,9 @@ export const ONE_WEEK_AGO_DAYS = 7;
export const ONE_MONTH_AGO_DAYS = 31;
export const CHART_DATE_FORMAT = 'dd mmm';
+
+export const DEFAULT = 'default';
+export const PARSE_FAILURE = 'parse_failure';
+export const LOAD_ANALYTICS_FAILURE = 'load_analytics_failure';
+export const LOAD_PIPELINES_FAILURE = 'load_analytics_failure';
+export const UNSUPPORTED_DATA = 'unsupported_data';
diff --git a/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql
new file mode 100644
index 00000000000..eb0dbf8dd16
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql
@@ -0,0 +1,14 @@
+query getPipelineCountByStatus($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ totalPipelines: pipelines {
+ count
+ }
+ successfulPipelines: pipelines(status: SUCCESS) {
+ count
+ }
+ failedPipelines: pipelines(status: FAILED) {
+ count
+ }
+ totalPipelineDuration
+ }
+}
diff --git a/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql
new file mode 100644
index 00000000000..18b645f8831
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/graphql/queries/get_project_pipeline_statistics.query.graphql
@@ -0,0 +1,17 @@
+query getProjectPipelineStatistics($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ pipelineAnalytics {
+ weekPipelinesTotals
+ weekPipelinesLabels
+ weekPipelinesSuccessful
+ monthPipelinesLabels
+ monthPipelinesTotals
+ monthPipelinesSuccessful
+ yearPipelinesLabels
+ yearPipelinesTotals
+ yearPipelinesSuccessful
+ pipelineTimesLabels
+ pipelineTimesValues
+ }
+ }
+}
diff --git a/app/assets/javascripts/projects/pipelines/charts/index.js b/app/assets/javascripts/projects/pipelines/charts/index.js
index eef1bc2d28b..f6e79f0ab51 100644
--- a/app/assets/javascripts/projects/pipelines/charts/index.js
+++ b/app/assets/javascripts/projects/pipelines/charts/index.js
@@ -1,8 +1,20 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import ProjectPipelinesChartsLegacy from './components/app_legacy.vue';
import ProjectPipelinesCharts from './components/app.vue';
-export default () => {
- const el = document.querySelector('#js-project-pipelines-charts-app');
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
+
+const mountPipelineChartsApp = el => {
+ // Not all of the values will be defined since some them will be
+ // empty depending on the value of the graphql_pipeline_analytics
+ // feature flag, once the rollout of the feature flag is completed
+ // the undefined values will be deleted
const {
countsFailed,
countsSuccess,
@@ -20,22 +32,48 @@ export default () => {
lastYearChartLabels,
lastYearChartTotals,
lastYearChartSuccess,
+ projectPath,
} = el.dataset;
- const parseAreaChartData = (labels, totals, success) => ({
- labels: JSON.parse(labels),
- totals: JSON.parse(totals),
- success: JSON.parse(success),
- });
+ const parseAreaChartData = (labels, totals, success) => {
+ let parsedData = {};
+
+ try {
+ parsedData = {
+ labels: JSON.parse(labels),
+ totals: JSON.parse(totals),
+ success: JSON.parse(success),
+ };
+ } catch {
+ parsedData = {};
+ }
+
+ return parsedData;
+ };
+
+ if (gon?.features?.graphqlPipelineAnalytics) {
+ return new Vue({
+ el,
+ name: 'ProjectPipelinesChartsApp',
+ components: {
+ ProjectPipelinesCharts,
+ },
+ apolloProvider,
+ provide: {
+ projectPath,
+ },
+ render: createElement => createElement(ProjectPipelinesCharts, {}),
+ });
+ }
return new Vue({
el,
- name: 'ProjectPipelinesChartsApp',
+ name: 'ProjectPipelinesChartsAppLegacy',
components: {
- ProjectPipelinesCharts,
+ ProjectPipelinesChartsLegacy,
},
render: createElement =>
- createElement(ProjectPipelinesCharts, {
+ createElement(ProjectPipelinesChartsLegacy, {
props: {
counts: {
failed: countsFailed,
@@ -67,3 +105,8 @@ export default () => {
}),
});
};
+
+export default () => {
+ const el = document.querySelector('#js-project-pipelines-charts-app');
+ return !el ? {} : mountPipelineChartsApp(el);
+};
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index d74a2d06786..d54a48cc444 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,6 +1,5 @@
import $ from 'jquery';
import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates';
-import DEFAULT_SAMPLE_DATA_TEMPLATES from '~/projects/default_sample_data_templates';
import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
import {
convertToTitleCase,
@@ -26,12 +25,14 @@ const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingPr
};
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
+ // eslint-disable-next-line @gitlab/no-global-event-off
$projectNameInput.off('keyup change').on('keyup change', () => {
onProjectNameChange($projectNameInput, $projectPathInput);
hasUserDefinedProjectName = $projectNameInput.val().trim().length > 0;
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
});
+ // eslint-disable-next-line @gitlab/no-global-event-off
$projectPathInput.off('keyup change').on('keyup change', () => {
onProjectPathChange($projectNameInput, $projectPathInput, hasUserDefinedProjectName);
hasUserDefinedProjectPath = $projectPathInput.val().trim().length > 0;
@@ -137,6 +138,7 @@ const bindEvents = () => {
target.focus();
})
.on('hide.bs.popover', () => {
+ // eslint-disable-next-line @gitlab/no-global-event-off
$(document).off('click.popover touchstart.popover');
});
}
@@ -147,8 +149,7 @@ const bindEvents = () => {
$selectedIcon.empty();
const value = $(this).val();
- const selectedTemplate =
- DEFAULT_PROJECT_TEMPLATES[value] || DEFAULT_SAMPLE_DATA_TEMPLATES[value];
+ const selectedTemplate = DEFAULT_PROJECT_TEMPLATES[value];
$selectedTemplateText.text(selectedTemplate.text);
$(selectedTemplate.icon)
.clone()
diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js
index 3ca5bca4bf2..cb4fd5265da 100644
--- a/app/assets/javascripts/projects/settings/access_dropdown.js
+++ b/app/assets/javascripts/projects/settings/access_dropdown.js
@@ -48,11 +48,12 @@ export default class AccessDropdown {
clicked: options => {
const { $el, e } = options;
const item = options.selectedObj;
+ const fossWithMergeAccess = !this.hasLicense && this.accessLevel === ACCESS_LEVELS.MERGE;
e.preventDefault();
- if (!this.hasLicense) {
- // We're not multiselecting quite yet with FOSS:
+ if (fossWithMergeAccess) {
+ // We're not multiselecting quite yet in "Merge" access dropdown, on FOSS:
// remove all preselected items before selecting this item
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37499
this.accessLevelsData.forEach(level => {
@@ -62,7 +63,7 @@ export default class AccessDropdown {
if ($el.is('.is-active')) {
if (this.noOneObj) {
- if (item.id === this.noOneObj.id && this.hasLicense) {
+ if (item.id === this.noOneObj.id && !fossWithMergeAccess) {
// remove all others selected items
this.accessLevelsData.forEach(level => {
if (level.id !== item.id) {
diff --git a/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
new file mode 100644
index 00000000000..a4924033c1e
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
@@ -0,0 +1,79 @@
+<script>
+import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
+import { __ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+
+const DEFAULT_ERROR_MESSAGE = __('An error occurred while updating the configuration.');
+
+export default {
+ components: {
+ GlAlert,
+ GlToggle,
+ GlTooltip,
+ },
+ props: {
+ isDisabledAndUnoverridable: {
+ type: Boolean,
+ required: true,
+ },
+ isEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ updatePath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isLoading: false,
+ isSharedRunnerEnabled: false,
+ errorMessage: null,
+ };
+ },
+ created() {
+ this.isSharedRunnerEnabled = this.isEnabled;
+ },
+ methods: {
+ toggleSharedRunners() {
+ this.isLoading = true;
+ this.errorMessage = null;
+
+ axios
+ .post(this.updatePath)
+ .then(() => {
+ this.isLoading = false;
+ this.isSharedRunnerEnabled = !this.isSharedRunnerEnabled;
+ })
+ .catch(error => {
+ this.isLoading = false;
+ this.errorMessage = error.response?.data?.error || DEFAULT_ERROR_MESSAGE;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <section class="gl-mt-5">
+ <gl-alert v-if="errorMessage" class="gl-mb-3" variant="danger" :dismissible="false">
+ {{ errorMessage }}
+ </gl-alert>
+ <div ref="sharedRunnersToggle">
+ <gl-toggle
+ :disabled="isDisabledAndUnoverridable"
+ :is-loading="isLoading"
+ :label="__('Enable shared runners for this project')"
+ :value="isSharedRunnerEnabled"
+ data-testid="toggle-shared-runners"
+ @change="toggleSharedRunners"
+ />
+ </div>
+ <gl-tooltip v-if="isDisabledAndUnoverridable" :target="() => $refs.sharedRunnersToggle">
+ {{ __('Shared runners are disabled on group level') }}
+ </gl-tooltip>
+ </section>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js b/app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js
new file mode 100644
index 00000000000..c5d45fe6fed
--- /dev/null
+++ b/app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import SharedRunnersToggle from '~/projects/settings/components/shared_runners_toggle.vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+
+export default (containerId = 'toggle-shared-runners-form') => {
+ const containerEl = document.getElementById(containerId);
+ const { isDisabledAndUnoverridable, isEnabled, updatePath } = containerEl.dataset;
+
+ return new Vue({
+ el: containerEl,
+ render(createElement) {
+ return createElement(SharedRunnersToggle, {
+ props: {
+ isDisabledAndUnoverridable: parseBoolean(isDisabledAndUnoverridable),
+ isEnabled: parseBoolean(isEnabled),
+ updatePath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
index df7d9b56aed..a07c57c42cb 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
@@ -30,6 +30,10 @@ export default {
required: false,
default: '',
},
+ customEmailEnabled: {
+ type: Boolean,
+ required: false,
+ },
selectedTemplate: {
type: String,
required: false,
@@ -140,6 +144,7 @@ export default {
:is-enabled="isEnabled"
:incoming-email="incomingEmail"
:custom-email="updatedCustomEmail"
+ :custom-email-enabled="customEmailEnabled"
:initial-selected-template="selectedTemplate"
:initial-outgoing-name="outgoingName"
:initial-project-key="projectKey"
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
index 5d120fd0b3f..2896cb491b5 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
@@ -31,6 +31,10 @@ export default {
required: false,
default: '',
},
+ customEmailEnabled: {
+ type: Boolean,
+ required: false,
+ },
initialSelectedTemplate: {
type: String,
required: false,
@@ -69,7 +73,7 @@ export default {
return [''].concat(this.templates);
},
hasProjectKeySupport() {
- return Boolean(this.glFeatures.serviceDeskCustomAddress);
+ return Boolean(this.customEmailEnabled);
},
email() {
return this.customEmail || this.incomingEmail;
diff --git a/app/assets/javascripts/projects/settings_service_desk/index.js b/app/assets/javascripts/projects/settings_service_desk/index.js
index c73163788ef..8f9828dd73d 100644
--- a/app/assets/javascripts/projects/settings_service_desk/index.js
+++ b/app/assets/javascripts/projects/settings_service_desk/index.js
@@ -18,6 +18,7 @@ export default () => {
endpoint: dataset.endpoint,
incomingEmail: dataset.incomingEmail,
customEmail: dataset.customEmail,
+ customEmailEnabled: parseBoolean(dataset.customEmailEnabled),
selectedTemplate: dataset.selectedTemplate,
outgoingName: dataset.outgoingName,
projectKey: dataset.projectKey,
@@ -31,6 +32,7 @@ export default () => {
endpoint: this.endpoint,
incomingEmail: this.incomingEmail,
customEmail: this.customEmail,
+ customEmailEnabled: this.customEmailEnabled,
selectedTemplate: this.selectedTemplate,
outgoingName: this.outgoingName,
projectKey: this.projectKey,