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--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/clusters_list/components/clusters.vue4
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue47
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js8
-rw-r--r--app/assets/javascripts/pages/projects/settings/operations/show/index.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js9
-rw-r--r--app/assets/stylesheets/pages/error_list.scss52
-rw-r--r--app/assets/stylesheets/pages/groups.scss2
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/projects/settings/operations/show.html.haml1
-rw-r--r--changelogs/unreleased/208885-optimize-ci_pipeline-counters-related-to-the-ci-pipeline.yml5
-rw-r--r--changelogs/unreleased/208936.yml5
-rw-r--r--db/migrate/20200306170321_add_index_on_user_id_and_created_at_to_ci_pipelines.rb19
-rw-r--r--db/schema.rb2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql58
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json187
-rw-r--r--doc/api/graphql/reference/index.md11
-rw-r--r--doc/ci/jenkins/index.md28
-rw-r--r--locale/gitlab.pot42
-rw-r--r--spec/frontend/clusters_list/mock_data.js5
-rw-r--r--spec/frontend/lib/utils/datetime_utility_spec.js17
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb72
30 files changed, 534 insertions, 81 deletions
diff --git a/Gemfile b/Gemfile
index 3f38cb7d62c..62a65cd97ef 100644
--- a/Gemfile
+++ b/Gemfile
@@ -149,7 +149,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '0.0.10'
-gem 'rouge', '~> 3.16.0'
+gem 'rouge', '~> 3.17.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index 169b30c2c6e..89cee333738 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -889,7 +889,7 @@ GEM
retriable (3.1.2)
rinku (2.0.0)
rotp (2.1.2)
- rouge (3.16.0)
+ rouge (3.17.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@@ -1346,7 +1346,7 @@ DEPENDENCIES
request_store (~> 1.3)
responders (~> 3.0)
retriable (~> 3.1.2)
- rouge (~> 3.16.0)
+ rouge (~> 3.17.0)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 4.0.0.beta4)
diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue
index a1b581dc627..f9f23fd556f 100644
--- a/app/assets/javascripts/clusters_list/components/clusters.vue
+++ b/app/assets/javascripts/clusters_list/components/clusters.vue
@@ -28,6 +28,10 @@ export default {
label: __('Size'),
},
{
+ key: 'memory',
+ label: __('Total memory (GB)'),
+ },
+ {
key: 'clusterType',
label: __('Cluster level'),
formatter: value => CLUSTER_TYPES[value],
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index 80a88489545..0e160e8d568 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -20,7 +20,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale';
import { isEmpty } from 'lodash';
-export const tableDataClass = 'table-col d-flex d-sm-table-cell align-items-center';
+export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center';
export default {
FIRST_PAGE: 1,
@@ -35,7 +35,7 @@ export default {
key: 'error',
label: __('Error'),
thClass: 'w-60p',
- tdClass: `${tableDataClass} px-3`,
+ tdClass: `${tableDataClass} px-3 rounded-top`,
},
{
key: 'events',
@@ -58,11 +58,11 @@ export default {
{
key: 'status',
label: '',
- tdClass: `${tableDataClass} text-center`,
+ tdClass: `table-col d-none d-md-table-cell align-items-center pl-md-0`,
},
{
key: 'details',
- tdClass: 'table-col d-sm-none d-flex align-items-center',
+ tdClass: 'table-col d-md-none d-flex align-items-center rounded-bottom bg-secondary',
thClass: 'invisible w-0',
},
],
@@ -221,7 +221,7 @@ export default {
<div class="error-list">
<div v-if="errorTrackingEnabled">
<div class="row flex-column flex-sm-row align-items-sm-center row-top m-0 mt-sm-2 p-0 p-sm-3">
- <div class="search-box flex-fill mr-sm-2 my-3 m-sm-0 p-3 p-sm-0">
+ <div class="search-box flex-fill mr-sm-2 my-3 m-sm-0 p-3 p-sm-0 bg-secondary">
<div class="filtered-search-box mb-0">
<gl-dropdown
:text="__('Recent searches')"
@@ -321,25 +321,25 @@ export default {
</div>
<template v-else>
- <h4 class="d-block d-sm-none my-3">{{ __('Open errors') }}</h4>
+ <h4 class="d-block d-md-none my-3">{{ __('Open errors') }}</h4>
<gl-table
- class="mt-3"
+ class="error-list-table mt-3"
:items="errors"
:fields="$options.fields"
:show-empty="true"
fixed
- stacked="sm"
+ stacked="md"
tbody-tr-class="table-row mb-4"
>
<template #head(error)>
- <div class="d-none d-sm-block">{{ __('Open errors') }}</div>
+ <div class="d-none d-md-block">{{ __('Open errors') }}</div>
</template>
<template #head(events)="data">
- <div class="text-sm-right">{{ data.label }}</div>
+ <div class="text-md-right">{{ data.label }}</div>
</template>
<template #head(users)="data">
- <div class="text-sm-right">{{ data.label }}</div>
+ <div class="text-md-right">{{ data.label }}</div>
</template>
<template #cell(error)="errors">
@@ -361,7 +361,7 @@ export default {
</template>
<template #cell(lastSeen)="errors">
- <div class="text-md-left text-right">
+ <div class="text-lg-left text-right">
<time-ago :time="errors.item.lastSeen" class="text-secondary" />
</div>
</template>
@@ -381,9 +381,28 @@ export default {
</template>
<template #cell(details)="errors">
<gl-button
+ category="primary"
+ variant="info"
+ block
+ class="mb-1 mt-2"
+ @click="updateIssueStatus(errors.item.id, 'resolved')"
+ >
+ {{ __('Resolve') }}
+ </gl-button>
+ <gl-button
+ category="secondary"
+ variant="default"
+ block
+ class="mb-2"
+ @click="updateIssueStatus(errors.item.id, 'ignored')"
+ >
+ {{ __('Ignore') }}
+ </gl-button>
+ <gl-button
:href="getDetailsLink(errors.item.id)"
- variant="outline-info"
- class="d-block"
+ category="secondary"
+ variant="info"
+ class="d-block mb-2"
>
{{ __('More details') }}
</gl-button>
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index a70bab013c6..f6077673ad5 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -566,6 +566,14 @@ export const getDateInPast = (date, daysInPast) =>
export const getDateInFuture = (date, daysInFuture) =>
new Date(newDate(date).setDate(date.getDate() + daysInFuture));
+/**
+ * Checks if a given date-instance was created with a valid date
+ *
+ * @param {Date} date
+ * @returns boolean
+ */
+export const isValidDate = date => date instanceof Date && !Number.isNaN(date.getTime());
+
/*
* Appending T00:00:00 makes JS assume local time and prevents it from shifting the date
* to match the user's time zone. We want to display the date in server time for now, to
diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
index a32c188909c..721d4a31fe4 100644
--- a/app/assets/javascripts/pages/projects/settings/operations/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
@@ -7,5 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm();
mountOperationSettings();
mountGrafanaIntegration();
- initSettingsPanels();
+ if (!IS_EE) {
+ initSettingsPanels();
+ }
});
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 7c71463c949..b85be8b9652 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { escape as esc } from 'lodash';
import { n__, s__, sprintf } from '~/locale';
import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue';
@@ -35,7 +35,7 @@ export default {
'mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch',
),
{
- commitsBehindLinkStart: `<a href="${_.escape(this.mr.targetBranchPath)}">`,
+ commitsBehindLinkStart: `<a href="${esc(this.mr.targetBranchPath)}">`,
commitsBehind: n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount),
commitsBehindLinkEnd: '</a>',
},
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index 90fb254ecca..d81e99d3c09 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isNumber } from 'lodash';
import ArtifactsApp from './artifacts_list_app.vue';
import Deployment from './deployment/deployment.vue';
import MrWidgetContainer from './mr_widget_container.vue';
@@ -67,7 +67,7 @@ export default {
return this.mr.visualReviewAppAvailable && this.glFeatures.anonymousVisualReviewFeedback;
},
showMergeTrainPositionIndicator() {
- return _.isNumber(this.mr.mergeTrainIndex);
+ return isNumber(this.mr.mergeTrainIndex);
},
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index 01524f4b650..266c07ead25 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
-import _ from 'underscore';
+import { escape as esc } from 'lodash';
import { __, n__, sprintf, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
@@ -60,7 +60,7 @@ export default {
{
commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</strong>`,
- targetBranch: `<span class="label-branch">${_.escape(this.targetBranch)}</span>`,
+ targetBranch: `<span class="label-branch">${esc(this.targetBranch)}</span>`,
},
false,
);
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
index ad80a51c5f9..a368e29d086 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue
@@ -1,5 +1,4 @@
<script>
-import _ from 'underscore';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
@@ -72,7 +71,7 @@ export default {
.merge(options)
.then(res => res.data)
.then(data => {
- if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) {
+ if (AUTO_MERGE_STRATEGIES.includes(data.status)) {
eventHub.$emit('MRWidgetUpdateRequested');
}
})
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index 3df4a777aca..139cbe17e35 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -1,6 +1,6 @@
<script>
import $ from 'jquery';
-import _ from 'underscore';
+import { escape as esc } from 'lodash';
import { s__, sprintf } from '~/locale';
import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
import StatusIcon from '../mr_widget_status_icon.vue';
@@ -50,7 +50,7 @@ export default {
content: sprintf(
s__('mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}'),
{
- link_start: `<a href="${_.escape(
+ link_start: `<a href="${esc(
this.mr.conflictsDocsPath,
)}" target="_blank" rel="noopener noreferrer">`,
link_end: '</a>',
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 5eccc0c543d..e34060c3393 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty } from 'lodash';
import { GlIcon, GlButton } from '@gitlab/ui';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
@@ -51,7 +51,7 @@ export default {
},
computed: {
isAutoMergeAvailable() {
- return !_.isEmpty(this.mr.availableAutoMergeStrategies);
+ return !isEmpty(this.mr.availableAutoMergeStrategies);
},
status() {
const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
@@ -158,7 +158,7 @@ export default {
.then(data => {
const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
- if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) {
+ if (AUTO_MERGE_STRATEGIES.includes(data.status)) {
eventHub.$emit('MRWidgetUpdateRequested');
} else if (data.status === 'success') {
this.initiateMergePolling();
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index df86725c025..8b12e8ffb73 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty } from 'lodash';
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
import stateMaps from 'ee_else_ce/vue_merge_request_widget/stores/state_maps';
@@ -118,7 +118,7 @@ export default {
return this.mr.allowCollaboration && this.mr.isOpen;
},
shouldRenderMergedPipeline() {
- return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline);
+ return this.mr.state === 'merged' && !isEmpty(this.mr.mergePipeline);
},
showMergePipelineForkWarning() {
return Boolean(
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 2aecd0938e4..321b9270dde 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -1,5 +1,4 @@
import { format } from 'timeago.js';
-import _ from 'underscore';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility';
@@ -228,11 +227,13 @@ export default class MergeRequestStore {
}
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
- if (_.includes(availableAutoMergeStrategies, MTWPS_MERGE_STRATEGY)) {
+ if (availableAutoMergeStrategies === undefined) return undefined;
+
+ if (availableAutoMergeStrategies.includes(MTWPS_MERGE_STRATEGY)) {
return MTWPS_MERGE_STRATEGY;
- } else if (_.includes(availableAutoMergeStrategies, MT_MERGE_STRATEGY)) {
+ } else if (availableAutoMergeStrategies.includes(MT_MERGE_STRATEGY)) {
return MT_MERGE_STRATEGY;
- } else if (_.includes(availableAutoMergeStrategies, MWPS_MERGE_STRATEGY)) {
+ } else if (availableAutoMergeStrategies.includes(MWPS_MERGE_STRATEGY)) {
return MWPS_MERGE_STRATEGY;
}
diff --git a/app/assets/stylesheets/pages/error_list.scss b/app/assets/stylesheets/pages/error_list.scss
index f97953ce824..88fdcc47492 100644
--- a/app/assets/stylesheets/pages/error_list.scss
+++ b/app/assets/stylesheets/pages/error_list.scss
@@ -20,47 +20,19 @@ $gray-border: 1px solid $border-color;
}
}
- @include media-breakpoint-down(xs) {
- .table-row {
- border: $gray-border;
- border-radius: 4px;
- }
-
- .search-box {
- border-top: $gray-border;
- border-bottom: $gray-border;
- background-color: $gray-50;
- }
-
- .table-col {
- min-height: 68px;
-
- &::before {
- text-align: left !important;
- }
-
- &:first-child {
- div {
- padding: 0 !important;
- align-items: flex-end;
- }
- }
-
- &:last-child {
- height: 64px;
- background-color: $gray-normal;
-
- &::before {
- content: none !important;
- }
-
- div {
- width: 100% !important;
- padding: 0 !important;
+ @include media-breakpoint-down(md) {
+ .error-list-table {
+ .table-col {
+ min-height: 68px;
+
+ &:last-child {
+ &::before {
+ content: none !important;
+ }
- a {
- color: $blue-500;
- border-color: $blue-500;
+ div {
+ width: 100% !important;
+ padding: 0 !important;
}
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 3085f5e89b5..305956e1baf 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -420,7 +420,7 @@ table.pipeline-project-metrics tr td {
p {
@include str-truncated;
- max-width: none;
+ max-width: 100%;
}
}
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 4916c4651dd..a9c19502a7c 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -11,6 +11,8 @@
= render_if_exists 'groups/self_or_ancestor_marked_for_deletion_notice', group: @group
+ = render_if_exists 'groups/group_activity_analytics', group: @group
+
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
.top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index 22477f315d6..ee47d70171b 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -8,3 +8,4 @@
= render 'projects/settings/operations/external_dashboard'
= render 'projects/settings/operations/grafana_integration'
= render_if_exists 'projects/settings/operations/tracing'
+= render_if_exists 'projects/settings/operations/status_page'
diff --git a/changelogs/unreleased/208885-optimize-ci_pipeline-counters-related-to-the-ci-pipeline.yml b/changelogs/unreleased/208885-optimize-ci_pipeline-counters-related-to-the-ci-pipeline.yml
new file mode 100644
index 00000000000..b08b4dbbd27
--- /dev/null
+++ b/changelogs/unreleased/208885-optimize-ci_pipeline-counters-related-to-the-ci-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize ci_pipelines counters in usage data
+merge_request: 26774
+author:
+type: performance
diff --git a/changelogs/unreleased/208936.yml b/changelogs/unreleased/208936.yml
new file mode 100644
index 00000000000..ce561fd6027
--- /dev/null
+++ b/changelogs/unreleased/208936.yml
@@ -0,0 +1,5 @@
+---
+title: update table layout for error tracking list on medium view ports
+merge_request: 26479
+author:
+type: other
diff --git a/db/migrate/20200306170321_add_index_on_user_id_and_created_at_to_ci_pipelines.rb b/db/migrate/20200306170321_add_index_on_user_id_and_created_at_to_ci_pipelines.rb
new file mode 100644
index 00000000000..b88f938d1c2
--- /dev/null
+++ b/db/migrate/20200306170321_add_index_on_user_id_and_created_at_to_ci_pipelines.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexOnUserIdAndCreatedAtToCiPipelines < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_pipelines, [:user_id, :created_at]
+ remove_concurrent_index :ci_pipelines, [:user_id]
+ end
+
+ def down
+ add_concurrent_index :ci_pipelines, [:user_id]
+ remove_concurrent_index :ci_pipelines, [:user_id, :created_at]
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c45e7f053f3..62cce205424 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -873,7 +873,7 @@ ActiveRecord::Schema.define(version: 2020_03_09_195710) do
t.index ["project_id", "status", "config_source"], name: "index_ci_pipelines_on_project_id_and_status_and_config_source"
t.index ["project_id", "status", "updated_at"], name: "index_ci_pipelines_on_project_id_and_status_and_updated_at"
t.index ["status"], name: "index_ci_pipelines_on_status"
- t.index ["user_id"], name: "index_ci_pipelines_on_user_id"
+ t.index ["user_id", "created_at"], name: "index_ci_pipelines_on_user_id_and_created_at"
end
create_table "ci_pipelines_config", primary_key: "pipeline_id", force: :cascade do |t|
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index fc26af40c69..f47f07953d7 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -2220,6 +2220,61 @@ type Epic implements Noteable {
}
"""
+Autogenerated input type of EpicAddIssue
+"""
+input EpicAddIssueInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The group the epic to mutate belongs to
+ """
+ groupPath: ID!
+
+ """
+ The iid of the epic to mutate
+ """
+ iid: ID!
+
+ """
+ The iid of the issue to be added
+ """
+ issueIid: String!
+
+ """
+ The project the issue belongs to
+ """
+ projectPath: ID!
+}
+
+"""
+Autogenerated return type of EpicAddIssue
+"""
+type EpicAddIssuePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The epic after mutation
+ """
+ epic: Epic
+
+ """
+ The epic-issue relation
+ """
+ epicIssue: EpicIssue
+
+ """
+ Reasons why the mutation failed.
+ """
+ errors: [String!]!
+}
+
+"""
The connection type for Epic.
"""
type EpicConnection {
@@ -2689,7 +2744,7 @@ input EpicSetSubscriptionInput {
clientMutationId: String
"""
- The group the epic to mutate is in
+ The group the epic to mutate belongs to
"""
groupPath: ID!
@@ -4872,6 +4927,7 @@ type Mutation {
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
destroyNote(input: DestroyNoteInput!): DestroyNotePayload
destroySnippet(input: DestroySnippetInput!): DestroySnippetPayload
+ epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 4d52f45d3e4..3472a8bf742 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -19403,6 +19403,33 @@
"deprecationReason": null
},
{
+ "name": "epicAddIssue",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "EpicAddIssueInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "EpicAddIssuePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "epicSetSubscription",
"description": null,
"args": [
@@ -25120,7 +25147,7 @@
},
{
"name": "groupPath",
- "description": "The group the epic to mutate is in",
+ "description": "The group the epic to mutate belongs to",
"type": {
"kind": "NON_NULL",
"name": null,
@@ -25163,6 +25190,164 @@
},
{
"kind": "OBJECT",
+ "name": "EpicAddIssuePayload",
+ "description": "Autogenerated return type of EpicAddIssue",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "epic",
+ "description": "The epic after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Epic",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "epicIssue",
+ "description": "The epic-issue relation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "EpicIssue",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Reasons why the mutation failed.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "INPUT_OBJECT",
+ "name": "EpicAddIssueInput",
+ "description": "Autogenerated input type of EpicAddIssue",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "iid",
+ "description": "The iid of the epic to mutate",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "groupPath",
+ "description": "The group the epic to mutate belongs to",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "projectPath",
+ "description": "The project the issue belongs to",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "issueIid",
+ "description": "The iid of the issue to be added",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "__Schema",
"description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",
"fields": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 7230be484e6..df85e13d194 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -346,6 +346,17 @@ Represents an epic.
| `webPath` | String! | Web path of the epic |
| `webUrl` | String! | Web URL of the epic |
+## EpicAddIssuePayload
+
+Autogenerated return type of EpicAddIssue
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `epic` | Epic | The epic after mutation |
+| `epicIssue` | EpicIssue | The epic-issue relation |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+
## EpicDescendantCount
Counts of descendent epics.
diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md
index 4b5f93fa1a2..3caea124351 100644
--- a/doc/ci/jenkins/index.md
+++ b/doc/ci/jenkins/index.md
@@ -24,12 +24,40 @@ can be a great resource.
There are some high level differences between the products worth mentioning:
- With GitLab you don't need a root `pipeline` keyword to wrap everything.
+- The way pipelines are triggered and [trigger other pipelines](../yaml/README.md#trigger)
+ is different than Jenkins. GitLab pipelines can be triggered:
+
+ - on push
+ - on [schedule](../pipelines/schedules.md)
+ - from the [GitLab UI](../pipelines.md#manually-executing-pipelines)
+ - by [API call](../triggers/README.md)
+ - by [webhook](../triggers/README.md#triggering-a-pipeline-from-a-webhook)
+ - by [ChatOps](../chatops/README.md)
+
+ You can control which jobs run in which cases, depending on how they are triggered,
+ with the [`rules` syntax](../yaml/README.md#rules).
+- GitLab [pipeline scheduling concepts](../pipelines/schedules.md) are also different than with Jenkins.
- All jobs within a single stage always run in parallel, and all stages run in sequence. We are planning
to allow certain jobs to break this sequencing as needed with our [directed acyclic graph](https://gitlab.com/gitlab-org/gitlab-foss/issues/47063)
feature.
+- The [`parallel`](../yaml/README.md#parallel) keyword can automatically parallelize tasks,
+ like tests that support parallelization.
+- Normally all jobs within a single stage run in parallel, and all stages run in sequence.
+ There are different [pipeline architectures](../pipelines/pipeline_architectures.md)
+ that allow you to change this behavior.
+- The new [`rules` syntax](../yaml/README.md#rules) is the recommended method of
+ controlling when different jobs run. It is more powerful than the `only/except` syntax.
+- One important difference is that jobs run independently of each other and have a
+ fresh environment in each job. Passing artifacts between jobs is controlled using the
+ [`artifacts`](../yaml/README.md#artifacts) and [`dependencies`](../yaml/README.md#dependencies)
+ keywords. When finished, the planned [Workspaces](https://gitlab.com/gitlab-org/gitlab/issues/29265)
+ feature will allow you to more easily persist a common workspace between serial jobs.
- The `.gitlab-ci.yml` file is checked in to the root of your repository, much like a Jenkinsfile, but
is in the YAML format (see [complete reference](../yaml/README.md)) instead of a Groovy DSL. It's most
analogous to the declarative Jenkinsfile format.
+- Manual approvals or gates can be set up as [`when:manual` jobs](../yaml/README.md#whenmanual). These can
+ also leverage [`protected environments`](../yaml/README.md#protecting-manual-jobs-premium)
+ to control who is able to approve them.
- GitLab comes with a [container registry](../../user/packages/container_registry/index.md), and we recommend using
container images to set up your build environment.
- Totally stuck and not sure where to turn for advice? The [GitLab community forum](https://forum.gitlab.com/) can be a great resource.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2eaf892d8c1..210e4ca7c12 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18771,6 +18771,45 @@ msgstr ""
msgid "Status: %{title}"
msgstr ""
+msgid "StatusPage|AWS Secret access key"
+msgstr ""
+
+msgid "StatusPage|AWS access key ID"
+msgstr ""
+
+msgid "StatusPage|AWS documentation"
+msgstr ""
+
+msgid "StatusPage|AWS region"
+msgstr ""
+
+msgid "StatusPage|Active"
+msgstr ""
+
+msgid "StatusPage|Bucket %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
+msgstr ""
+
+msgid "StatusPage|For help with configuration, visit %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|S3 Bucket name"
+msgstr ""
+
+msgid "StatusPage|Status page"
+msgstr ""
+
+msgid "StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
+msgstr ""
+
+msgid "StatusPage|configuration documentation"
+msgstr ""
+
+msgid "StatusPage|your status page frontend."
+msgstr ""
+
msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
msgstr ""
@@ -20819,6 +20858,9 @@ msgstr ""
msgid "Total issues"
msgstr ""
+msgid "Total memory (GB)"
+msgstr ""
+
msgid "Total test time for all commits/merges"
msgstr ""
diff --git a/spec/frontend/clusters_list/mock_data.js b/spec/frontend/clusters_list/mock_data.js
index 0a49c2e9f43..1812bf9b03f 100644
--- a/spec/frontend/clusters_list/mock_data.js
+++ b/spec/frontend/clusters_list/mock_data.js
@@ -5,6 +5,7 @@ export default [
size: '3',
clusterType: 'group_type',
status: 'disabled',
+ memory: '22.50 (30% free)',
},
{
name: 'My Cluster 2',
@@ -12,6 +13,7 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'unreachable',
+ memory: '11 (60% free)',
},
{
name: 'My Cluster 3',
@@ -19,6 +21,7 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'authentication_failure',
+ memory: '22 (33% free)',
},
{
name: 'My Cluster 4',
@@ -26,6 +29,7 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'deleting',
+ memory: '45 (15% free)',
},
{
name: 'My Cluster 5',
@@ -33,5 +37,6 @@ export default [
size: '12',
clusterType: 'project_type',
status: 'connected',
+ memory: '20.12 (35% free)',
},
];
diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js
index 27b88d78ff0..f6878c7c920 100644
--- a/spec/frontend/lib/utils/datetime_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime_utility_spec.js
@@ -474,6 +474,23 @@ describe('getDateInFuture', () => {
});
});
+describe('isValidDate', () => {
+ it.each`
+ valueToCheck | isValid
+ ${new Date()} | ${true}
+ ${new Date('December 17, 1995 03:24:00')} | ${true}
+ ${new Date('1995-12-17T03:24:00')} | ${true}
+ ${new Date('foo')} | ${false}
+ ${5} | ${false}
+ ${''} | ${false}
+ ${false} | ${false}
+ ${undefined} | ${false}
+ ${null} | ${false}
+ `('returns $expectedReturnValue when called with $dateToCheck', ({ valueToCheck, isValid }) => {
+ expect(datetimeUtility.isValidDate(valueToCheck)).toBe(isValid);
+ });
+});
+
describe('getDatesInRange', () => {
it('returns an empty array if 1st or 2nd argument is not a Date object', () => {
const d1 = new Date('2019-01-01');
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 7b282433061..d1c441c8605 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -43,6 +43,78 @@ describe Gitlab::ProjectAuthorizations do
end
end
+ context 'unapproved access request' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ subject(:mapping) { map_access_levels(authorizations) }
+
+ context 'group membership' do
+ let!(:group_project) { create(:project, namespace: group) }
+
+ before do
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[group_project.id]).to be_nil
+ end
+ end
+
+ context 'inherited group membership' do
+ let!(:sub_group) { create(:group, parent: group) }
+ let!(:sub_group_project) { create(:project, namespace: sub_group) }
+
+ before do
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[sub_group_project.id]).to be_nil
+ end
+ end
+
+ context 'project membership' do
+ let!(:group_project) { create(:project, namespace: group) }
+
+ before do
+ create(:project_member, :developer, :access_request, user: user, project: group_project)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[group_project.id]).to be_nil
+ end
+ end
+
+ context 'shared group' do
+ let!(:shared_group) { create(:group) }
+ let!(:shared_group_project) { create(:project, namespace: shared_group) }
+
+ before do
+ create(:group_group_link, shared_group: shared_group, shared_with_group: group)
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[shared_group_project.id]).to be_nil
+ end
+ end
+
+ context 'shared project' do
+ let!(:another_group) { create(:group) }
+ let!(:shared_project) { create(:project, namespace: another_group) }
+
+ before do
+ create(:project_group_link, group: group, project: shared_project)
+ create(:group_member, :developer, :access_request, user: user, group: group)
+ end
+
+ it 'does not create authorization' do
+ expect(mapping[shared_project.id]).to be_nil
+ end
+ end
+ end
+
context 'with nested groups' do
let(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }