Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
79b32f05d4
commit
6b5f961bef
46 changed files with 2034 additions and 798 deletions
|
@ -52,7 +52,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
updateToDoCount(add) {
|
||||
const oldCount = parseInt(document.querySelector('.todos-count').innerText, 10);
|
||||
const oldCount = parseInt(document.querySelector('.js-todos-count').innerText, 10);
|
||||
const count = add ? oldCount + 1 : oldCount - 1;
|
||||
const headerTodoEvent = new CustomEvent('todo:toggle', {
|
||||
detail: {
|
||||
|
|
|
@ -5,7 +5,7 @@ import createDesignTodoMutation from '../graphql/mutations/create_design_todo.mu
|
|||
import TodoButton from '~/vue_shared/components/todo_button.vue';
|
||||
import allVersionsMixin from '../mixins/all_versions';
|
||||
import { updateStoreAfterDeleteDesignTodo } from '../utils/cache_update';
|
||||
import { findIssueId } from '../utils/design_management_utils';
|
||||
import { findIssueId, findDesignId } from '../utils/design_management_utils';
|
||||
import { CREATE_DESIGN_TODO_ERROR, DELETE_DESIGN_TODO_ERROR } from '../utils/error_messages';
|
||||
|
||||
export default {
|
||||
|
@ -45,6 +45,7 @@ export default {
|
|||
return {
|
||||
projectPath: this.projectPath,
|
||||
issueId: findIssueId(this.design.issue.id),
|
||||
designId: findDesignId(this.design.id),
|
||||
issueIid: this.issueIid,
|
||||
filenames: [this.$route.params.id],
|
||||
atVersion: this.designsVersion,
|
||||
|
@ -59,6 +60,22 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
updateGlobalTodoCount(additionalTodoCount) {
|
||||
const currentCount = parseInt(document.querySelector('.js-todos-count').innerText, 10);
|
||||
const todoToggleEvent = new CustomEvent('todo:toggle', {
|
||||
detail: {
|
||||
count: Math.max(currentCount + additionalTodoCount, 0),
|
||||
},
|
||||
});
|
||||
|
||||
document.dispatchEvent(todoToggleEvent);
|
||||
},
|
||||
incrementGlobalTodoCount() {
|
||||
this.updateGlobalTodoCount(1);
|
||||
},
|
||||
decrementGlobalTodoCount() {
|
||||
this.updateGlobalTodoCount(-1);
|
||||
},
|
||||
createTodo() {
|
||||
this.todoLoading = true;
|
||||
return this.$apollo
|
||||
|
@ -75,6 +92,9 @@ export default {
|
|||
}
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.incrementGlobalTodoCount();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('error', Error(CREATE_DESIGN_TODO_ERROR));
|
||||
throw err;
|
||||
|
@ -115,6 +135,9 @@ export default {
|
|||
}
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.decrementGlobalTodoCount();
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('error', Error(DELETE_DESIGN_TODO_ERROR));
|
||||
throw err;
|
||||
|
|
|
@ -30,11 +30,15 @@ const resolvers = {
|
|||
|
||||
cache.writeQuery({ query: activeDiscussionQuery, data });
|
||||
},
|
||||
createDesignTodo: (_, { projectPath, issueId, issueIid, filenames, atVersion }, { cache }) => {
|
||||
createDesignTodo: (
|
||||
_,
|
||||
{ projectPath, issueId, designId, issueIid, filenames, atVersion },
|
||||
{ cache },
|
||||
) => {
|
||||
return axios
|
||||
.post(`/${projectPath}/todos`, {
|
||||
issue_id: issueId,
|
||||
issuable_id: issueIid,
|
||||
issuable_id: designId,
|
||||
issuable_type: 'design',
|
||||
})
|
||||
.then(({ data }) => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mutation createDesignTodo(
|
||||
$projectPath: String!
|
||||
$issueId: String!
|
||||
$designId: String!
|
||||
$issueIid: String!
|
||||
$filenames: [String]!
|
||||
$atVersion: String
|
||||
|
@ -8,6 +9,7 @@ mutation createDesignTodo(
|
|||
createDesignTodo(
|
||||
projectPath: $projectPath
|
||||
issueId: $issueId
|
||||
designId: $designId
|
||||
issueIid: $issueIid
|
||||
filenames: $filenames
|
||||
atVersion: $atVersion
|
||||
|
|
|
@ -32,6 +32,8 @@ export const findNoteId = id => (id.match('DiffNote/(.+$)') || [])[1];
|
|||
|
||||
export const findIssueId = id => (id.match('Issue/(.+$)') || [])[1];
|
||||
|
||||
export const findDesignId = id => (id.match('Design/(.+$)') || [])[1];
|
||||
|
||||
export const extractDesigns = data => data.project.issue.designCollection.designs.nodes;
|
||||
|
||||
export const extractDesign = data => (extractDesigns(data) || [])[0];
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<script>
|
||||
/* eslint-disable vue/no-v-html */
|
||||
import { mapGetters } from 'vuex';
|
||||
import { escape } from 'lodash';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import { GlButton, GlSprintf } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
changesEmptyStateIllustration: {
|
||||
|
@ -17,20 +15,6 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters(['getNoteableData']),
|
||||
emptyStateText() {
|
||||
return sprintf(
|
||||
__(
|
||||
'No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}',
|
||||
),
|
||||
{
|
||||
ref_start: '<span class="ref-name">',
|
||||
ref_end: '</span>',
|
||||
source_branch: escape(this.getNoteableData.source_branch),
|
||||
target_branch: escape(this.getNoteableData.target_branch),
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -42,7 +26,14 @@ export default {
|
|||
</div>
|
||||
<div class="col-12">
|
||||
<div class="text-content text-center">
|
||||
<span v-html="emptyStateText"></span>
|
||||
<gl-sprintf :message="__('No changes between %{sourceBranch} and %{targetBranch}')">
|
||||
<template #sourceBranch>
|
||||
<span class="ref-name">{{ getNoteableData.source_branch }}</span>
|
||||
</template>
|
||||
<template #targetBranch>
|
||||
<span class="ref-name">{{ getNoteableData.target_branch }}</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<div class="text-center">
|
||||
<gl-button :href="getNoteableData.new_blob_path" variant="success" category="primary">{{
|
||||
__('Create commit')
|
||||
|
|
|
@ -14,7 +14,7 @@ import Tracking from '~/tracking';
|
|||
export default function initTodoToggle() {
|
||||
$(document).on('todo:toggle', (e, count) => {
|
||||
const updatedCount = count || e?.detail?.count || 0;
|
||||
const $todoPendingCount = $('.todos-count');
|
||||
const $todoPendingCount = $('.js-todos-count');
|
||||
|
||||
$todoPendingCount.text(highCountTrim(updatedCount));
|
||||
$todoPendingCount.toggleClass('hidden', updatedCount === 0);
|
||||
|
|
|
@ -116,6 +116,7 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #right-secondary>
|
||||
<span>
|
||||
<gl-sprintf :message="__('Created %{timestamp}')">
|
||||
<template #timestamp>
|
||||
<span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
|
||||
|
@ -123,6 +124,7 @@ export default {
|
|||
</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template v-if="!disableDelete" #right-action>
|
||||
|
|
|
@ -36,7 +36,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-align-items-center gl-mb-2">
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
<template v-if="hasPipeline">
|
||||
<gl-icon name="git-merge" class="gl-mr-2" />
|
||||
<span data-testid="pipeline-ref" class="gl-mr-2">{{ packageEntity.pipeline.ref }}</span>
|
||||
|
|
|
@ -39,7 +39,7 @@ export default {
|
|||
created() {
|
||||
this.observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
this.dispose(mutation.removedNodes);
|
||||
mutation.removedNodes.forEach(this.dispose);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -61,22 +61,36 @@ export default {
|
|||
childList: true,
|
||||
});
|
||||
},
|
||||
dispose(elements) {
|
||||
if (!elements) {
|
||||
dispose(target) {
|
||||
if (!target) {
|
||||
this.tooltips = [];
|
||||
return;
|
||||
}
|
||||
|
||||
elements.forEach(element => {
|
||||
const index = this.tooltips.findIndex(tooltip => tooltip.target === element);
|
||||
} else {
|
||||
const index = this.tooltips.indexOf(this.findTooltipByTarget(target));
|
||||
|
||||
if (index > -1) {
|
||||
this.tooltips.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
fixTitle(target) {
|
||||
const tooltip = this.findTooltipByTarget(target);
|
||||
|
||||
if (tooltip) {
|
||||
tooltip.title = target.getAttribute('title');
|
||||
}
|
||||
},
|
||||
triggerEvent(target, event) {
|
||||
const tooltip = this.findTooltipByTarget(target);
|
||||
|
||||
if (tooltip) {
|
||||
this.$refs[tooltip.id][0].$emit(event);
|
||||
}
|
||||
},
|
||||
tooltipExists(element) {
|
||||
return this.tooltips.some(tooltip => tooltip.target === element);
|
||||
return Boolean(this.findTooltipByTarget(element));
|
||||
},
|
||||
findTooltipByTarget(element) {
|
||||
return this.tooltips.find(tooltip => tooltip.target === element);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -86,6 +100,7 @@ export default {
|
|||
<gl-tooltip
|
||||
v-for="(tooltip, index) in tooltips"
|
||||
:id="tooltip.id"
|
||||
:ref="tooltip.id"
|
||||
:key="index"
|
||||
:target="tooltip.target"
|
||||
:triggers="tooltip.triggers"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { toArray } from 'lodash';
|
||||
import Tooltips from './components/tooltips.vue';
|
||||
|
||||
let app;
|
||||
|
@ -31,13 +32,13 @@ const tooltipsApp = () => {
|
|||
}).$mount(container);
|
||||
}
|
||||
|
||||
return app;
|
||||
return app.$refs.tooltips;
|
||||
};
|
||||
|
||||
const isTooltip = (node, selector) => node.matches && node.matches(selector);
|
||||
|
||||
const addTooltips = (elements, config) => {
|
||||
tooltipsApp().$refs.tooltips.addTooltips(Array.from(elements), config);
|
||||
tooltipsApp().addTooltips(toArray(elements), config);
|
||||
};
|
||||
|
||||
const handleTooltipEvent = (rootTarget, e, selector, config = {}) => {
|
||||
|
@ -63,9 +64,14 @@ export const initTooltips = (selector, config = {}) => {
|
|||
return tooltipsApp();
|
||||
};
|
||||
|
||||
export const dispose = elements => {
|
||||
return tooltipsApp().$refs.tooltips.dispose(elements);
|
||||
};
|
||||
const elementsIterator = handler => elements => toArray(elements).forEach(handler);
|
||||
|
||||
export const dispose = elementsIterator(element => tooltipsApp().dispose(element));
|
||||
export const fixTitle = elementsIterator(element => tooltipsApp().fixTitle(element));
|
||||
export const enable = elementsIterator(element => tooltipsApp().triggerEvent(element, 'enable'));
|
||||
export const disable = elementsIterator(element => tooltipsApp().triggerEvent(element, 'disable'));
|
||||
export const hide = elementsIterator(element => tooltipsApp().triggerEvent(element, 'close'));
|
||||
export const show = elementsIterator(element => tooltipsApp().triggerEvent(element, 'open'));
|
||||
|
||||
export const destroy = () => {
|
||||
tooltipsApp().$destroy();
|
||||
|
|
|
@ -83,7 +83,7 @@ export default {
|
|||
</div>
|
||||
<div
|
||||
v-if="$slots['left-secondary']"
|
||||
class="gl-text-gray-500 gl-mt-1 gl-min-h-6 gl-min-w-0 gl-flex-fill-1"
|
||||
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1 gl-min-h-6 gl-min-w-0 gl-flex-fill-1"
|
||||
>
|
||||
<slot name="left-secondary"></slot>
|
||||
</div>
|
||||
|
@ -93,11 +93,14 @@ export default {
|
|||
>
|
||||
<div
|
||||
v-if="$slots['right-primary']"
|
||||
class="gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
|
||||
class="gl-display-flex gl-align-items-center gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
|
||||
>
|
||||
<slot name="right-primary"></slot>
|
||||
</div>
|
||||
<div v-if="$slots['right-secondary']" class="gl-mt-1 gl-min-h-6">
|
||||
<div
|
||||
v-if="$slots['right-secondary']"
|
||||
class="gl-display-flex gl-align-items-center gl-mt-1 gl-min-h-6"
|
||||
>
|
||||
<slot name="right-secondary"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -48,6 +48,14 @@ module Types
|
|||
field :user, Types::UserType, null: true,
|
||||
description: 'Pipeline user',
|
||||
resolve: -> (pipeline, _args, _context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, pipeline.user_id).find }
|
||||
field :retryable, GraphQL::BOOLEAN_TYPE,
|
||||
description: 'Specifies if a pipeline can be retried',
|
||||
method: :retryable?,
|
||||
null: false
|
||||
field :cancelable, GraphQL::BOOLEAN_TYPE,
|
||||
description: 'Specifies if a pipeline can be canceled',
|
||||
method: :cancelable?,
|
||||
null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,6 +44,10 @@
|
|||
%span.light ID:
|
||||
%strong
|
||||
= @user.id
|
||||
%li
|
||||
%span.light= _('Namespace ID:')
|
||||
%strong
|
||||
= @user.namespace_id
|
||||
|
||||
%li.two-factor-status
|
||||
%span.light Two-factor Authentication:
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
track_property: 'navigation',
|
||||
container: 'body' } do
|
||||
= sprite_icon('todo-done')
|
||||
%span.badge.badge-pill.todos-count{ class: ('hidden' if todos_pending_count == 0) }
|
||||
%span.badge.badge-pill.todos-count.js-todos-count{ class: ('hidden' if todos_pending_count == 0) }
|
||||
= todos_count_format(todos_pending_count)
|
||||
%li.nav-item.header-help.dropdown.d-none.d-md-block{ **tracking_attrs('main_navigation', 'click_question_mark_link', 'navigation') }
|
||||
= link_to help_path, class: 'header-help-dropdown-toggle', data: { toggle: "dropdown" } do
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
- default_ref = params[:ref] || @project.default_branch
|
||||
|
||||
- if @error
|
||||
.alert.alert-danger
|
||||
%button.close{ type: "button", "data-dismiss" => "alert" } ×
|
||||
.gl-alert.gl-alert-danger
|
||||
= sprite_icon('error', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
%button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label' => _('Dismiss') }
|
||||
= sprite_icon('close', css_class: 'gl-icon')
|
||||
= @error
|
||||
|
||||
%h3.page-title
|
||||
|
|
|
@ -131,6 +131,14 @@
|
|||
:weight: 1
|
||||
:idempotent:
|
||||
:tags: []
|
||||
- :name: cronjob:ci_platform_metrics_update_cron
|
||||
:feature_category: :continuous_integration
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:resource_boundary: :cpu
|
||||
:weight: 1
|
||||
:idempotent:
|
||||
:tags: []
|
||||
- :name: cronjob:container_expiration_policy
|
||||
:feature_category: :container_registry
|
||||
:has_external_dependencies:
|
||||
|
|
16
app/workers/ci_platform_metrics_update_cron_worker.rb
Normal file
16
app/workers/ci_platform_metrics_update_cron_worker.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CiPlatformMetricsUpdateCronWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
include ApplicationWorker
|
||||
|
||||
# This worker does not perform work scoped to a context
|
||||
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
|
||||
|
||||
feature_category :continuous_integration
|
||||
urgency :low
|
||||
worker_resource_boundary :cpu
|
||||
|
||||
def perform
|
||||
CiPlatformMetric.insert_auto_devops_platform_targets!
|
||||
end
|
||||
end
|
5
changelogs/unreleased/241867-Replace-v-html.yml
Normal file
5
changelogs/unreleased/241867-Replace-v-html.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace v-html with v-safe-html in no_changes.vue
|
||||
merge_request: 41471
|
||||
author: Kev @KevSlashNull
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add namespace ID to user pages in the admin area
|
||||
merge_request: 41877
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Skip warning for file changed during backup instead of failing the backup operation
|
||||
merge_request: 40572
|
||||
author:
|
||||
type: fixed
|
5
changelogs/unreleased/lm-add-pipeline-actions.yml
Normal file
5
changelogs/unreleased/lm-add-pipeline-actions.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'GraphQL: Add retryable and cancelable to PipelineType'
|
||||
merge_request: 40780
|
||||
author:
|
||||
type: added
|
|
@ -456,6 +456,10 @@ production: &base
|
|||
schedule_migrate_external_diffs_worker:
|
||||
cron: "15 * * * *"
|
||||
|
||||
# Update CI Platform Metrics daily
|
||||
ci_platform_metrics_update_cron_worker:
|
||||
cron: "47 9 * * *"
|
||||
|
||||
# GitLab EE only jobs. These jobs are automatically enabled for an EE
|
||||
# installation, and ignored for a CE installation.
|
||||
ee_cron_jobs:
|
||||
|
|
|
@ -511,6 +511,9 @@ Settings.cron_jobs['update_container_registry_info_worker']['job_class'] = 'Upda
|
|||
Settings.cron_jobs['postgres_dynamic_partitions_creator'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['postgres_dynamic_partitions_creator']['cron'] ||= '21 */6 * * *'
|
||||
Settings.cron_jobs['postgres_dynamic_partitions_creator']['job_class'] ||= 'PartitionCreationWorker'
|
||||
Settings.cron_jobs['ci_platform_metrics_update_cron_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['cron'] ||= '47 9 * * *'
|
||||
Settings.cron_jobs['ci_platform_metrics_update_cron_worker']['job_class'] = 'CiPlatformMetricsUpdateCronWorker'
|
||||
|
||||
Gitlab.ee do
|
||||
Settings.cron_jobs['adjourned_group_deletion_worker'] ||= Settingslogic.new({})
|
||||
|
|
|
@ -8555,7 +8555,12 @@ enum IssueType {
|
|||
"""
|
||||
Represents an iteration object.
|
||||
"""
|
||||
type Iteration {
|
||||
type Iteration implements TimeboxBurnupTimeSeriesInterface {
|
||||
"""
|
||||
Daily scope and completed totals for burnup charts
|
||||
"""
|
||||
burnupTimeSeries: [BurnupChartDailyTotals!]
|
||||
|
||||
"""
|
||||
Timestamp of iteration creation
|
||||
"""
|
||||
|
@ -10398,7 +10403,7 @@ type MetricsDashboardAnnotationEdge {
|
|||
"""
|
||||
Represents a milestone.
|
||||
"""
|
||||
type Milestone {
|
||||
type Milestone implements TimeboxBurnupTimeSeriesInterface {
|
||||
"""
|
||||
Daily scope and completed totals for burnup charts
|
||||
"""
|
||||
|
@ -11333,6 +11338,11 @@ type Pipeline {
|
|||
"""
|
||||
beforeSha: String
|
||||
|
||||
"""
|
||||
Specifies if a pipeline can be canceled
|
||||
"""
|
||||
cancelable: Boolean!
|
||||
|
||||
"""
|
||||
Timestamp of the pipeline's commit
|
||||
"""
|
||||
|
@ -11380,6 +11390,11 @@ type Pipeline {
|
|||
"""
|
||||
iid: String!
|
||||
|
||||
"""
|
||||
Specifies if a pipeline can be retried
|
||||
"""
|
||||
retryable: Boolean!
|
||||
|
||||
"""
|
||||
Vulnerability and scanned resource counts for each security scanner of the pipeline
|
||||
"""
|
||||
|
@ -16504,6 +16519,13 @@ Time represented in ISO 8601
|
|||
"""
|
||||
scalar Time
|
||||
|
||||
interface TimeboxBurnupTimeSeriesInterface {
|
||||
"""
|
||||
Daily scope and completed totals for burnup charts
|
||||
"""
|
||||
burnupTimeSeries: [BurnupChartDailyTotals!]
|
||||
}
|
||||
|
||||
type Timelog {
|
||||
"""
|
||||
Timestamp of when the time tracked was spent at. Deprecated in 12.10: Use `spentAt`
|
||||
|
|
|
@ -23567,6 +23567,28 @@
|
|||
"name": "Iteration",
|
||||
"description": "Represents an iteration object.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "burnupTimeSeries",
|
||||
"description": "Daily scope and completed totals for burnup charts",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "BurnupChartDailyTotals",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"description": "Timestamp of iteration creation",
|
||||
|
@ -23798,7 +23820,11 @@
|
|||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "TimeboxBurnupTimeSeriesInterface",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
|
@ -29142,7 +29168,11 @@
|
|||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "TimeboxBurnupTimeSeriesInterface",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
|
@ -33724,6 +33754,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "cancelable",
|
||||
"description": "Specifies if a pipeline can be canceled",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "committedAt",
|
||||
"description": "Timestamp of the pipeline's commit",
|
||||
|
@ -33866,6 +33914,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "retryable",
|
||||
"description": "Specifies if a pipeline can be retried",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "securityReportSummary",
|
||||
"description": "Vulnerability and scanned resource counts for each security scanner of the pipeline",
|
||||
|
@ -48382,6 +48448,50 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "TimeboxBurnupTimeSeriesInterface",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "burnupTimeSeries",
|
||||
"description": "Daily scope and completed totals for burnup charts",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "BurnupChartDailyTotals",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": [
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Iteration",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Milestone",
|
||||
"ofType": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Timelog",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -105,6 +105,8 @@ To display the Deploy Boards for a specific [environment](../../ci/environments/
|
|||
re-deploy your application. If you are using Auto DevOps, this will
|
||||
be done automatically and no action is necessary.
|
||||
|
||||
If you are using GCP to manage clusters, you can see the deployment details in GCP itself by going to **Workloads > deployment name > Details**:
|
||||
|
||||
![Deploy Boards Kubernetes Label](img/deploy_boards_kubernetes_label.png)
|
||||
|
||||
Once all of the above are set up and the pipeline has run at least once,
|
||||
|
|
|
@ -31,10 +31,10 @@ module Backup
|
|||
raise Backup::Error, 'Backup failed'
|
||||
end
|
||||
|
||||
run_pipeline!([%W(#{tar} --warning=no-file-changed --exclude=lost+found -C #{@backup_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
run_pipeline!([%W(#{tar} --exclude=lost+found -C #{@backup_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
FileUtils.rm_rf(@backup_files_dir)
|
||||
else
|
||||
run_pipeline!([%W(#{tar} --warning=no-file-changed --exclude=lost+found -C #{app_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
run_pipeline!([%W(#{tar} --exclude=lost+found -C #{app_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -21,30 +21,47 @@ module Gitlab
|
|||
MD
|
||||
end
|
||||
|
||||
def sorted_fields(fields)
|
||||
fields.sort_by { |field| field[:name] }
|
||||
def render_name_and_description(object)
|
||||
content = "### #{object[:name]}\n"
|
||||
|
||||
if object[:description].present?
|
||||
content += "\n#{object[:description]}\n"
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def sorted_by_name(objects)
|
||||
objects.sort_by { |o| o[:name] }
|
||||
end
|
||||
|
||||
def render_field(field)
|
||||
'| %s | %s | %s |' % [
|
||||
render_field_name(field),
|
||||
render_name(field),
|
||||
render_field_type(field[:type][:info]),
|
||||
render_field_description(field)
|
||||
render_description(field)
|
||||
]
|
||||
end
|
||||
|
||||
def render_field_name(field)
|
||||
rendered_name = "`#{field[:name]}`"
|
||||
rendered_name += ' **{warning-solid}**' if field[:is_deprecated]
|
||||
def render_enum_value(value)
|
||||
'| %s | %s |' % [
|
||||
render_name(value),
|
||||
render_description(value)
|
||||
]
|
||||
end
|
||||
|
||||
def render_name(object)
|
||||
rendered_name = "`#{object[:name]}`"
|
||||
rendered_name += ' **{warning-solid}**' if object[:is_deprecated]
|
||||
rendered_name
|
||||
end
|
||||
|
||||
# Returns the field description. If the field has been deprecated,
|
||||
# Returns the object description. If the object has been deprecated,
|
||||
# the deprecation reason will be returned in place of the description.
|
||||
def render_field_description(field)
|
||||
return field[:description] unless field[:is_deprecated]
|
||||
def render_description(object)
|
||||
return object[:description] unless object[:is_deprecated]
|
||||
|
||||
"**Deprecated:** #{field[:deprecation_reason]}"
|
||||
"**Deprecated:** #{object[:deprecation_reason]}"
|
||||
end
|
||||
|
||||
# Some fields types are arrays of other types and are displayed
|
||||
|
@ -70,6 +87,13 @@ module Gitlab
|
|||
!object_type[:name]["__"]
|
||||
end
|
||||
end
|
||||
|
||||
# We ignore the built-in enum types.
|
||||
def enums
|
||||
graphql_enum_types.select do |enum_type|
|
||||
!enum_type[:name].in?(%w(__DirectiveLocation __TypeKind))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,15 +15,45 @@
|
|||
CAUTION: **Caution:**
|
||||
Fields that are deprecated are marked with **{warning-solid}**.
|
||||
\
|
||||
|
||||
:plain
|
||||
## Object types
|
||||
|
||||
Object types represent the resources that GitLab's GraphQL API can return.
|
||||
They contain _fields_. Each field has its own type, which will either be one of the
|
||||
basic GraphQL [scalar types](https://graphql.org/learn/schema/#scalar-types)
|
||||
(e.g.: `String` or `Boolean`) or other object types.
|
||||
|
||||
For more information, see
|
||||
[Object Types and Fields](https://graphql.org/learn/schema/#object-types-and-fields)
|
||||
on `graphql.org`.
|
||||
\
|
||||
|
||||
- objects.each do |type|
|
||||
- unless type[:fields].empty?
|
||||
= "## #{type[:name]}"
|
||||
- if type[:description]&.present?
|
||||
\
|
||||
= type[:description]
|
||||
\
|
||||
~ "| Name | Type | Description |"
|
||||
~ "| --- | ---- | ---------- |"
|
||||
- sorted_fields(type[:fields]).each do |field|
|
||||
= render_name_and_description(type)
|
||||
~ "| Field | Type | Description |"
|
||||
~ "| ----- | ---- | ----------- |"
|
||||
- sorted_by_name(type[:fields]).each do |field|
|
||||
= render_field(field)
|
||||
\
|
||||
|
||||
:plain
|
||||
## Enumeration types
|
||||
|
||||
Also called _Enums_, enumeration types are a special kind of scalar that
|
||||
is restricted to a particular set of allowed values.
|
||||
|
||||
For more information, see
|
||||
[Enumeration Types](https://graphql.org/learn/schema/#enumeration-types)
|
||||
on `graphql.org`.
|
||||
\
|
||||
|
||||
- enums.each do |enum|
|
||||
- unless enum[:values].empty?
|
||||
= render_name_and_description(enum)
|
||||
~ "| Value | Description |"
|
||||
~ "| ----- | ----------- |"
|
||||
- sorted_by_name(enum[:values]).each do |value|
|
||||
= render_enum_value(value)
|
||||
\
|
||||
|
|
|
@ -16382,6 +16382,9 @@ msgstr ""
|
|||
msgid "Namespace"
|
||||
msgstr ""
|
||||
|
||||
msgid "Namespace ID:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Namespace is empty"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16870,7 +16873,7 @@ msgstr ""
|
|||
msgid "No changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
|
||||
msgid "No changes between %{sourceBranch} and %{targetBranch}"
|
||||
msgstr ""
|
||||
|
||||
msgid "No child epics match applied filters"
|
||||
|
|
|
@ -21,6 +21,8 @@ module RuboCop
|
|||
method_name = node.children[1]
|
||||
return unless TRANSLATION_METHODS.include?(method_name)
|
||||
|
||||
translation_memoized = false
|
||||
|
||||
node.each_ancestor do |ancestor|
|
||||
receiver, _ = *ancestor
|
||||
break if lambda_node?(receiver) # translations defined in lambda nodes should be allowed
|
||||
|
@ -30,6 +32,14 @@ module RuboCop
|
|||
|
||||
break
|
||||
end
|
||||
|
||||
translation_memoized = true if memoization?(ancestor)
|
||||
|
||||
if translation_memoized && class_method_definition?(ancestor)
|
||||
add_offense(node, location: :expression)
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -38,6 +48,14 @@ module RuboCop
|
|||
def constant_assignment?(node)
|
||||
node.type == :casgn
|
||||
end
|
||||
|
||||
def memoization?(node)
|
||||
node.type == :or_asgn
|
||||
end
|
||||
|
||||
def class_method_definition?(node)
|
||||
node.type == :defs
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Profiles::EmailsController do
|
||||
let(:user) { create(:user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
@ -16,36 +16,43 @@ RSpec.describe Profiles::EmailsController do
|
|||
end
|
||||
|
||||
describe '#create' do
|
||||
context 'when email address is valid' do
|
||||
let(:email_params) { { email: "add_email@example.com" } }
|
||||
let(:email) { 'add_email@example.com' }
|
||||
let(:params) { { email: { email: email } } }
|
||||
|
||||
subject { post(:create, params: params) }
|
||||
|
||||
it 'sends an email confirmation' do
|
||||
expect { post(:create, params: { email: email_params }) }.to change { ActionMailer::Base.deliveries.size }
|
||||
end
|
||||
expect { subject }.to change { ActionMailer::Base.deliveries.size }
|
||||
end
|
||||
|
||||
context 'when email address is invalid' do
|
||||
let(:email_params) { { email: "test.@example.com" } }
|
||||
let(:email) { 'invalid.@example.com' }
|
||||
|
||||
it 'does not send an email confirmation' do
|
||||
expect { post(:create, params: { email: email_params }) }.not_to change { ActionMailer::Base.deliveries.size }
|
||||
expect { subject }.not_to change { ActionMailer::Base.deliveries.size }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#resend_confirmation_instructions' do
|
||||
let(:email_params) { { email: "add_email@example.com" } }
|
||||
let_it_be(:email) { create(:email, user: user) }
|
||||
let(:params) { { id: email.id } }
|
||||
|
||||
subject { put(:resend_confirmation_instructions, params: params) }
|
||||
|
||||
it 'resends an email confirmation' do
|
||||
email = user.emails.create(email: 'add_email@example.com')
|
||||
expect { subject }.to change { ActionMailer::Base.deliveries.size }
|
||||
|
||||
expect { put(:resend_confirmation_instructions, params: { id: email }) }.to change { ActionMailer::Base.deliveries.size }
|
||||
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]]
|
||||
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions"
|
||||
expect(ActionMailer::Base.deliveries.last.to).to eq [email.email]
|
||||
expect(ActionMailer::Base.deliveries.last.subject).to match 'Confirmation instructions'
|
||||
end
|
||||
|
||||
it 'unable to resend an email confirmation' do
|
||||
expect { put(:resend_confirmation_instructions, params: { id: 1 }) }.not_to change { ActionMailer::Base.deliveries.size }
|
||||
context 'email does not exist' do
|
||||
let(:params) { { id: non_existing_record_id } }
|
||||
|
||||
it 'does not send an email confirmation' do
|
||||
expect { subject }.not_to change { ActionMailer::Base.deliveries.size }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -279,7 +279,8 @@ RSpec.describe "Admin::Users" do
|
|||
|
||||
expect(page).to have_content(user.email)
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content(user.id)
|
||||
expect(page).to have_content("ID: #{user.id}")
|
||||
expect(page).to have_content("Namespace ID: #{user.namespace_id}")
|
||||
expect(page).to have_button('Deactivate user')
|
||||
expect(page).to have_button('Block user')
|
||||
expect(page).to have_button('Delete user')
|
||||
|
|
|
@ -3,44 +3,53 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Tooltips on .timeago dates', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
|
||||
let(:created_date) { Date.yesterday.to_time }
|
||||
let(:expected_format) { created_date.in_time_zone.strftime('%b %-d, %Y %l:%M%P') }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, name: 'test', namespace: user.namespace) }
|
||||
let(:created_date) { 1.day.ago.beginning_of_minute - 1.hour }
|
||||
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'on the activity tab' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
||||
Event.create( project: project, author_id: user.id, action: :joined,
|
||||
updated_at: created_date, created_at: created_date)
|
||||
|
||||
sign_in user
|
||||
visit user_activity_path(user)
|
||||
wait_for_requests
|
||||
|
||||
page.find('.js-timeago').hover
|
||||
end
|
||||
|
||||
it 'has the datetime formated correctly' do
|
||||
expect(page).to have_selector('.local-timeago', text: expected_format)
|
||||
expect(page).to have_selector('.js-timeago', text: '1 day ago')
|
||||
|
||||
page.find('.js-timeago').hover
|
||||
|
||||
expect(datetime_in_tooltip).to eq(created_date)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on the snippets tab' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
create(:snippet, author: user, updated_at: created_date, created_at: created_date)
|
||||
|
||||
sign_in user
|
||||
visit user_snippets_path(user)
|
||||
wait_for_requests
|
||||
|
||||
page.find('.js-timeago.snippet-created-ago').hover
|
||||
end
|
||||
|
||||
it 'has the datetime formated correctly' do
|
||||
expect(page).to have_selector('.local-timeago', text: expected_format)
|
||||
expect(page).to have_selector('.js-timeago.snippet-created-ago', text: '1 day ago')
|
||||
|
||||
page.find('.js-timeago.snippet-created-ago').hover
|
||||
|
||||
expect(datetime_in_tooltip).to eq(created_date)
|
||||
end
|
||||
end
|
||||
|
||||
def datetime_in_tooltip
|
||||
datetime_text = page.find('.local-timeago').text
|
||||
DateTime.parse(datetime_text)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,6 +52,7 @@ describe('Design management design todo button', () => {
|
|||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders TodoButton component', () => {
|
||||
|
@ -68,7 +69,14 @@ describe('Design management design todo button', () => {
|
|||
});
|
||||
|
||||
describe('when clicked', () => {
|
||||
let dispatchEventSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||
innerText: 2,
|
||||
});
|
||||
|
||||
createComponent({ design: mockDesignWithPendingTodos }, { mountFn: mount });
|
||||
wrapper.trigger('click');
|
||||
return wrapper.vm.$nextTick();
|
||||
|
@ -86,6 +94,14 @@ describe('Design management design todo button', () => {
|
|||
expect(mutate).toHaveBeenCalledTimes(1);
|
||||
expect(mutate).toHaveBeenCalledWith(todoMarkDoneMutationVariables);
|
||||
});
|
||||
|
||||
it('calls dispatchDocumentEvent to update global To-Do counter correctly', () => {
|
||||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchedEvent.detail).toEqual({ count: 1 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,7 +115,14 @@ describe('Design management design todo button', () => {
|
|||
});
|
||||
|
||||
describe('when clicked', () => {
|
||||
let dispatchEventSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
|
||||
jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||
innerText: 2,
|
||||
});
|
||||
|
||||
createComponent({}, { mountFn: mount });
|
||||
wrapper.trigger('click');
|
||||
return wrapper.vm.$nextTick();
|
||||
|
@ -112,6 +135,7 @@ describe('Design management design todo button', () => {
|
|||
variables: {
|
||||
atVersion: null,
|
||||
filenames: ['my-design.jpg'],
|
||||
designId: '1',
|
||||
issueId: '1',
|
||||
issueIid: '10',
|
||||
projectPath: 'project-path',
|
||||
|
@ -121,6 +145,14 @@ describe('Design management design todo button', () => {
|
|||
expect(mutate).toHaveBeenCalledTimes(1);
|
||||
expect(mutate).toHaveBeenCalledWith(createDesignTodoMutationVariables);
|
||||
});
|
||||
|
||||
it('calls dispatchDocumentEvent to update global To-Do counter correctly', () => {
|
||||
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
|
||||
|
||||
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchedEvent.detail).toEqual({ count: 3 });
|
||||
expect(dispatchedEvent.type).toBe('todo:toggle');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import initTodoToggle, { initNavUserDropdownTracking } from '~/header';
|
|||
|
||||
describe('Header', () => {
|
||||
describe('Todos notification', () => {
|
||||
const todosPendingCount = '.todos-count';
|
||||
const todosPendingCount = '.js-todos-count';
|
||||
const fixtureTemplate = 'issues/open-issue.html';
|
||||
|
||||
function isTodosCountHidden() {
|
||||
|
|
|
@ -40,7 +40,7 @@ exports[`packages_list_row renders 1`] = `
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="gl-text-gray-500 gl-mt-1 gl-min-h-6 gl-min-w-0 gl-flex-fill-1"
|
||||
class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1 gl-min-h-6 gl-min-w-0 gl-flex-fill-1"
|
||||
>
|
||||
<div
|
||||
class="gl-display-flex"
|
||||
|
@ -94,7 +94,7 @@ exports[`packages_list_row renders 1`] = `
|
|||
class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500 gl-flex-shrink-0"
|
||||
>
|
||||
<div
|
||||
class="gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
|
||||
class="gl-display-flex gl-align-items-center gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
|
||||
>
|
||||
<publish-method-stub
|
||||
packageentity="[object Object]"
|
||||
|
@ -102,11 +102,13 @@ exports[`packages_list_row renders 1`] = `
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="gl-mt-1 gl-min-h-6"
|
||||
class="gl-display-flex gl-align-items-center gl-mt-1 gl-min-h-6"
|
||||
>
|
||||
<span>
|
||||
<gl-sprintf-stub
|
||||
message="Created %{timestamp}"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`publish_method renders 1`] = `
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-mb-2"
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
>
|
||||
<gl-icon-stub
|
||||
class="gl-mr-2"
|
||||
|
|
|
@ -120,7 +120,7 @@ describe('tooltips/components/tooltips.vue', () => {
|
|||
wrapper.vm.addTooltips([target, createTooltipTarget()]);
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
wrapper.vm.dispose([target]);
|
||||
wrapper.vm.dispose(target);
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(allTooltips()).toHaveLength(1);
|
||||
|
@ -148,6 +148,48 @@ describe('tooltips/components/tooltips.vue', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('triggerEvent', () => {
|
||||
it('triggers a bootstrap-vue tooltip global event for the tooltip specified', async () => {
|
||||
const target = createTooltipTarget();
|
||||
const event = 'hide';
|
||||
|
||||
buildWrapper();
|
||||
|
||||
wrapper.vm.addTooltips([target]);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
wrapper.vm.triggerEvent(target, event);
|
||||
|
||||
expect(wrapper.find(GlTooltip).emitted(event)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fixTitle', () => {
|
||||
it('updates tooltip content with the latest value the target title property', async () => {
|
||||
const target = createTooltipTarget();
|
||||
const currentTitle = 'title';
|
||||
const newTitle = 'new title';
|
||||
|
||||
target.setAttribute('title', currentTitle);
|
||||
|
||||
buildWrapper();
|
||||
|
||||
wrapper.vm.addTooltips([target]);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find(GlTooltip).text()).toBe(currentTitle);
|
||||
|
||||
target.setAttribute('title', newTitle);
|
||||
wrapper.vm.fixTitle(target);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find(GlTooltip).text()).toBe(newTitle);
|
||||
});
|
||||
});
|
||||
|
||||
it('disconnects mutation observer on beforeDestroy', () => {
|
||||
buildWrapper();
|
||||
wrapper.vm.addTooltips([createTooltipTarget()]);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { initTooltips, dispose, destroy } from '~/tooltips';
|
||||
import { initTooltips, dispose, destroy, hide, show, enable, disable, fixTitle } from '~/tooltips';
|
||||
|
||||
describe('tooltips/index.js', () => {
|
||||
let tooltipsApp;
|
||||
|
@ -80,4 +80,41 @@ describe('tooltips/index.js', () => {
|
|||
expect(document.querySelector('.gl-tooltip')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
methodName | method | event
|
||||
${'enable'} | ${enable} | ${'enable'}
|
||||
${'disable'} | ${disable} | ${'disable'}
|
||||
${'hide'} | ${hide} | ${'close'}
|
||||
${'show'} | ${show} | ${'open'}
|
||||
`(
|
||||
'$methodName calls triggerEvent in tooltip app with $event event',
|
||||
async ({ method, event }) => {
|
||||
const target = createTooltipTarget();
|
||||
|
||||
buildTooltipsApp();
|
||||
|
||||
await tooltipsApp.$nextTick();
|
||||
|
||||
jest.spyOn(tooltipsApp, 'triggerEvent');
|
||||
|
||||
method([target]);
|
||||
|
||||
expect(tooltipsApp.triggerEvent).toHaveBeenCalledWith(target, event);
|
||||
},
|
||||
);
|
||||
|
||||
it('fixTitle calls fixTitle in tooltip app with the target specified', async () => {
|
||||
const target = createTooltipTarget();
|
||||
|
||||
buildTooltipsApp();
|
||||
|
||||
await tooltipsApp.$nextTick();
|
||||
|
||||
jest.spyOn(tooltipsApp, 'fixTitle');
|
||||
|
||||
fixTitle([target]);
|
||||
|
||||
expect(tooltipsApp.fixTitle).toHaveBeenCalledWith(target);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -198,6 +198,7 @@ RSpec.describe IssuablesHelper do
|
|||
initialDescriptionHtml: '<p dir="auto">issue text</p>',
|
||||
initialDescriptionText: 'issue text',
|
||||
initialTaskStatus: '0 of 0 tasks completed',
|
||||
issueType: 'issue',
|
||||
iid: issue.iid.to_s
|
||||
}
|
||||
expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
|
||||
|
|
|
@ -36,10 +36,10 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
|
|||
|
||||
specify do
|
||||
expectation = <<~DOC
|
||||
## ArrayTest
|
||||
### ArrayTest
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `foo` | String! => Array | A description |
|
||||
DOC
|
||||
|
||||
|
@ -59,10 +59,10 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
|
|||
|
||||
specify do
|
||||
expectation = <<~DOC
|
||||
## OrderingTest
|
||||
### OrderingTest
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `bar` | String! | A description of bar field |
|
||||
| `foo` | String! | A description of foo field |
|
||||
DOC
|
||||
|
@ -82,15 +82,45 @@ RSpec.describe Gitlab::Graphql::Docs::Renderer do
|
|||
|
||||
specify do
|
||||
expectation = <<~DOC
|
||||
## DeprecatedTest
|
||||
### DeprecatedTest
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated. Deprecated in 1.10 |
|
||||
DOC
|
||||
|
||||
is_expected.to include(expectation)
|
||||
end
|
||||
end
|
||||
|
||||
context 'A type with an emum field' do
|
||||
let(:type) do
|
||||
enum_type = Class.new(Types::BaseEnum) do
|
||||
graphql_name 'MyEnum'
|
||||
|
||||
value 'BAZ', description: 'A description of BAZ'
|
||||
value 'BAR', description: 'A description of BAR', deprecation_reason: 'This is deprecated'
|
||||
end
|
||||
|
||||
Class.new(Types::BaseObject) do
|
||||
graphql_name 'EnumTest'
|
||||
|
||||
field :foo, enum_type, null: false, description: 'A description of foo field'
|
||||
end
|
||||
end
|
||||
|
||||
specify do
|
||||
expectation = <<~DOC
|
||||
### MyEnum
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| `BAR` **{warning-solid}** | **Deprecated:** This is deprecated |
|
||||
| `BAZ` | A description of BAZ |
|
||||
DOC
|
||||
|
||||
is_expected.to include(expectation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,6 +38,17 @@ RSpec.describe RuboCop::Cop::StaticTranslationDefinition, type: :rubocop do
|
|||
['A = _("a")', '_("a")', 1],
|
||||
['B = s_("b")', 's_("b")', 1],
|
||||
['C = n_("c")', 'n_("c")', 1],
|
||||
[
|
||||
<<~CODE,
|
||||
class MyClass
|
||||
def self.translations
|
||||
@cache ||= { hello: _("hello") }
|
||||
end
|
||||
end
|
||||
CODE
|
||||
'_("hello")',
|
||||
3
|
||||
],
|
||||
[
|
||||
<<~CODE,
|
||||
module MyModule
|
||||
|
@ -78,6 +89,20 @@ RSpec.describe RuboCop::Cop::StaticTranslationDefinition, type: :rubocop do
|
|||
'CONSTANT_1 = __("a")',
|
||||
'CONSTANT_2 = s__("a")',
|
||||
'CONSTANT_3 = n__("a")',
|
||||
<<~CODE,
|
||||
class MyClass
|
||||
def self.method
|
||||
@cache ||= { hello: -> { _("hello") } }
|
||||
end
|
||||
end
|
||||
CODE
|
||||
<<~CODE,
|
||||
class MyClass
|
||||
def method
|
||||
@cache ||= { hello: _("hello") }
|
||||
end
|
||||
end
|
||||
CODE
|
||||
<<~CODE,
|
||||
def method
|
||||
s_('a')
|
||||
|
|
15
spec/workers/ci_platform_metrics_update_cron_worker_spec.rb
Normal file
15
spec/workers/ci_platform_metrics_update_cron_worker_spec.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe CiPlatformMetricsUpdateCronWorker, type: :worker do
|
||||
describe '#perform' do
|
||||
subject { described_class.new.perform }
|
||||
|
||||
it 'inserts new platform metrics' do
|
||||
expect(CiPlatformMetric).to receive(:insert_auto_devops_platform_targets!).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue