Commit 3f3e4bcc authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 65a1175e
...@@ -149,7 +149,7 @@ gem 'wikicloth', '0.8.1' ...@@ -149,7 +149,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10' gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '0.0.10' gem 'asciidoctor-plantuml', '0.0.10'
gem 'rouge', '~> 3.16.0' gem 'rouge', '~> 3.17.0'
gem 'truncato', '~> 0.7.11' gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0' gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.5' gem 'nokogiri', '~> 1.10.5'
......
...@@ -889,7 +889,7 @@ GEM ...@@ -889,7 +889,7 @@ GEM
retriable (3.1.2) retriable (3.1.2)
rinku (2.0.0) rinku (2.0.0)
rotp (2.1.2) rotp (2.1.2)
rouge (3.16.0) rouge (3.17.0)
rqrcode (0.7.0) rqrcode (0.7.0)
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
...@@ -1346,7 +1346,7 @@ DEPENDENCIES ...@@ -1346,7 +1346,7 @@ DEPENDENCIES
request_store (~> 1.3) request_store (~> 1.3)
responders (~> 3.0) responders (~> 3.0)
retriable (~> 3.1.2) retriable (~> 3.1.2)
rouge (~> 3.16.0) rouge (~> 3.17.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
rspec-rails (~> 4.0.0.beta4) rspec-rails (~> 4.0.0.beta4)
......
...@@ -27,6 +27,10 @@ export default { ...@@ -27,6 +27,10 @@ export default {
key: 'size', key: 'size',
label: __('Size'), label: __('Size'),
}, },
{
key: 'memory',
label: __('Total memory (GB)'),
},
{ {
key: 'clusterType', key: 'clusterType',
label: __('Cluster level'), label: __('Cluster level'),
......
...@@ -20,7 +20,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; ...@@ -20,7 +20,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { isEmpty } from 'lodash'; 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 { export default {
FIRST_PAGE: 1, FIRST_PAGE: 1,
...@@ -35,7 +35,7 @@ export default { ...@@ -35,7 +35,7 @@ export default {
key: 'error', key: 'error',
label: __('Error'), label: __('Error'),
thClass: 'w-60p', thClass: 'w-60p',
tdClass: `${tableDataClass} px-3`, tdClass: `${tableDataClass} px-3 rounded-top`,
}, },
{ {
key: 'events', key: 'events',
...@@ -58,11 +58,11 @@ export default { ...@@ -58,11 +58,11 @@ export default {
{ {
key: 'status', key: 'status',
label: '', label: '',
tdClass: `${tableDataClass} text-center`, tdClass: `table-col d-none d-md-table-cell align-items-center pl-md-0`,
}, },
{ {
key: 'details', 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', thClass: 'invisible w-0',
}, },
], ],
...@@ -221,7 +221,7 @@ export default { ...@@ -221,7 +221,7 @@ export default {
<div class="error-list"> <div class="error-list">
<div v-if="errorTrackingEnabled"> <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="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"> <div class="filtered-search-box mb-0">
<gl-dropdown <gl-dropdown
:text="__('Recent searches')" :text="__('Recent searches')"
...@@ -321,25 +321,25 @@ export default { ...@@ -321,25 +321,25 @@ export default {
</div> </div>
<template v-else> <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 <gl-table
class="mt-3" class="error-list-table mt-3"
:items="errors" :items="errors"
:fields="$options.fields" :fields="$options.fields"
:show-empty="true" :show-empty="true"
fixed fixed
stacked="sm" stacked="md"
tbody-tr-class="table-row mb-4" tbody-tr-class="table-row mb-4"
> >
<template #head(error)> <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>
<template #head(events)="data"> <template #head(events)="data">
<div class="text-sm-right">{{ data.label }}</div> <div class="text-md-right">{{ data.label }}</div>
</template> </template>
<template #head(users)="data"> <template #head(users)="data">
<div class="text-sm-right">{{ data.label }}</div> <div class="text-md-right">{{ data.label }}</div>
</template> </template>
<template #cell(error)="errors"> <template #cell(error)="errors">
...@@ -361,7 +361,7 @@ export default { ...@@ -361,7 +361,7 @@ export default {
</template> </template>
<template #cell(lastSeen)="errors"> <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" /> <time-ago :time="errors.item.lastSeen" class="text-secondary" />
</div> </div>
</template> </template>
...@@ -380,10 +380,29 @@ export default { ...@@ -380,10 +380,29 @@ export default {
</gl-button-group> </gl-button-group>
</template> </template>
<template #cell(details)="errors"> <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 <gl-button
:href="getDetailsLink(errors.item.id)" :href="getDetailsLink(errors.item.id)"
variant="outline-info" category="secondary"
class="d-block" variant="info"
class="d-block mb-2"
> >
{{ __('More details') }} {{ __('More details') }}
</gl-button> </gl-button>
......
...@@ -566,6 +566,14 @@ export const getDateInPast = (date, daysInPast) => ...@@ -566,6 +566,14 @@ export const getDateInPast = (date, daysInPast) =>
export const getDateInFuture = (date, daysInFuture) => export const getDateInFuture = (date, daysInFuture) =>
new Date(newDate(date).setDate(date.getDate() + 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 * 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 * to match the user's time zone. We want to display the date in server time for now, to
......
...@@ -7,5 +7,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,5 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm(); mountErrorTrackingForm();
mountOperationSettings(); mountOperationSettings();
mountGrafanaIntegration(); mountGrafanaIntegration();
if (!IS_EE) {
initSettingsPanels(); initSettingsPanels();
}
}); });
<script> <script>
import _ from 'underscore'; import { escape as esc } from 'lodash';
import { n__, s__, sprintf } from '~/locale'; import { n__, s__, sprintf } from '~/locale';
import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility'; import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
...@@ -35,7 +35,7 @@ export default { ...@@ -35,7 +35,7 @@ export default {
'mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch', '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), commitsBehind: n__('%d commit behind', '%d commits behind', this.mr.divergedCommitsCount),
commitsBehindLinkEnd: '</a>', commitsBehindLinkEnd: '</a>',
}, },
......
<script> <script>
import _ from 'underscore'; import { isNumber } from 'lodash';
import ArtifactsApp from './artifacts_list_app.vue'; import ArtifactsApp from './artifacts_list_app.vue';
import Deployment from './deployment/deployment.vue'; import Deployment from './deployment/deployment.vue';
import MrWidgetContainer from './mr_widget_container.vue'; import MrWidgetContainer from './mr_widget_container.vue';
...@@ -67,7 +67,7 @@ export default { ...@@ -67,7 +67,7 @@ export default {
return this.mr.visualReviewAppAvailable && this.glFeatures.anonymousVisualReviewFeedback; return this.mr.visualReviewAppAvailable && this.glFeatures.anonymousVisualReviewFeedback;
}, },
showMergeTrainPositionIndicator() { showMergeTrainPositionIndicator() {
return _.isNumber(this.mr.mergeTrainIndex); return isNumber(this.mr.mergeTrainIndex);
}, },
}, },
}; };
......
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import _ from 'underscore'; import { escape as esc } from 'lodash';
import { __, n__, sprintf, s__ } from '~/locale'; import { __, n__, sprintf, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
...@@ -60,7 +60,7 @@ export default { ...@@ -60,7 +60,7 @@ export default {
{ {
commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`, commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</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, false,
); );
......
<script> <script>
import _ from 'underscore';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge'; import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import Flash from '../../../flash'; import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue'; import statusIcon from '../mr_widget_status_icon.vue';
...@@ -72,7 +71,7 @@ export default { ...@@ -72,7 +71,7 @@ export default {
.merge(options) .merge(options)
.then(res => res.data) .then(res => res.data)
.then(data => { .then(data => {
if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) { if (AUTO_MERGE_STRATEGIES.includes(data.status)) {
eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('MRWidgetUpdateRequested');
} }
}) })
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import { escape as esc } from 'lodash';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover'; import { mouseenter, debouncedMouseleave, togglePopover } from '~/shared/popover';
import StatusIcon from '../mr_widget_status_icon.vue'; import StatusIcon from '../mr_widget_status_icon.vue';
...@@ -50,7 +50,7 @@ export default { ...@@ -50,7 +50,7 @@ export default {
content: sprintf( content: sprintf(
s__('mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}'), s__('mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}'),
{ {
link_start: `<a href="${_.escape( link_start: `<a href="${esc(
this.mr.conflictsDocsPath, this.mr.conflictsDocsPath,
)}" target="_blank" rel="noopener noreferrer">`, )}" target="_blank" rel="noopener noreferrer">`,
link_end: '</a>', link_end: '</a>',
......
<script> <script>
import _ from 'underscore'; import { isEmpty } from 'lodash';
import { GlIcon, GlButton } from '@gitlab/ui'; import { GlIcon, GlButton } from '@gitlab/ui';
import successSvg from 'icons/_icon_status_success.svg'; import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg'; import warningSvg from 'icons/_icon_status_warning.svg';
...@@ -51,7 +51,7 @@ export default { ...@@ -51,7 +51,7 @@ export default {
}, },
computed: { computed: {
isAutoMergeAvailable() { isAutoMergeAvailable() {
return !_.isEmpty(this.mr.availableAutoMergeStrategies); return !isEmpty(this.mr.availableAutoMergeStrategies);
}, },
status() { status() {
const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr; const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
...@@ -158,7 +158,7 @@ export default { ...@@ -158,7 +158,7 @@ export default {
.then(data => { .then(data => {
const hasError = data.status === 'failed' || data.status === 'hook_validation_error'; 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'); eventHub.$emit('MRWidgetUpdateRequested');
} else if (data.status === 'success') { } else if (data.status === 'success') {
this.initiateMergePolling(); this.initiateMergePolling();
......
<script> <script>
import _ from 'underscore'; import { isEmpty } from 'lodash';
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store'; 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 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'; import stateMaps from 'ee_else_ce/vue_merge_request_widget/stores/state_maps';
...@@ -118,7 +118,7 @@ export default { ...@@ -118,7 +118,7 @@ export default {
return this.mr.allowCollaboration && this.mr.isOpen; return this.mr.allowCollaboration && this.mr.isOpen;
}, },
shouldRenderMergedPipeline() { shouldRenderMergedPipeline() {
return this.mr.state === 'merged' && !_.isEmpty(this.mr.mergePipeline); return this.mr.state === 'merged' && !isEmpty(this.mr.mergePipeline);
}, },
showMergePipelineForkWarning() { showMergePipelineForkWarning() {
return Boolean( return Boolean(
......
import { format } from 'timeago.js'; import { format } from 'timeago.js';
import _ from 'underscore';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key'; import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps'; import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility'; import { formatDate } from '../../lib/utils/datetime_utility';
...@@ -228,11 +227,13 @@ export default class MergeRequestStore { ...@@ -228,11 +227,13 @@ export default class MergeRequestStore {
} }
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) { static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
if (_.includes(availableAutoMergeStrategies, MTWPS_MERGE_STRATEGY)) { if (availableAutoMergeStrategies === undefined) return undefined;
if (availableAutoMergeStrategies.includes(MTWPS_MERGE_STRATEGY)) {
return 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; return MT_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MWPS_MERGE_STRATEGY)) { } else if (availableAutoMergeStrategies.includes(MWPS_MERGE_STRATEGY)) {
return MWPS_MERGE_STRATEGY; return MWPS_MERGE_STRATEGY;
} }
......
...@@ -20,36 +20,12 @@ $gray-border: 1px solid $border-color; ...@@ -20,36 +20,12 @@ $gray-border: 1px solid $border-color;
} }
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(md) {
.table-row { .error-list-table {
border: $gray-border;
border-radius: 4px;
}
.search-box {
border-top: $gray-border;
border-bottom: $gray-border;
background-color: $gray-50;
}
.table-col { .table-col {
min-height: 68px; min-height: 68px;
&::before {
text-align: left !important;
}
&:first-child {
div {
padding: 0 !important;
align-items: flex-end;
}
}
&:last-child { &:last-child {
height: 64px;
background-color: $gray-normal;
&::before { &::before {
content: none !important; content: none !important;
} }
...@@ -57,10 +33,6 @@ $gray-border: 1px solid $border-color; ...@@ -57,10 +33,6 @@ $gray-border: 1px solid $border-color;
div { div {
width: 100% !important; width: 100% !important;
padding: 0 !important; padding: 0 !important;
a {
color: $blue-500;
border-color: $blue-500;
} }
} }
} }
......
...@@ -420,7 +420,7 @@ table.pipeline-project-metrics tr td { ...@@ -420,7 +420,7 @@ table.pipeline-project-metrics tr td {
p { p {
@include str-truncated; @include str-truncated;
max-width: none; max-width: 100%;
} }
} }
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
= render_if_exists 'groups/self_or_ancestor_marked_for_deletion_notice', group: @group = 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) } } } .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 .top-area.group-nav-container.justify-content-between
.scrolling-tabs-container.inner-page-scroll-tabs .scrolling-tabs-container.inner-page-scroll-tabs
......
...@@ -8,3 +8,4 @@ ...@@ -8,3 +8,4 @@
= render 'projects/settings/operations/external_dashboard' = render 'projects/settings/operations/external_dashboard'
= render 'projects/settings/operations/grafana_integration' = render 'projects/settings/operations/grafana_integration'
= render_if_exists 'projects/settings/operations/tracing' = render_if_exists 'projects/settings/operations/tracing'
= render_if_exists 'projects/settings/operations/status_page'
---
title: Optimize ci_pipelines counters in usage data
merge_request: 26774
author:
type: performance
---
title: update table layout for error tracking list on medium view ports
merge_request: 26479
author:
type: other
# 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
...@@ -873,7 +873,7 @@ ActiveRecord::Schema.define(version: 2020_03_09_195710) do ...@@ -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", "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 ["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 ["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 end
create_table "ci_pipelines_config", primary_key: "pipeline_id", force: :cascade do |t| create_table "ci_pipelines_config", primary_key: "pipeline_id", force: :cascade do |t|
......
...@@ -2219,6 +2219,61 @@ type Epic implements Noteable { ...@@ -2219,6 +2219,61 @@ type Epic implements Noteable {
webUrl: String! webUrl: String!
} }
"""
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. The connection type for Epic.
""" """
...@@ -2689,7 +2744,7 @@ input EpicSetSubscriptionInput { ...@@ -2689,7 +2744,7 @@ input EpicSetSubscriptionInput {
clientMutationId: String clientMutationId: String
""" """
The group the epic to mutate is in The group the epic to mutate belongs to
""" """
groupPath: ID! groupPath: ID!
...@@ -4872,6 +4927,7 @@ type Mutation { ...@@ -4872,6 +4927,7 @@ type Mutation {
designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload designManagementUpload(input: DesignManagementUploadInput!): DesignManagementUploadPayload
destroyNote(input: DestroyNoteInput!): DestroyNotePayload destroyNote(input: DestroyNoteInput!): DestroyNotePayload
destroySnippet(input: DestroySnippetInput!): DestroySnippetPayload destroySnippet(input: DestroySnippetInput!): DestroySnippetPayload
epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload
......
...@@ -19402,6 +19402,33 @@ ...@@ -19402,6 +19402,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "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", "name": "epicSetSubscription",
"description": null, "description": null,
...@@ -25120,7 +25147,7 @@ ...@@ -25120,7 +25147,7 @@
}, },
{ {
"name": "groupPath", "name": "groupPath",
"description": "The group the epic to mutate is in", "description": "The group the epic to mutate belongs to",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
...@@ -25161,6 +25188,164 @@ ...@@ -25161,6 +25188,164 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"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", "kind": "OBJECT",
"name": "__Schema", "name": "__Schema",
......
...@@ -346,6 +346,17 @@ Represents an epic. ...@@ -346,6 +346,17 @@ Represents an epic.
| `webPath` | String! | Web path of the epic | | `webPath` | String! | Web path of the epic |
| `webUrl` | String! | Web URL 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 ## EpicDescendantCount
Counts of descendent epics. Counts of descendent epics.
......
...@@ -24,12 +24,40 @@ can be a great resource. ...@@ -24,12 +24,40 @@ can be a great resource.
There are some high level differences between the products worth mentioning: There are some high level differences between the products worth mentioning:
- With GitLab you don't need a root `pipeline` keyword to wrap everything. - 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 - 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) 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. 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 - 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 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. 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 - GitLab comes with a [container registry](../../user/packages/container_registry/index.md), and we recommend using
container images to set up your build environment. 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. - Totally stuck and not sure where to turn for advice? The [GitLab community forum](https://forum.gitlab.com/) can be a great resource.
......
...@@ -18771,6 +18771,45 @@ msgstr "" ...@@ -18771,6 +18771,45 @@ msgstr ""
msgid "Status: %{title}" msgid "Status: %{title}"
msgstr "" 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." msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments."
msgstr "" msgstr ""
...@@ -20819,6 +20858,9 @@ msgstr "" ...@@ -20819,6 +20858,9 @@ msgstr ""
msgid "Total issues" msgid "Total issues"
msgstr "" msgstr ""
msgid "Total memory (GB)"
msgstr ""
msgid "Total test time for all commits/merges" msgid "Total test time for all commits/merges"
msgstr "" msgstr ""
......
...@@ -5,6 +5,7 @@ export default [ ...@@ -5,6 +5,7 @@ export default [
size: '3', size: '3',
clusterType: 'group_type', clusterType: 'group_type',
status: 'disabled', status: 'disabled',
memory: '22.50 (30% free)',
}, },
{ {
name: 'My Cluster 2', name: 'My Cluster 2',
...@@ -12,6 +13,7 @@ export default [ ...@@ -12,6 +13,7 @@ export default [
size: '12', size: '12',
clusterType: 'project_type', clusterType: 'project_type',
status: 'unreachable', status: 'unreachable',
memory: '11 (60% free)',
}, },
{ {
name: 'My Cluster 3', name: 'My Cluster 3',
...@@ -19,6 +21,7 @@ export default [ ...@@ -19,6 +21,7 @@ export default [
size: '12', size: '12',
clusterType: 'project_type', clusterType: 'project_type',
status: 'authentication_failure', status: 'authentication_failure',
memory: '22 (33% free)',
}, },
{ {
name: 'My Cluster 4', name: 'My Cluster 4',
...@@ -26,6 +29,7 @@ export default [ ...@@ -26,6 +29,7 @@ export default [
size: '12', size: '12',
clusterType: 'project_type', clusterType: 'project_type',
status: 'deleting', status: 'deleting',
memory: '45 (15% free)',
}, },
{ {
name: 'My Cluster 5', name: 'My Cluster 5',
...@@ -33,5 +37,6 @@ export default [ ...@@ -33,5 +37,6 @@ export default [
size: '12', size: '12',
clusterType: 'project_type', clusterType: 'project_type',
status: 'connected', status: 'connected',
memory: '20.12 (35% free)',
}, },
]; ];
...@@ -474,6 +474,23 @@ describe('getDateInFuture', () => { ...@@ -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', () => { describe('getDatesInRange', () => {
it('returns an empty array if 1st or 2nd argument is not a Date object', () => { it('returns an empty array if 1st or 2nd argument is not a Date object', () => {
const d1 = new Date('2019-01-01'); const d1 = new Date('2019-01-01');
......
...@@ -43,6 +43,78 @@ describe Gitlab::ProjectAuthorizations do ...@@ -43,6 +43,78 @@ describe Gitlab::ProjectAuthorizations do
end end
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 context 'with nested groups' do
let(:group) { create(:group) } let(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) } let!(:nested_group) { create(:group, parent: group) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment