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'
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'
......
......@@ -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)
......
......@@ -27,6 +27,10 @@ export default {
key: 'size',
label: __('Size'),
},
{
key: 'memory',
label: __('Total memory (GB)'),
},
{
key: 'clusterType',
label: __('Cluster level'),
......
......@@ -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>
......@@ -380,10 +380,29 @@ export default {
</gl-button-group>
</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>
......
......@@ -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
......
......@@ -7,5 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm();
mountOperationSettings();
mountGrafanaIntegration();
initSettingsPanels();
if (!IS_EE) {
initSettingsPanels();
}
});
<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>',
},
......
<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);
},
},
};
......
<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,
);
......
<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');
}
})
......
<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>',
......
<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();
......
<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(
......
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;
}
......
......@@ -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;
}
}
}
......
......@@ -420,7 +420,7 @@ table.pipeline-project-metrics tr td {
p {
@include str-truncated;
max-width: none;
max-width: 100%;
}
}
......
......@@ -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
......
......@@ -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'
---
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
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|
......
......@@ -2219,6 +2219,61 @@ type Epic implements Noteable {
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.
"""
......@@ -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
......
......@@ -19402,6 +19402,33 @@
"isDeprecated": false,
"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,
......@@ -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,
......@@ -25161,6 +25188,164 @@
"enumValues": 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",
"name": "__Schema",
......
......@@ -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.
......
......@@ -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.
......
......@@ -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 ""
......
......@@ -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)',
},
];
......@@ -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');
......
......@@ -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) }
......
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