Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4b9ace6c1f
commit
4fa04f789e
|
@ -18,17 +18,6 @@ Capybara/CurrentPathExpectation:
|
|||
Layout/ArgumentAlignment:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 13
|
||||
# Cop supports --auto-correct.
|
||||
Layout/ClosingParenthesisIndentation:
|
||||
Exclude:
|
||||
- 'db/post_migrate/20180704145007_update_project_indexes.rb'
|
||||
- 'ee/db/geo/migrate/20180405074130_add_partial_index_project_repository_verification.rb'
|
||||
- 'spec/services/issues/resolve_discussions_spec.rb'
|
||||
- 'spec/services/projects/update_service_spec.rb'
|
||||
- 'spec/support/helpers/stub_object_storage.rb'
|
||||
- 'spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb'
|
||||
|
||||
# Offense count: 72
|
||||
# Cop supports --auto-correct.
|
||||
Layout/EmptyLinesAroundArguments:
|
||||
|
@ -57,17 +46,6 @@ Layout/FirstArrayElementIndentation:
|
|||
Layout/FirstHashElementIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: consistent, align_parentheses
|
||||
Layout/FirstParameterIndentation:
|
||||
Exclude:
|
||||
- 'lib/gitlab/cross_project_access.rb'
|
||||
- 'lib/gitlab/data_builder/push.rb'
|
||||
- 'spec/support/helpers/repo_helpers.rb'
|
||||
- 'spec/support/helpers/stub_object_storage.rb'
|
||||
|
||||
# Offense count: 2164
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
|
||||
|
@ -91,33 +69,11 @@ Layout/LineLength:
|
|||
Layout/MultilineOperationIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 9
|
||||
# Cop supports --auto-correct.
|
||||
Layout/RescueEnsureAlignment:
|
||||
Exclude:
|
||||
- 'app/models/blob_viewer/dependency_manager.rb'
|
||||
- 'app/models/project.rb'
|
||||
- 'app/services/prometheus/proxy_service.rb'
|
||||
- 'app/workers/delete_stored_files_worker.rb'
|
||||
- 'config/initializers/1_settings.rb'
|
||||
- 'config/initializers/trusted_proxies.rb'
|
||||
- 'lib/gitlab/background_migration/archive_legacy_traces.rb'
|
||||
- 'lib/gitlab/highlight.rb'
|
||||
- 'lib/tasks/gitlab/lfs/migrate.rake'
|
||||
|
||||
# Offense count: 36
|
||||
# Cop supports --auto-correct.
|
||||
Layout/SpaceAroundMethodCallOperator:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowForAlignment.
|
||||
Layout/SpaceBeforeFirstArg:
|
||||
Exclude:
|
||||
- 'spec/requests/api/runner_spec.rb'
|
||||
- 'spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb'
|
||||
|
||||
# Offense count: 642
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
|
@ -153,15 +109,6 @@ Lint/NonDeterministicRequireOrder:
|
|||
- 'qa/spec/spec_helper.rb'
|
||||
- 'spec/spec_helper.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# Configuration parameters: AllowedImplicitNamespaces.
|
||||
# AllowedImplicitNamespaces: Gem
|
||||
Lint/RaiseException:
|
||||
Exclude:
|
||||
- 'db/migrate/20190402150158_backport_enterprise_schema.rb'
|
||||
- 'ee/spec/requests/api/helpers_spec.rb'
|
||||
- 'spec/requests/api/helpers_spec.rb'
|
||||
|
||||
# Offense count: 27
|
||||
# Cop supports --auto-correct.
|
||||
Lint/RedundantCopDisableDirective:
|
||||
|
@ -818,14 +765,6 @@ Style/RescueModifier:
|
|||
Style/RescueStandardError:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Cop supports --auto-correct.
|
||||
Style/SelfAssignment:
|
||||
Exclude:
|
||||
- 'app/models/concerns/bulk_member_access_load.rb'
|
||||
- 'app/serializers/base_serializer.rb'
|
||||
- 'spec/support/import_export/configuration_helper.rb'
|
||||
|
||||
# Offense count: 50
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: AllowIfMethodIsEmpty.
|
||||
|
|
|
@ -1 +1 @@
|
|||
12dcff902c9a2178fa6f4992d9d562ad9b422dd2
|
||||
12d115c50517935dc8e7e2e1248aa450bf00710e
|
||||
|
|
|
@ -37,6 +37,11 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
gitlabIssuesEnabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
upgradePlanPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -133,7 +138,7 @@ export default {
|
|||
:disabled="!enableJiraIssues"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<p>
|
||||
<p v-if="gitlabIssuesEnabled">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
|
|
|
@ -34,6 +34,7 @@ function parseDatasetToProps(data) {
|
|||
enableComments,
|
||||
showJiraIssuesIntegration,
|
||||
enableJiraIssues,
|
||||
gitlabIssuesEnabled,
|
||||
} = parseBooleanInData(booleanAttributes);
|
||||
|
||||
return {
|
||||
|
@ -50,6 +51,7 @@ function parseDatasetToProps(data) {
|
|||
showJiraIssuesIntegration,
|
||||
initialEnableJiraIssues: enableJiraIssues,
|
||||
initialProjectKey: projectKey,
|
||||
gitlabIssuesEnabled,
|
||||
upgradePlanPath,
|
||||
editProjectPath,
|
||||
},
|
||||
|
|
|
@ -158,7 +158,7 @@ export default {
|
|||
value: this.issuable.merge_requests_count,
|
||||
title: __('Related merge requests'),
|
||||
dataTestId: 'merge-requests',
|
||||
class: 'js-merge-requests icon-merge-request-unmerged',
|
||||
class: 'js-merge-requests',
|
||||
icon: 'merge-request',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,54 +1,111 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
/**
|
||||
* Returns the attributes used for gl-empty-state in the Service Desk issues list.
|
||||
* Generates empty state messages for Service Desk issues list.
|
||||
*
|
||||
* @param {emptyStateMeta} emptyStateMeta - Meta data used to generate empty state messages
|
||||
* @returns {Object} Object containing empty state messages generated using the meta data.
|
||||
*/
|
||||
export function emptyStateHelper(emptyStateMeta) {
|
||||
const { isServiceDeskSupported, svgPath, serviceDeskHelpPage } = emptyStateMeta;
|
||||
export function generateMessages(emptyStateMeta) {
|
||||
const {
|
||||
svgPath,
|
||||
serviceDeskHelpPage,
|
||||
serviceDeskAddress,
|
||||
editProjectPage,
|
||||
incomingEmailHelpPage,
|
||||
} = emptyStateMeta;
|
||||
|
||||
if (isServiceDeskSupported) {
|
||||
const title = __(
|
||||
'Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab',
|
||||
);
|
||||
const commonMessage = __(
|
||||
'Those emails automatically become issues (with the comments becoming the email conversation) listed here.',
|
||||
);
|
||||
const commonDescription = `
|
||||
<span>${commonMessage}</span>
|
||||
<a href="${serviceDeskHelpPage}">${__('Read more')}</a>`;
|
||||
const serviceDeskSupportedTitle = __(
|
||||
'Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab',
|
||||
);
|
||||
|
||||
if (emptyStateMeta.canEditProjectSettings && emptyStateMeta.isServiceDeskEnabled) {
|
||||
return {
|
||||
title,
|
||||
svgPath,
|
||||
description: `<p>${__('Have your users email')} <code>${
|
||||
emptyStateMeta.serviceDeskAddress
|
||||
}</code></p> ${commonDescription}`,
|
||||
};
|
||||
}
|
||||
const serviceDeskSupportedMessage = __(
|
||||
'Those emails automatically become issues (with the comments becoming the email conversation) listed here.',
|
||||
);
|
||||
|
||||
if (emptyStateMeta.canEditProjectSettings && !emptyStateMeta.isServiceDeskEnabled) {
|
||||
return {
|
||||
title,
|
||||
svgPath,
|
||||
description: commonDescription,
|
||||
primaryLink: emptyStateMeta.editProjectPage,
|
||||
primaryText: __('Turn on Service Desk'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
svgPath,
|
||||
description: commonDescription,
|
||||
};
|
||||
}
|
||||
const commonDescription = `
|
||||
<span>${serviceDeskSupportedMessage}</span>
|
||||
<a href="${serviceDeskHelpPage}">${__('Read more')}</a>`;
|
||||
|
||||
return {
|
||||
title: __('Service Desk is enabled but not yet active'),
|
||||
svgPath,
|
||||
description: __('You must set up incoming email before it becomes active.'),
|
||||
primaryLink: emptyStateMeta.incomingEmailHelpPage,
|
||||
primaryText: __('More information'),
|
||||
serviceDeskEnabledAndCanEditProjectSettings: {
|
||||
title: serviceDeskSupportedTitle,
|
||||
svgPath,
|
||||
description: `<p>${__('Have your users email')}
|
||||
<code>${serviceDeskAddress}</code>
|
||||
</p>
|
||||
${commonDescription}`,
|
||||
},
|
||||
serviceDeskEnabledAndCannotEditProjectSettings: {
|
||||
title: serviceDeskSupportedTitle,
|
||||
svgPath,
|
||||
description: commonDescription,
|
||||
},
|
||||
serviceDeskDisabledAndCanEditProjectSettings: {
|
||||
title: serviceDeskSupportedTitle,
|
||||
svgPath,
|
||||
description: commonDescription,
|
||||
primaryLink: editProjectPage,
|
||||
primaryText: __('Turn on Service Desk'),
|
||||
},
|
||||
serviceDeskDisabledAndCannotEditProjectSettings: {
|
||||
title: serviceDeskSupportedTitle,
|
||||
svgPath,
|
||||
description: commonDescription,
|
||||
},
|
||||
serviceDeskIsNotSupported: {
|
||||
title: __('Service Desk is not supported'),
|
||||
svgPath,
|
||||
description: __(
|
||||
'In order to enable Service Desk for your instance, you must first set up incoming email.',
|
||||
),
|
||||
primaryLink: incomingEmailHelpPage,
|
||||
primaryText: __('More information'),
|
||||
},
|
||||
serviceDeskIsNotEnabled: {
|
||||
title: __('Service Desk is not enabled'),
|
||||
svgPath,
|
||||
description: __(
|
||||
'For help setting up the Service Desk for your instance, please contact an administrator.',
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attributes used for gl-empty-state in the Service Desk issues list.
|
||||
*
|
||||
* @param {Object} emptyStateMeta - Meta data used to generate empty state messages
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function emptyStateHelper(emptyStateMeta) {
|
||||
const messages = generateMessages(emptyStateMeta);
|
||||
|
||||
const { isServiceDeskSupported, canEditProjectSettings, isServiceDeskEnabled } = emptyStateMeta;
|
||||
|
||||
if (isServiceDeskSupported) {
|
||||
if (isServiceDeskEnabled && canEditProjectSettings) {
|
||||
return messages.serviceDeskEnabledAndCanEditProjectSettings;
|
||||
}
|
||||
|
||||
if (isServiceDeskEnabled && !canEditProjectSettings) {
|
||||
return messages.serviceDeskEnabledAndCannotEditProjectSettings;
|
||||
}
|
||||
|
||||
// !isServiceDeskEnabled && canEditProjectSettings
|
||||
if (canEditProjectSettings) {
|
||||
return messages.serviceDeskDisabledAndCanEditProjectSettings;
|
||||
}
|
||||
|
||||
// !isServiceDeskEnabled && !canEditProjectSettings
|
||||
return messages.serviceDeskDisabledAndCannotEditProjectSettings;
|
||||
}
|
||||
|
||||
// !serviceDeskSupported && canEditProjectSettings
|
||||
if (canEditProjectSettings) {
|
||||
return messages.serviceDeskIsNotSupported;
|
||||
}
|
||||
|
||||
// !serviceDeskSupported && !canEditProjectSettings
|
||||
return messages.serviceDeskIsNotEnabled;
|
||||
}
|
||||
|
|
|
@ -48,11 +48,16 @@ export default {
|
|||
return {
|
||||
preAnimation: false,
|
||||
pulseAnimation: false,
|
||||
initialUpdate: true,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
descriptionHtml() {
|
||||
this.animateChange();
|
||||
descriptionHtml(newDescription, oldDescription) {
|
||||
if (!this.initialUpdate && newDescription !== oldDescription) {
|
||||
this.animateChange();
|
||||
} else {
|
||||
this.initialUpdate = false;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.renderGFM();
|
||||
|
|
|
@ -20,20 +20,25 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
pinnedLinks() {
|
||||
return [
|
||||
{
|
||||
const links = [];
|
||||
if (this.publishedIncidentUrl) {
|
||||
links.push({
|
||||
id: 'publishedIncidentUrl',
|
||||
url: this.publishedIncidentUrl,
|
||||
text: STATUS_PAGE_PUBLISHED,
|
||||
icon: 'tanuki',
|
||||
},
|
||||
{
|
||||
});
|
||||
}
|
||||
if (this.zoomMeetingUrl) {
|
||||
links.push({
|
||||
id: 'zoomMeetingUrl',
|
||||
url: this.zoomMeetingUrl,
|
||||
text: JOIN_ZOOM_MEETING,
|
||||
icon: 'brand-zoom',
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return links;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -45,7 +50,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-justify-content-start">
|
||||
<div v-if="pinnedLinks && pinnedLinks.length" class="gl-display-flex gl-justify-content-start">
|
||||
<template v-for="(link, i) in pinnedLinks">
|
||||
<div v-if="link.url" :key="link.id" :class="{ 'gl-pr-3': needsPaddingClass(i) }">
|
||||
<gl-button
|
||||
|
|
|
@ -4,6 +4,7 @@ import PackageTags from './package_tags.vue';
|
|||
import PublishMethod from './publish_method.vue';
|
||||
import { getPackageTypeLabel } from '../utils';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
|
||||
export default {
|
||||
name: 'PackageListRow',
|
||||
|
@ -14,6 +15,7 @@ export default {
|
|||
GlSprintf,
|
||||
PackageTags,
|
||||
PublishMethod,
|
||||
ListItem,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -59,14 +61,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-responsive-table-row" data-qa-selector="packages-row">
|
||||
<div class="table-section section-50 d-flex flex-md-column justify-content-between flex-wrap">
|
||||
<div class="d-flex align-items-center mr-2">
|
||||
<gl-link
|
||||
:href="packageLink"
|
||||
data-qa-selector="package_link"
|
||||
class="text-dark font-weight-bold mb-md-1"
|
||||
>
|
||||
<list-item data-qa-selector="packages-row">
|
||||
<template #left-primary>
|
||||
<div class="gl-display-flex gl-align-items-center gl-mr-3">
|
||||
<gl-link :href="packageLink" class="gl-text-body" data-qa-selector="package_link">
|
||||
{{ packageEntity.name }}
|
||||
</gl-link>
|
||||
|
||||
|
@ -78,52 +76,51 @@ export default {
|
|||
:tag-display-limit="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="d-flex text-secondary text-truncate mt-md-2">
|
||||
</template>
|
||||
<template #left-secondary>
|
||||
<div class="gl-display-flex">
|
||||
<span>{{ packageEntity.version }}</span>
|
||||
|
||||
<div v-if="hasPipeline" class="d-none d-md-inline-block ml-1">
|
||||
<div v-if="hasPipeline" class="gl-display-none gl-display-sm-flex gl-ml-2">
|
||||
<gl-sprintf :message="s__('PackageRegistry|published by %{author}')">
|
||||
<template #author>{{ packageEntity.pipeline.user.name }}</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
|
||||
<div v-if="hasProjectLink" class="d-flex align-items-center">
|
||||
<gl-icon name="review-list" class="text-secondary ml-2 mr-1" />
|
||||
<div v-if="hasProjectLink" class="gl-display-flex gl-align-items-center">
|
||||
<gl-icon name="review-list" class="gl-ml-3 gl-mr-2" />
|
||||
|
||||
<gl-link
|
||||
class="gl-text-body"
|
||||
data-testid="packages-row-project"
|
||||
:href="`/${packageEntity.project_path}`"
|
||||
class="text-secondary"
|
||||
>{{ packageEntity.projectPathName }}</gl-link
|
||||
>
|
||||
{{ packageEntity.projectPathName }}
|
||||
</gl-link>
|
||||
</div>
|
||||
|
||||
<div v-if="showPackageType" class="d-flex align-items-center" data-testid="package-type">
|
||||
<gl-icon name="package" class="text-secondary ml-2 mr-1" />
|
||||
<gl-icon name="package" class="gl-ml-3 gl-mr-2" />
|
||||
<span>{{ packageType }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
class="table-section d-flex flex-md-column justify-content-between align-items-md-end"
|
||||
:class="disableDelete ? 'section-50' : 'section-40'"
|
||||
>
|
||||
<template #right-primary>
|
||||
<publish-method :package-entity="packageEntity" :is-group="isGroup" />
|
||||
</template>
|
||||
|
||||
<div class="text-secondary order-0 order-md-1 mt-md-2">
|
||||
<gl-sprintf :message="__('Created %{timestamp}')">
|
||||
<template #timestamp>
|
||||
<span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
|
||||
{{ timeFormatted(packageEntity.created_at) }}
|
||||
</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
</div>
|
||||
<template #right-secondary>
|
||||
<gl-sprintf :message="__('Created %{timestamp}')">
|
||||
<template #timestamp>
|
||||
<span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
|
||||
{{ timeFormatted(packageEntity.created_at) }}
|
||||
</span>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
|
||||
<div v-if="!disableDelete" class="table-section section-10 d-flex justify-content-end">
|
||||
<template v-if="!disableDelete" #right-action>
|
||||
<gl-button
|
||||
data-testid="action-delete"
|
||||
icon="remove"
|
||||
|
@ -134,6 +131,6 @@ export default {
|
|||
:disabled="!packageEntity._links.delete_api_path"
|
||||
@click="$emit('packageToDelete', packageEntity)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</list-item>
|
||||
</template>
|
||||
|
|
|
@ -36,10 +36,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex align-items-center text-secondary order-1 order-md-0 mb-md-1">
|
||||
<div class="d-flex align-items-center order-1 order-md-0 mb-md-1">
|
||||
<template v-if="hasPipeline">
|
||||
<gl-icon name="git-merge" class="mr-1" />
|
||||
<strong ref="pipeline-ref" class="mr-1 text-dark">{{ packageEntity.pipeline.ref }}</strong>
|
||||
<span ref="pipeline-ref" class="mr-1">{{ packageEntity.pipeline.ref }}</span>
|
||||
|
||||
<gl-icon name="commit" class="mr-1" />
|
||||
<gl-link ref="pipeline-sha" :href="linkToCommit" class="mr-1">{{ packageShaShort }}</gl-link>
|
||||
|
@ -47,15 +47,13 @@ export default {
|
|||
<clipboard-button
|
||||
:text="packageEntity.pipeline.sha"
|
||||
:title="__('Copy commit SHA')"
|
||||
css-class="border-0 text-secondary py-0 px-1"
|
||||
css-class="border-0 py-0 px-1"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<gl-icon name="upload" class="mr-1" />
|
||||
<strong ref="manual-ref" class="text-dark">{{
|
||||
s__('PackageRegistry|Manually Published')
|
||||
}}</strong>
|
||||
<span ref="manual-ref">{{ s__('PackageRegistry|Manually Published') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -47,7 +47,6 @@ export default {
|
|||
:disabled="disabled"
|
||||
:title="title"
|
||||
:aria-label="title"
|
||||
category="secondary"
|
||||
variant="danger"
|
||||
icon="remove"
|
||||
@click="$emit('delete')"
|
||||
|
|
|
@ -67,7 +67,6 @@ export default {
|
|||
:key="tag.path"
|
||||
:tag="tag"
|
||||
:first="index === 0"
|
||||
:last="index === tags.length - 1"
|
||||
:selected="selectedItems[tag.name]"
|
||||
:is-desktop="isDesktop"
|
||||
@select="updateSelectedItems(tag.name)"
|
||||
|
|
|
@ -5,8 +5,8 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
|||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { formatDate } from '~/lib/utils/datetime_utility';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import DeleteButton from '../delete_button.vue';
|
||||
import ListItem from '../list_item.vue';
|
||||
import DetailsRow from '~/registry/shared/components/details_row.vue';
|
||||
import {
|
||||
REMOVE_TAG_BUTTON_TITLE,
|
||||
|
|
|
@ -38,7 +38,6 @@ export default {
|
|||
:key="index"
|
||||
:item="listItem"
|
||||
:first="index === 0"
|
||||
:last="index === images.length - 1"
|
||||
@delete="$emit('delete', $event)"
|
||||
/>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { GlTooltipDirective, GlIcon, GlSprintf } from '@gitlab/ui';
|
||||
import { n__ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import ListItem from '../list_item.vue';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import DeleteButton from '../delete_button.vue';
|
||||
|
||||
import {
|
||||
|
|
|
@ -10,11 +10,6 @@ export default {
|
|||
default: false,
|
||||
required: false,
|
||||
},
|
||||
last: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -35,12 +30,10 @@ export default {
|
|||
computed: {
|
||||
optionalClasses() {
|
||||
return {
|
||||
'gl-border-t-1': !this.first,
|
||||
'gl-border-t-2': this.first,
|
||||
'gl-border-b-1': !this.last,
|
||||
'gl-border-b-2': this.last,
|
||||
'gl-border-t-transparent': !this.first && !this.selected,
|
||||
'gl-border-t-gray-100': this.first && !this.selected,
|
||||
'disabled-content': this.disabled,
|
||||
'gl-border-gray-100': !this.selected,
|
||||
'gl-border-b-gray-100': !this.selected,
|
||||
'gl-bg-blue-50 gl-border-blue-200': this.selected,
|
||||
};
|
||||
},
|
||||
|
@ -58,21 +51,26 @@ export default {
|
|||
|
||||
<template>
|
||||
<div
|
||||
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid"
|
||||
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1"
|
||||
:class="optionalClasses"
|
||||
>
|
||||
<div class="gl-display-flex gl-align-items-center gl-py-4 gl-px-2">
|
||||
<div class="gl-display-flex gl-align-items-center gl-py-5">
|
||||
<div
|
||||
v-if="$slots['left-action']"
|
||||
class="gl-w-7 gl-display-none gl-display-sm-flex gl-justify-content-start gl-pl-2"
|
||||
>
|
||||
<slot name="left-action"></slot>
|
||||
</div>
|
||||
<div class="gl-display-flex gl-flex-direction-column gl-flex-fill-1">
|
||||
<div
|
||||
class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-fill-1"
|
||||
>
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-text-body gl-font-weight-bold"
|
||||
class="gl-display-flex gl-flex-direction-column gl-justify-content-space-between gl-xs-mb-3"
|
||||
>
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
<div
|
||||
v-if="$slots['left-primary']"
|
||||
class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6"
|
||||
>
|
||||
<slot name="left-primary"></slot>
|
||||
<gl-button
|
||||
v-if="detailsSlots.length > 0"
|
||||
|
@ -83,24 +81,27 @@ export default {
|
|||
@click="toggleDetails"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="right-primary"></slot>
|
||||
<div v-if="$slots['left-secondary']" class="gl-text-gray-500 gl-mt-1 gl-min-h-6">
|
||||
<slot name="left-secondary"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-font-sm gl-text-gray-300"
|
||||
class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500"
|
||||
>
|
||||
<div>
|
||||
<slot name="left-secondary"></slot>
|
||||
<div
|
||||
v-if="$slots['right-primary']"
|
||||
class="gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
|
||||
>
|
||||
<slot name="right-primary"></slot>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="$slots['right-secondary']" class="gl-mt-1 gl-min-h-6">
|
||||
<slot name="right-secondary"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="$slots['right-action']"
|
||||
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-2"
|
||||
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-1"
|
||||
>
|
||||
<slot name="right-action"></slot>
|
||||
</div>
|
|
@ -120,6 +120,43 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.gl-shadow-x0-y0-b3-s1-blue-500 {
|
||||
box-shadow: inset 0 0 3px $gl-border-size-1 $blue-500;
|
||||
}
|
||||
|
||||
// remove when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1692 is merged
|
||||
.gl-border-t-transparent {
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.gl-align-items-flex-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.gl-sm-align-items-flex-end {
|
||||
@media (min-width: $breakpoint-sm) {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-sm-text-body {
|
||||
@media (min-width: $breakpoint-sm) {
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-sm-font-weight-bold {
|
||||
@media (min-width: $breakpoint-sm) {
|
||||
font-weight: $gl-font-weight-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.gl-align-items-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.gl-min-h-6 {
|
||||
min-height: $gl-spacing-scale-6;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
|
||||
before_action :check_issues_available!
|
||||
before_action :issue, unless: ->(c) { c.issue_except_actions.include?(c.action_name.to_sym) }
|
||||
after_action :log_issue_show, unless: ->(c) { c.issue_except_actions.include?(c.action_name.to_sym) }
|
||||
|
||||
before_action :set_issuables_index, if: ->(c) { c.set_issuables_index_only_actions.include?(c.action_name.to_sym) }
|
||||
|
||||
|
@ -249,6 +250,13 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
@issue
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def log_issue_show
|
||||
return unless current_user && @issue
|
||||
|
||||
::Gitlab::Search::RecentIssues.new(user: current_user).log_view(@issue)
|
||||
end
|
||||
|
||||
alias_method :subscribable_resource, :issue
|
||||
alias_method :issuable, :issue
|
||||
alias_method :awardable, :issue
|
||||
|
|
|
@ -7,6 +7,7 @@ module SearchHelper
|
|||
return unless current_user
|
||||
|
||||
resources_results = [
|
||||
recent_issues_autocomplete(term),
|
||||
groups_autocomplete(term),
|
||||
projects_autocomplete(term)
|
||||
].flatten
|
||||
|
@ -178,6 +179,20 @@ module SearchHelper
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
def recent_issues_autocomplete(term, limit = 5)
|
||||
return [] unless current_user
|
||||
|
||||
::Gitlab::Search::RecentIssues.new(user: current_user).search(term).limit(limit).map do |i|
|
||||
{
|
||||
category: "Recent issues",
|
||||
id: i.id,
|
||||
label: search_result_sanitize(i.title),
|
||||
url: issue_path(i),
|
||||
avatar_url: i.project.avatar_url || ''
|
||||
}
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def search_result_sanitize(str)
|
||||
|
|
|
@ -33,8 +33,8 @@ module BlobViewer
|
|||
@json_data ||= begin
|
||||
prepare!
|
||||
Gitlab::Json.parse(blob.data)
|
||||
rescue
|
||||
{}
|
||||
rescue
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -880,7 +880,9 @@ module Ci
|
|||
end
|
||||
|
||||
def test_report_summary
|
||||
Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
|
||||
strong_memoize(:test_report_summary) do
|
||||
Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
|
||||
end
|
||||
end
|
||||
|
||||
def test_reports
|
||||
|
|
|
@ -22,7 +22,7 @@ module BulkMemberAccessLoad
|
|||
end
|
||||
|
||||
# Look up only the IDs we need
|
||||
resource_ids = resource_ids - access.keys
|
||||
resource_ids -= access.keys
|
||||
|
||||
return access if resource_ids.empty?
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module FromExcept
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
# Produces a query that uses a FROM to select data using an EXCEPT.
|
||||
#
|
||||
# Example:
|
||||
# groups = Group.from_except([group1.self_and_hierarchy, group2.self_and_hierarchy])
|
||||
#
|
||||
# This would produce the following SQL query:
|
||||
#
|
||||
# SELECT *
|
||||
# FROM (
|
||||
# SELECT "namespaces". *
|
||||
# ...
|
||||
#
|
||||
# EXCEPT
|
||||
#
|
||||
# SELECT "namespaces". *
|
||||
# ...
|
||||
# ) groups;
|
||||
#
|
||||
# members - An Array of ActiveRecord::Relation objects to use in the except.
|
||||
#
|
||||
# remove_duplicates - A boolean indicating if duplicate entries should be
|
||||
# removed. Defaults to true.
|
||||
#
|
||||
# alias_as - The alias to use for the sub query. Defaults to the name of the
|
||||
# table of the current model.
|
||||
# rubocop: disable Gitlab/Except
|
||||
extend FromSetOperator
|
||||
define_set_operator Gitlab::SQL::Except
|
||||
# rubocop: enable Gitlab/Except
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module FromIntersect
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
# Produces a query that uses a FROM to select data using an INTERSECT.
|
||||
#
|
||||
# Example:
|
||||
# groups = Group.from_intersect([group1.self_and_hierarchy, group2.self_and_hierarchy])
|
||||
#
|
||||
# This would produce the following SQL query:
|
||||
#
|
||||
# SELECT *
|
||||
# FROM (
|
||||
# SELECT "namespaces". *
|
||||
# ...
|
||||
#
|
||||
# INTERSECT
|
||||
#
|
||||
# SELECT "namespaces". *
|
||||
# ...
|
||||
# ) groups;
|
||||
#
|
||||
# members - An Array of ActiveRecord::Relation objects to use in the intersect.
|
||||
#
|
||||
# remove_duplicates - A boolean indicating if duplicate entries should be
|
||||
# removed. Defaults to true.
|
||||
#
|
||||
# alias_as - The alias to use for the sub query. Defaults to the name of the
|
||||
# table of the current model.
|
||||
# rubocop: disable Gitlab/Intersect
|
||||
extend FromSetOperator
|
||||
define_set_operator Gitlab::SQL::Intersect
|
||||
# rubocop: enable Gitlab/Intersect
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module FromSetOperator
|
||||
# Define a high level method to more easily work with the SQL set operations
|
||||
# of UNION, INTERSECT, and EXCEPT as defined by Gitlab::SQL::Union,
|
||||
# Gitlab::SQL::Intersect, and Gitlab::SQL::Except respectively.
|
||||
def define_set_operator(operator)
|
||||
method_name = 'from_' + operator.name.demodulize.downcase
|
||||
method_name = method_name.to_sym
|
||||
|
||||
raise "Trying to redefine method '#{method(method_name)}'" if methods.include?(method_name)
|
||||
|
||||
define_method(method_name) do |members, remove_duplicates: true, alias_as: table_name|
|
||||
operator_sql = operator.new(members, remove_duplicates: remove_duplicates).to_sql
|
||||
|
||||
from(Arel.sql("(#{operator_sql}) #{alias_as}"))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -35,13 +35,29 @@ module FromUnion
|
|||
# alias_as - The alias to use for the sub query. Defaults to the name of the
|
||||
# table of the current model.
|
||||
# rubocop: disable Gitlab/Union
|
||||
extend FromSetOperator
|
||||
define_set_operator Gitlab::SQL::Union
|
||||
|
||||
alias_method :from_union_set_operator, :from_union
|
||||
def from_union(members, remove_duplicates: true, alias_as: table_name)
|
||||
if Feature.enabled?(:sql_set_operators)
|
||||
from_union_set_operator(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
|
||||
else
|
||||
# The original from_union method.
|
||||
standard_from_union(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def standard_from_union(members, remove_duplicates: true, alias_as: table_name)
|
||||
union = Gitlab::SQL::Union
|
||||
.new(members, remove_duplicates: remove_duplicates)
|
||||
.to_sql
|
||||
|
||||
from(Arel.sql("(#{union}) #{alias_as}"))
|
||||
end
|
||||
|
||||
# rubocop: enable Gitlab/Union
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module IdInOrdered
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :id_in_ordered, -> (ids) do
|
||||
raise ArgumentError, "ids must be an array of integers" unless ids.is_a?(Enumerable) && ids.all? { |id| id.is_a?(Integer) }
|
||||
|
||||
# No need to sort if no more than 1 and the sorting code doesn't work
|
||||
# with an empty array
|
||||
return id_in(ids) unless ids.count > 1
|
||||
|
||||
id_attribute = arel_table[:id]
|
||||
id_in(ids)
|
||||
.order(
|
||||
Arel.sql("array_position(ARRAY[#{ids.join(',')}], #{id_attribute.relation.name}.#{id_attribute.name})"))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,6 +18,7 @@ class Issue < ApplicationRecord
|
|||
include MilestoneEventable
|
||||
include WhereComposite
|
||||
include StateEventable
|
||||
include IdInOrdered
|
||||
|
||||
DueDateStruct = Struct.new(:title, :name).freeze
|
||||
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
||||
|
|
|
@ -2152,8 +2152,8 @@ class Project < ApplicationRecord
|
|||
data = repository.route_map_for(sha)
|
||||
|
||||
Gitlab::RouteMap.new(data) if data
|
||||
rescue Gitlab::RouteMap::FormatError
|
||||
nil
|
||||
rescue Gitlab::RouteMap::FormatError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class BaseSerializer
|
|||
end
|
||||
|
||||
def represent(resource, opts = {}, entity_class = nil)
|
||||
entity_class = entity_class || self.class.entity_class
|
||||
entity_class ||= self.class.entity_class
|
||||
|
||||
entity_class
|
||||
.represent(resource, opts.merge(request: @request))
|
||||
|
|
|
@ -85,10 +85,6 @@ class PipelineEntity < Grape::Entity
|
|||
pipeline.failed_builds
|
||||
end
|
||||
|
||||
expose :tests_total_count do |pipeline|
|
||||
pipeline.test_report_summary.total[:count]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
alias_method :pipeline, :object
|
||||
|
|
|
@ -23,11 +23,15 @@ class EventCreateService
|
|||
end
|
||||
|
||||
def open_mr(merge_request, current_user)
|
||||
create_record_event(merge_request, current_user, :created)
|
||||
create_record_event(merge_request, current_user, :created).tap do
|
||||
track_event(event_action: :created, event_target: MergeRequest, author_id: current_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
def close_mr(merge_request, current_user)
|
||||
create_record_event(merge_request, current_user, :closed)
|
||||
create_record_event(merge_request, current_user, :closed).tap do
|
||||
track_event(event_action: :closed, event_target: MergeRequest, author_id: current_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
def reopen_mr(merge_request, current_user)
|
||||
|
@ -35,7 +39,9 @@ class EventCreateService
|
|||
end
|
||||
|
||||
def merge_mr(merge_request, current_user)
|
||||
create_record_event(merge_request, current_user, :merged)
|
||||
create_record_event(merge_request, current_user, :merged).tap do
|
||||
track_event(event_action: :merged, event_target: MergeRequest, author_id: current_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
def open_milestone(milestone, current_user)
|
||||
|
@ -55,7 +61,11 @@ class EventCreateService
|
|||
end
|
||||
|
||||
def leave_note(note, current_user)
|
||||
create_record_event(note, current_user, :commented)
|
||||
create_record_event(note, current_user, :commented).tap do
|
||||
if note.is_a?(DiffNote) && note.for_merge_request?
|
||||
track_event(event_action: :commented, event_target: MergeRequest, author_id: current_user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def join_project(project, current_user)
|
||||
|
@ -109,7 +119,7 @@ class EventCreateService
|
|||
def wiki_event(wiki_page_meta, author, action, fingerprint)
|
||||
raise IllegalActionError, action unless Event::WIKI_ACTIONS.include?(action)
|
||||
|
||||
Gitlab::UsageDataCounters::TrackUniqueEvents.track_event(event_action: action, event_target: wiki_page_meta.class, author_id: author.id)
|
||||
track_event(event_action: action, event_target: wiki_page_meta.class, author_id: author.id)
|
||||
|
||||
duplicate = Event.for_wiki_meta(wiki_page_meta).for_fingerprint(fingerprint).first
|
||||
return duplicate if duplicate.present?
|
||||
|
@ -154,7 +164,7 @@ class EventCreateService
|
|||
result = Event.insert_all(attribute_sets, returning: %w[id])
|
||||
|
||||
tuples.each do |record, status, _|
|
||||
Gitlab::UsageDataCounters::TrackUniqueEvents.track_event(event_action: status, event_target: record.class, author_id: current_user.id)
|
||||
track_event(event_action: status, event_target: record.class, author_id: current_user.id)
|
||||
end
|
||||
|
||||
result
|
||||
|
@ -172,7 +182,7 @@ class EventCreateService
|
|||
new_event
|
||||
end
|
||||
|
||||
Gitlab::UsageDataCounters::TrackUniqueEvents.track_event(event_action: :pushed, event_target: Project, author_id: current_user.id)
|
||||
track_event(event_action: :pushed, event_target: Project, author_id: current_user.id)
|
||||
|
||||
Users::LastPushEventService.new(current_user)
|
||||
.cache_last_push_event(event)
|
||||
|
@ -206,6 +216,10 @@ class EventCreateService
|
|||
|
||||
{ resource_parent_attr => resource_parent.id }
|
||||
end
|
||||
|
||||
def track_event(**params)
|
||||
Gitlab::UsageDataCounters::TrackUniqueEvents.track_event(**params)
|
||||
end
|
||||
end
|
||||
|
||||
EventCreateService.prepend_if_ee('EE::EventCreateService')
|
||||
|
|
|
@ -44,8 +44,8 @@ module Prometheus
|
|||
def self.from_cache(proxyable_class_name, proxyable_id, method, path, params)
|
||||
proxyable_class = begin
|
||||
proxyable_class_name.constantize
|
||||
rescue NameError
|
||||
nil
|
||||
rescue NameError
|
||||
nil
|
||||
end
|
||||
return unless proxyable_class
|
||||
|
||||
|
|
|
@ -64,7 +64,8 @@
|
|||
-# haml-lint:disable InlineJavaScript
|
||||
%script#js-issuable-app-initial-data{ type: "application/json" }= issuable_initial_data(@issue).to_json
|
||||
#js-issuable-app
|
||||
%h2.title= markdown_field(@issue, :title)
|
||||
.title-container
|
||||
%h2.title= markdown_field(@issue, :title)
|
||||
- if @issue.description.present?
|
||||
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
|
||||
.md= markdown_field(@issue, :description)
|
||||
|
|
|
@ -9,8 +9,8 @@ class DeleteStoredFilesWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
def perform(class_name, keys)
|
||||
klass = begin
|
||||
class_name.constantize
|
||||
rescue NameError
|
||||
nil
|
||||
rescue NameError
|
||||
nil
|
||||
end
|
||||
|
||||
unless klass
|
||||
|
|
|
@ -9,5 +9,7 @@ class PartitionCreationWorker
|
|||
|
||||
def perform
|
||||
Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions
|
||||
ensure
|
||||
Gitlab::Database::Partitioning::PartitionMonitoring.new.report_metrics
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds creator_id field to packages_packages table
|
||||
merge_request: 40562
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add merge request usage to usage data
|
||||
merge_request: 40391
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Display informative messages when service desk is unsupported
|
||||
merge_request: 40454
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Layout/ClosingParenthesisIndentation cop
|
||||
merge_request: 41084
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Layout/FirstParameterIndentation cop
|
||||
merge_request: 41089
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Lint/RaiseException cop
|
||||
merge_request: 41099
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Layout/RescueEnsureAlignment cop
|
||||
merge_request: 41093
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Style/SelfAssignment cop
|
||||
merge_request: 41079
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Layout/SpaceBeforeFirstArg cop
|
||||
merge_request: 41097
|
||||
author: Rajendra Kadam
|
||||
type: fixed
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: recent_items_search
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40669
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/244277
|
||||
group: group::global search
|
||||
type: development
|
||||
default_enabled: false
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: sql-set-operators
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39786
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39786
|
||||
group: group::access
|
||||
type: development
|
||||
default_enabled: false
|
|
@ -176,8 +176,8 @@ Settings.gitlab['user'] ||= 'git'
|
|||
Settings.gitlab['ssh_user'] ||= Settings.gitlab.user
|
||||
Settings.gitlab['user_home'] ||= begin
|
||||
Etc.getpwnam(Settings.gitlab['user']).dir
|
||||
rescue ArgumentError # no user configured
|
||||
'/home/' + Settings.gitlab['user']
|
||||
rescue ArgumentError # no user configured
|
||||
'/home/' + Settings.gitlab['user']
|
||||
end
|
||||
Settings.gitlab['time_zone'] ||= nil
|
||||
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
|
||||
|
|
|
@ -15,7 +15,7 @@ end
|
|||
|
||||
gitlab_trusted_proxies = Array(Gitlab.config.gitlab.trusted_proxies).map do |proxy|
|
||||
IPAddr.new(proxy)
|
||||
rescue IPAddr::InvalidAddressError
|
||||
rescue IPAddr::InvalidAddressError
|
||||
end.compact
|
||||
|
||||
Rails.application.config.action_dispatch.trusted_proxies = (
|
||||
|
|
|
@ -23,9 +23,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
|
|||
get 'archived', action: :show, as: :group_archived # rubocop:disable Cop/PutGroupRoutesUnderScope
|
||||
end
|
||||
|
||||
# These routes are legit and the cop rule will be improved in
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/230703
|
||||
get '/', action: :show, as: :group_canonical # rubocop:disable Cop/PutGroupRoutesUnderScope
|
||||
get '/', action: :show, as: :group_canonical
|
||||
end
|
||||
|
||||
scope(path: 'groups/*group_id/-',
|
||||
|
@ -112,11 +110,9 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
|
|||
as: :group,
|
||||
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ },
|
||||
controller: :groups) do
|
||||
# These routes are legit and the cop rule will be improved in
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/230703
|
||||
get '/', action: :show # rubocop:disable Cop/PutGroupRoutesUnderScope
|
||||
patch '/', action: :update # rubocop:disable Cop/PutGroupRoutesUnderScope
|
||||
put '/', action: :update # rubocop:disable Cop/PutGroupRoutesUnderScope
|
||||
delete '/', action: :destroy # rubocop:disable Cop/PutGroupRoutesUnderScope
|
||||
get '/', action: :show
|
||||
patch '/', action: :update
|
||||
put '/', action: :update
|
||||
delete '/', action: :destroy
|
||||
end
|
||||
end
|
||||
|
|
|
@ -914,7 +914,7 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0]
|
|||
MSG
|
||||
end
|
||||
|
||||
raise Exception.new(message)
|
||||
raise StandardError.new(message)
|
||||
end
|
||||
|
||||
def create_missing_tables
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCreatorIdToPackages < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column(:packages_packages, :creator_id, :integer)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexToPackageCreator < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_packages_packages_on_creator_id'
|
||||
|
||||
def up
|
||||
add_concurrent_index :packages_packages, :creator_id, name: INDEX_NAME
|
||||
add_concurrent_foreign_key(:packages_packages, :users, column: :creator_id, on_delete: :nullify)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_foreign_key_if_exists(:packages_packages, :users, column: :creator_id)
|
||||
remove_concurrent_index_by_name(:packages_packages, INDEX_NAME)
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
f4f1efcc93476a1d70add93e166f4c702ad7dfc97ad29c3455722fd98824498f
|
|
@ -0,0 +1 @@
|
|||
1e8dd4542b13009b748d352933a4a59fcabb31e916226fcbf87043396f94e09f
|
|
@ -14025,7 +14025,8 @@ CREATE TABLE public.packages_packages (
|
|||
updated_at timestamp with time zone NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
version character varying,
|
||||
package_type smallint NOT NULL
|
||||
package_type smallint NOT NULL,
|
||||
creator_id integer
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.packages_packages_id_seq
|
||||
|
@ -20457,6 +20458,8 @@ CREATE INDEX index_packages_package_files_on_file_store ON public.packages_packa
|
|||
|
||||
CREATE INDEX index_packages_package_files_on_package_id_and_file_name ON public.packages_package_files USING btree (package_id, file_name);
|
||||
|
||||
CREATE INDEX index_packages_packages_on_creator_id ON public.packages_packages USING btree (creator_id);
|
||||
|
||||
CREATE INDEX index_packages_packages_on_name_trigram ON public.packages_packages USING gin (name public.gin_trgm_ops);
|
||||
|
||||
CREATE INDEX index_packages_packages_on_project_id_and_created_at ON public.packages_packages USING btree (project_id, created_at);
|
||||
|
@ -22039,6 +22042,9 @@ ALTER TABLE ONLY public.ci_builds
|
|||
ALTER TABLE ONLY public.design_management_versions
|
||||
ADD CONSTRAINT fk_c1440b4896 FOREIGN KEY (author_id) REFERENCES public.users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY public.packages_packages
|
||||
ADD CONSTRAINT fk_c188f0dba4 FOREIGN KEY (creator_id) REFERENCES public.users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY public.geo_event_log
|
||||
ADD CONSTRAINT fk_c1f241c70d FOREIGN KEY (upload_deleted_event_id) REFERENCES public.geo_upload_deleted_events(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -194,6 +194,15 @@ The following metrics are available:
|
|||
|:--------------------------------- |:--------- |:------------------------------------------------------------- |:-------------------------------------- |
|
||||
| `db_load_balancing_hosts` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/13630) | Current number of load balancing hosts |
|
||||
|
||||
## Database partitioning metrics **(PREMIUM ONLY)**
|
||||
|
||||
The following metrics are available:
|
||||
|
||||
| Metric | Type | Since | Description |
|
||||
|:--------------------------------- |:--------- |:------------------------------------------------------------- |:----------------------------------------------------------------- |
|
||||
| `db_partitions_present` | Gauge | [13.4](https://gitlab.com/gitlab-org/gitlab/-/issues/227353) | Number of database partitions present |
|
||||
| `db_partitions_missing` | Gauge | [13.4](https://gitlab.com/gitlab-org/gitlab/-/issues/227353) | Number of database partitions currently expected, but not present |
|
||||
|
||||
## Connection pool metrics
|
||||
|
||||
These metrics record the status of the database
|
||||
|
|
|
@ -1438,7 +1438,7 @@ On each node:
|
|||
gitlab_workhorse['enable'] = false
|
||||
grafana['enable'] = false
|
||||
|
||||
# If you run a seperate monitoring node you can disable these services
|
||||
# If you run a separate monitoring node you can disable these services
|
||||
alertmanager['enable'] = false
|
||||
prometheus['enable'] = false
|
||||
|
||||
|
|
|
@ -1438,7 +1438,7 @@ On each node:
|
|||
gitlab_workhorse['enable'] = false
|
||||
grafana['enable'] = false
|
||||
|
||||
# If you run a seperate monitoring node you can disable these services
|
||||
# If you run a separate monitoring node you can disable these services
|
||||
alertmanager['enable'] = false
|
||||
prometheus['enable'] = false
|
||||
|
||||
|
|
|
@ -419,7 +419,7 @@ To configure the Gitaly server:
|
|||
gitlab_workhorse['enable'] = false
|
||||
grafana['enable'] = false
|
||||
|
||||
# If you run a seperate monitoring node you can disable these services
|
||||
# If you run a separate monitoring node you can disable these services
|
||||
alertmanager['enable'] = false
|
||||
prometheus['enable'] = false
|
||||
|
||||
|
|
|
@ -1145,7 +1145,7 @@ On each node:
|
|||
grafana['enable'] = false
|
||||
gitlab_exporter['enable'] = false
|
||||
|
||||
# If you run a seperate monitoring node you can disable these services
|
||||
# If you run a separate monitoring node you can disable these services
|
||||
alertmanager['enable'] = false
|
||||
prometheus['enable'] = false
|
||||
|
||||
|
|
|
@ -1438,7 +1438,7 @@ On each node:
|
|||
gitlab_workhorse['enable'] = false
|
||||
grafana['enable'] = false
|
||||
|
||||
# If you run a seperate monitoring node you can disable these services
|
||||
# If you run a separate monitoring node you can disable these services
|
||||
alertmanager['enable'] = false
|
||||
prometheus['enable'] = false
|
||||
|
||||
|
|
|
@ -1144,7 +1144,7 @@ On each node:
|
|||
grafana['enable'] = false
|
||||
gitlab_exporter['enable'] = false
|
||||
|
||||
# If you run a seperate monitoring node you can disable these services
|
||||
# If you run a separate monitoring node you can disable these services
|
||||
alertmanager['enable'] = false
|
||||
prometheus['enable'] = false
|
||||
|
||||
|
|
|
@ -17410,6 +17410,11 @@ type Vulnerability {
|
|||
"""
|
||||
description: String
|
||||
|
||||
"""
|
||||
Timestamp of when the vulnerability was first detected
|
||||
"""
|
||||
detectedAt: Time!
|
||||
|
||||
"""
|
||||
GraphQL ID of the vulnerability
|
||||
"""
|
||||
|
|
|
@ -51106,6 +51106,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "detectedAt",
|
||||
"description": "Timestamp of when the vulnerability was first detected",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "GraphQL ID of the vulnerability",
|
||||
|
|
|
@ -2580,6 +2580,7 @@ Represents a vulnerability.
|
|||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `description` | String | Description of the vulnerability |
|
||||
| `detectedAt` | Time! | Timestamp of when the vulnerability was first detected |
|
||||
| `id` | ID! | GraphQL ID of the vulnerability |
|
||||
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
|
||||
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability |
|
||||
|
|
|
@ -18,7 +18,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def add_check(
|
||||
klass,
|
||||
klass,
|
||||
actions: {},
|
||||
positive_condition: nil,
|
||||
negative_condition: nil,
|
||||
|
|
|
@ -86,7 +86,7 @@ module Gitlab
|
|||
#
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def build(
|
||||
project:, user:, ref:, oldrev: nil, newrev: nil,
|
||||
project:, user:, ref:, oldrev: nil, newrev: nil,
|
||||
commits: [], commits_count: nil, message: nil, push_options: {},
|
||||
with_changed_files: true)
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
module Partitioning
|
||||
class PartitionMonitoring
|
||||
attr_reader :models
|
||||
|
||||
def initialize(models = PartitionCreator.models)
|
||||
@models = models
|
||||
end
|
||||
|
||||
def report_metrics
|
||||
models.each do |model|
|
||||
strategy = model.partitioning_strategy
|
||||
|
||||
gauge_present.set({ table: model.table_name }, strategy.current_partitions.size)
|
||||
gauge_missing.set({ table: model.table_name }, strategy.missing_partitions.size)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gauge_present
|
||||
@gauge_present ||= Gitlab::Metrics.gauge(:db_partitions_present, 'Number of database partitions present')
|
||||
end
|
||||
|
||||
def gauge_missing
|
||||
@gauge_missing ||= Gitlab::Metrics.gauge(:db_partitions_missing, 'Number of database partitions currently expected, but not present')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,8 +31,8 @@ module Gitlab
|
|||
def lexer
|
||||
@lexer ||= custom_language || begin
|
||||
Rouge::Lexer.guess(filename: @blob_name, source: @blob_content).new
|
||||
rescue Rouge::Guesser::Ambiguous => e
|
||||
e.alternatives.min_by(&:tag)
|
||||
rescue Rouge::Guesser::Ambiguous => e
|
||||
e.alternatives.min_by(&:tag)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Search
|
||||
class RecentIssues
|
||||
ITEMS_LIMIT = 100
|
||||
EXPIRES_AFTER = 7.days
|
||||
|
||||
def initialize(user:, items_limit: ITEMS_LIMIT, expires_after: EXPIRES_AFTER)
|
||||
@user = user
|
||||
@items_limit = items_limit
|
||||
@expires_after = expires_after
|
||||
end
|
||||
|
||||
def log_view(issue)
|
||||
return unless recent_items_enabled?
|
||||
|
||||
with_redis do |redis|
|
||||
redis.zadd(key, Time.now.to_f, issue.id)
|
||||
redis.expire(key, @expires_after)
|
||||
|
||||
# There is a race condition here where we could end up removing an
|
||||
# item from 2 places concurrently but this is fine since worst case
|
||||
# scenario we remove an extra item from the end of the list.
|
||||
if redis.zcard(key) > @items_limit
|
||||
redis.zremrangebyrank(key, 0, 0) # Remove least recent
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def search(term)
|
||||
return Issue.none unless recent_items_enabled?
|
||||
|
||||
ids = with_redis do |redis|
|
||||
redis.zrevrange(key, 0, @items_limit - 1)
|
||||
end.map(&:to_i)
|
||||
|
||||
IssuesFinder.new(@user, search: term, in: 'title').execute.reorder(nil).id_in_ordered(ids) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_redis(&blk)
|
||||
Gitlab::Redis::SharedState.with(&blk) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def key
|
||||
"recent_items:#{type.name.downcase}:#{@user.id}"
|
||||
end
|
||||
|
||||
def type
|
||||
Issue
|
||||
end
|
||||
|
||||
def recent_items_enabled?
|
||||
Feature.enabled?(:recent_items_search, @user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SQL
|
||||
# Class for building SQL EXCEPT statements.
|
||||
#
|
||||
# ORDER BYs are dropped from the relations as the final sort order is not
|
||||
# guaranteed any way.
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# except = Gitlab::SQL::Except.new([user.projects, user.personal_projects])
|
||||
# sql = except.to_sql
|
||||
#
|
||||
# Project.where("id IN (#{sql})")
|
||||
class Except < SetOperator
|
||||
def self.operator_keyword
|
||||
'EXCEPT'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SQL
|
||||
# Class for building SQL INTERSECT statements.
|
||||
#
|
||||
# ORDER BYs are dropped from the relations as the final sort order is not
|
||||
# guaranteed any way.
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# hierarchies = [group1.self_and_hierarchy, group2.self_and_hierarchy]
|
||||
# intersect = Gitlab::SQL::Intersect.new(hierarchies)
|
||||
# sql = intersect.to_sql
|
||||
#
|
||||
# Project.where("id IN (#{sql})")
|
||||
class Intersect < SetOperator
|
||||
def self.operator_keyword
|
||||
'INTERSECT'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SQL
|
||||
# Class for building SQL set operator statements (UNION, INTERSECT, and
|
||||
# EXCEPT).
|
||||
#
|
||||
# ORDER BYs are dropped from the relations as the final sort order is not
|
||||
# guaranteed any way.
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# union = Gitlab::SQL::Union.new([user.personal_projects, user.projects])
|
||||
# sql = union.to_sql
|
||||
#
|
||||
# Project.where("id IN (#{sql})")
|
||||
class SetOperator
|
||||
def initialize(relations, remove_duplicates: true)
|
||||
@relations = relations
|
||||
@remove_duplicates = remove_duplicates
|
||||
end
|
||||
|
||||
def self.operator_keyword
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def to_sql
|
||||
# Some relations may include placeholders for prepared statements, these
|
||||
# aren't incremented properly when joining relations together this way.
|
||||
# By using "unprepared_statements" we remove the usage of placeholders
|
||||
# (thus fixing this problem), at a slight performance cost.
|
||||
fragments = ActiveRecord::Base.connection.unprepared_statement do
|
||||
relations.map { |rel| rel.reorder(nil).to_sql }.reject(&:blank?)
|
||||
end
|
||||
|
||||
if fragments.any?
|
||||
"(" + fragments.join(")\n#{operator_keyword_fragment}\n(") + ")"
|
||||
else
|
||||
'NULL'
|
||||
end
|
||||
end
|
||||
|
||||
# UNION [ALL] | INTERSECT [ALL] | EXCEPT [ALL]
|
||||
def operator_keyword_fragment
|
||||
remove_duplicates ? self.class.operator_keyword : "#{self.class.operator_keyword} ALL"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :relations, :remove_duplicates
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,30 +13,9 @@ module Gitlab
|
|||
# sql = union.to_sql
|
||||
#
|
||||
# Project.where("id IN (#{sql})")
|
||||
class Union
|
||||
def initialize(relations, remove_duplicates: true)
|
||||
@relations = relations
|
||||
@remove_duplicates = remove_duplicates
|
||||
end
|
||||
|
||||
def to_sql
|
||||
# Some relations may include placeholders for prepared statements, these
|
||||
# aren't incremented properly when joining relations together this way.
|
||||
# By using "unprepared_statements" we remove the usage of placeholders
|
||||
# (thus fixing this problem), at a slight performance cost.
|
||||
fragments = ActiveRecord::Base.connection.unprepared_statement do
|
||||
@relations.map { |rel| rel.reorder(nil).to_sql }.reject(&:blank?)
|
||||
end
|
||||
|
||||
if fragments.any?
|
||||
"(" + fragments.join(")\n#{union_keyword}\n(") + ")"
|
||||
else
|
||||
'NULL'
|
||||
end
|
||||
end
|
||||
|
||||
def union_keyword
|
||||
@remove_duplicates ? 'UNION' : 'UNION ALL'
|
||||
class Union < SetOperator
|
||||
def self.operator_keyword
|
||||
'UNION'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -426,16 +426,17 @@ module Gitlab
|
|||
{} # augmented in EE
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def merge_requests_users(time_period)
|
||||
distinct_count(
|
||||
Event.where(target_type: Event::TARGET_TYPES[:merge_request].to_s).where(time_period),
|
||||
:author_id,
|
||||
start: user_minimum_id,
|
||||
finish: user_maximum_id
|
||||
)
|
||||
counter = Gitlab::UsageDataCounters::TrackUniqueEvents
|
||||
|
||||
redis_usage_data do
|
||||
counter.count_unique_events(
|
||||
event_action: Gitlab::UsageDataCounters::TrackUniqueEvents::MERGE_REQUEST_ACTION,
|
||||
date_from: time_period[:created_at].first,
|
||||
date_to: time_period[:created_at].last
|
||||
)
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def installation_type
|
||||
if Rails.env.production?
|
||||
|
|
|
@ -6,6 +6,7 @@ module Gitlab
|
|||
WIKI_ACTION = :wiki_action
|
||||
DESIGN_ACTION = :design_action
|
||||
PUSH_ACTION = :project_action
|
||||
MERGE_REQUEST_ACTION = :merge_request_action
|
||||
|
||||
ACTION_TRANSFORMATIONS = HashWithIndifferentAccess.new({
|
||||
wiki: {
|
||||
|
@ -20,6 +21,12 @@ module Gitlab
|
|||
},
|
||||
project: {
|
||||
pushed: PUSH_ACTION
|
||||
},
|
||||
merge_request: {
|
||||
closed: MERGE_REQUEST_ACTION,
|
||||
merged: MERGE_REQUEST_ACTION,
|
||||
created: MERGE_REQUEST_ACTION,
|
||||
commented: MERGE_REQUEST_ACTION
|
||||
}
|
||||
}).freeze
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@ namespace :gitlab do
|
|||
|
||||
LfsObject.with_files_stored_locally
|
||||
.find_each(batch_size: 10) do |lfs_object|
|
||||
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
|
||||
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
|
||||
|
||||
logger.info("Transferred LFS object #{lfs_object.oid} of size #{lfs_object.size.to_i.bytes} to object storage")
|
||||
rescue => e
|
||||
logger.error("Failed to transfer LFS object #{lfs_object.oid} with error: #{e.message}")
|
||||
end
|
||||
logger.info("Transferred LFS object #{lfs_object.oid} of size #{lfs_object.size.to_i.bytes} to object storage")
|
||||
rescue => e
|
||||
logger.error("Failed to transfer LFS object #{lfs_object.oid} with error: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
task migrate_to_local: :environment do
|
||||
|
|
|
@ -10972,6 +10972,9 @@ msgstr ""
|
|||
msgid "Footer message"
|
||||
msgstr ""
|
||||
|
||||
msgid "For help setting up the Service Desk for your instance, please contact an administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13046,6 +13049,9 @@ msgstr ""
|
|||
msgid "In %{time_to_now}"
|
||||
msgstr ""
|
||||
|
||||
msgid "In order to enable Service Desk for your instance, you must first set up incoming email."
|
||||
msgstr ""
|
||||
|
||||
msgid "In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index."
|
||||
msgstr ""
|
||||
|
||||
|
@ -22392,6 +22398,12 @@ msgstr ""
|
|||
msgid "Service Desk is enabled but not yet active"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service Desk is not enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service Desk is not supported"
|
||||
msgstr ""
|
||||
|
||||
msgid "Service Templates"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.161.0",
|
||||
"@gitlab/ui": "20.13.0",
|
||||
"@gitlab/ui": "20.16.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-1",
|
||||
"@sentry/browser": "^5.10.2",
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
# Cop that disallows the use of `Gitlab::SQL::Except`, in favour of using
|
||||
# the `FromExcept` module.
|
||||
class Except < RuboCop::Cop::Cop
|
||||
MSG = 'Use the `FromExcept` concern, instead of using `Gitlab::SQL::Except` directly'
|
||||
|
||||
def_node_matcher :raw_except?, <<~PATTERN
|
||||
(send (const (const (const nil? :Gitlab) :SQL) :Except) :new ...)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless raw_except?(node)
|
||||
|
||||
add_offense(node, location: :expression)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
# Cop that disallows the use of `Gitlab::SQL::Intersect`, in favour of using
|
||||
# the `FromIntersect` module.
|
||||
class Intersect < RuboCop::Cop::Cop
|
||||
MSG = 'Use the `FromIntersect` concern, instead of using `Gitlab::SQL::Intersect` directly'
|
||||
|
||||
def_node_matcher :raw_intersect?, <<~PATTERN
|
||||
(send (const (const (const nil? :Gitlab) :SQL) :Intersect) :new ...)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless raw_intersect?(node)
|
||||
|
||||
add_offense(node, location: :expression)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,6 +12,7 @@ module RuboCop
|
|||
def on_send(node)
|
||||
return unless route_method?(node)
|
||||
return unless outside_scope?(node)
|
||||
return if root_route?(node)
|
||||
|
||||
add_offense(node)
|
||||
end
|
||||
|
@ -25,5 +26,13 @@ module RuboCop
|
|||
def route_method?(node)
|
||||
ROUTE_METHODS.include?(node.method_name)
|
||||
end
|
||||
|
||||
def root_route?(node)
|
||||
first_argument = node.arguments.first
|
||||
|
||||
if first_argument.respond_to?(:value)
|
||||
first_argument.value == '/'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,18 @@ then
|
|||
((ERRORCODE++))
|
||||
fi
|
||||
|
||||
MD_DOC_PATH=${MD_DOC_PATH:-doc}
|
||||
# Run Vale and Markdownlint only on changed files. Only works on merged results
|
||||
# pipelines, so first checks if a merged results CI variable is present. If not present,
|
||||
# runs test on all files.
|
||||
if [ -z "${CI_MERGE_REQUEST_TARGET_BRANCH_SHA}" ]
|
||||
then
|
||||
MD_DOC_PATH=${MD_DOC_PATH:-doc}
|
||||
echo "Merge request pipeline (detached) detected. Testing all files."
|
||||
else
|
||||
MERGE_BASE=$(git merge-base ${CI_MERGE_REQUEST_TARGET_BRANCH_SHA} ${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA})
|
||||
MD_DOC_PATH=$(git diff --name-only "${MERGE_BASE}..${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}" '*.md')
|
||||
echo -e "Merged results pipeline detected. Testing only the following files:\n${MD_DOC_PATH}"
|
||||
fi
|
||||
|
||||
function run_locally_or_in_docker() {
|
||||
local cmd=$1
|
||||
|
|
|
@ -1011,6 +1011,24 @@ RSpec.describe Projects::IssuesController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'logs the view with Gitlab::Search::RecentIssues' do
|
||||
sign_in(user)
|
||||
recent_issues_double = instance_double(::Gitlab::Search::RecentIssues, log_view: nil)
|
||||
expect(::Gitlab::Search::RecentIssues).to receive(:new).with(user: user).and_return(recent_issues_double)
|
||||
|
||||
go(id: issue.to_param)
|
||||
|
||||
expect(recent_issues_double).to have_received(:log_view)
|
||||
end
|
||||
|
||||
context 'when not logged in' do
|
||||
it 'does not log the view with Gitlab::Search::RecentIssues' do
|
||||
expect(::Gitlab::Search::RecentIssues).not_to receive(:new)
|
||||
|
||||
go(id: issue.to_param)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #realtime_changes' do
|
||||
|
|
|
@ -43,7 +43,7 @@ RSpec.describe Projects::PipelinesController do
|
|||
end
|
||||
end
|
||||
|
||||
it 'executes N+1 queries' do
|
||||
it 'does not execute N+1 queries' do
|
||||
get_pipelines_index_json
|
||||
|
||||
control_count = ActiveRecord::QueryRecorder.new do
|
||||
|
@ -53,7 +53,7 @@ RSpec.describe Projects::PipelinesController do
|
|||
create_all_pipeline_types
|
||||
|
||||
# There appears to be one extra query for Pipelines#has_warnings? for some reason
|
||||
expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 7)
|
||||
expect { get_pipelines_index_json }.not_to exceed_query_limit(control_count + 1)
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['pipelines'].count).to eq 12
|
||||
end
|
||||
|
|
|
@ -51,8 +51,8 @@ RSpec.describe 'issuable list', :js do
|
|||
it "counts merge requests closing issues icons for each issue" do
|
||||
visit_issuable_list(:issue)
|
||||
|
||||
expect(page).to have_selector('.icon-merge-request-unmerged', count: 1)
|
||||
expect(first('.icon-merge-request-unmerged').find(:xpath, '..')).to have_content(1)
|
||||
expect(page).to have_selector('[data-testid="merge-requests"]', count: 1)
|
||||
expect(first('[data-testid="merge-requests"]').find(:xpath, '..')).to have_content(1)
|
||||
end
|
||||
|
||||
def visit_issuable_list(issuable_type)
|
||||
|
|
|
@ -7,6 +7,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
# The following two conditions equate to Gitlab::ServiceDesk.supported == true
|
||||
allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
|
||||
allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
|
||||
|
||||
|
@ -27,53 +28,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
|
|||
end
|
||||
|
||||
describe 'issues list' do
|
||||
context 'when service desk is misconfigured' do
|
||||
before do
|
||||
allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(false)
|
||||
visit service_desk_project_issues_path(project)
|
||||
end
|
||||
|
||||
it 'shows a message to say the configuration is incomplete' do
|
||||
expect(page).to have_css('.empty-state')
|
||||
expect(page).to have_text('Service Desk is enabled but not yet active')
|
||||
expect(page).to have_text('You must set up incoming email before it becomes active')
|
||||
expect(page).to have_link('More information', href: help_page_path('administration/incoming_email', anchor: 'set-it-up'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service desk has not been activated' do
|
||||
let(:project_without_service_desk) { create(:project, :private, service_desk_enabled: false) }
|
||||
|
||||
describe 'service desk info content' do
|
||||
context 'when user has permissions to edit project settings' do
|
||||
before do
|
||||
project_without_service_desk.add_maintainer(user)
|
||||
visit service_desk_project_issues_path(project_without_service_desk)
|
||||
end
|
||||
|
||||
it 'displays the large info box, documentation, and a button to configure' do
|
||||
aggregate_failures do
|
||||
expect(page).to have_css('.empty-state')
|
||||
expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
|
||||
expect(page).to have_link('Turn on Service Desk')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permission to edit project settings' do
|
||||
before do
|
||||
project_without_service_desk.add_guest(user)
|
||||
visit service_desk_project_issues_path(project_without_service_desk)
|
||||
end
|
||||
|
||||
it 'does not show a button configure service desk' do
|
||||
expect(page).not_to have_link('Turn on Service Desk')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service desk has been activated' do
|
||||
context 'when service desk is supported' do
|
||||
context 'when there are no issues' do
|
||||
describe 'service desk info content' do
|
||||
it 'displays the large info box, documentation, and the address' do
|
||||
|
@ -81,6 +36,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
|
|||
|
||||
aggregate_failures do
|
||||
expect(page).to have_css('.empty-state')
|
||||
expect(page).to have_text('Use Service Desk to connect with your users')
|
||||
expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
|
||||
expect(page).not_to have_link('Turn on Service Desk')
|
||||
expect(page).to have_content(project.service_desk_address)
|
||||
|
@ -99,6 +55,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
|
|||
it 'displays the large info box and the documentation link' do
|
||||
aggregate_failures do
|
||||
expect(page).to have_css('.empty-state')
|
||||
expect(page).to have_text('Use Service Desk to connect with your users')
|
||||
expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
|
||||
expect(page).not_to have_link('Turn on Service Desk')
|
||||
expect(page).not_to have_content(project.service_desk_address)
|
||||
|
@ -155,5 +112,46 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service desk is not supported' do
|
||||
let(:project_without_service_desk) { create(:project, :private, service_desk_enabled: false) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::ServiceDesk).to receive(:supported?).and_return(false)
|
||||
visit service_desk_project_issues_path(project)
|
||||
end
|
||||
|
||||
describe 'service desk info content' do
|
||||
context 'when user has permissions to edit project settings' do
|
||||
before do
|
||||
project_without_service_desk.add_maintainer(user)
|
||||
visit service_desk_project_issues_path(project_without_service_desk)
|
||||
end
|
||||
|
||||
it 'informs user to setup incoming email to turn on support for Service Desk' do
|
||||
aggregate_failures do
|
||||
expect(page).to have_css('.empty-state')
|
||||
expect(page).to have_text('Service Desk is not supported')
|
||||
expect(page).to have_text('In order to enable Service Desk for your instance, you must first set up incoming email.')
|
||||
expect(page).to have_link('More information', href: help_page_path('administration/incoming_email', anchor: 'set-it-up'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permission to edit project settings' do
|
||||
before do
|
||||
project_without_service_desk.add_developer(user)
|
||||
visit service_desk_project_issues_path(project_without_service_desk)
|
||||
end
|
||||
|
||||
it 'informs user to contact an administrator to enable service desk' do
|
||||
expect(page).to have_css('.empty-state')
|
||||
# NOTE: here, "enabled" is not used in the sense of "ServiceDesk::Enabled?"
|
||||
expect(page).to have_text('Service Desk is not enabled')
|
||||
expect(page).to have_text('For help setting up the Service Desk for your instance, please contact an administrator.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -92,5 +92,21 @@ describe('JiraIssuesFields', () => {
|
|||
|
||||
expect(wrapper.find(`a[href="${defaultProps.editProjectPath}"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('GitLab issues warning', () => {
|
||||
const expectedText = 'Consider disabling GitLab issues';
|
||||
|
||||
it('contains warning when GitLab issues is enabled', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.text()).toContain(expectedText);
|
||||
});
|
||||
|
||||
it('does not contain warning when GitLab issues is disabled', () => {
|
||||
createComponent({ gitlabIssuesEnabled: false });
|
||||
|
||||
expect(wrapper.text()).not.toContain(expectedText);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { emptyStateHelper, generateMessages } from '~/issuables_list/service_desk_helper';
|
||||
|
||||
describe('service desk helper', () => {
|
||||
const emptyStateMessages = generateMessages({});
|
||||
|
||||
// Note: isServiceDeskEnabled must not be true when isServiceDeskSupported is false (it's an invalid case).
|
||||
describe.each`
|
||||
isServiceDeskSupported | isServiceDeskEnabled | canEditProjectSettings | expectedMessage
|
||||
${true} | ${true} | ${true} | ${'serviceDeskEnabledAndCanEditProjectSettings'}
|
||||
${true} | ${true} | ${false} | ${'serviceDeskEnabledAndCannotEditProjectSettings'}
|
||||
${true} | ${false} | ${true} | ${'serviceDeskDisabledAndCanEditProjectSettings'}
|
||||
${true} | ${false} | ${false} | ${'serviceDeskDisabledAndCannotEditProjectSettings'}
|
||||
${false} | ${false} | ${true} | ${'serviceDeskIsNotSupported'}
|
||||
${false} | ${false} | ${false} | ${'serviceDeskIsNotEnabled'}
|
||||
`(
|
||||
'isServiceDeskSupported = $isServiceDeskSupported, isServiceDeskEnabled = $isServiceDeskEnabled, canEditProjectSettings = $canEditProjectSettings',
|
||||
({ isServiceDeskSupported, isServiceDeskEnabled, canEditProjectSettings, expectedMessage }) => {
|
||||
it(`displays ${expectedMessage} message`, () => {
|
||||
const emptyStateMeta = {
|
||||
isServiceDeskEnabled,
|
||||
isServiceDeskSupported,
|
||||
canEditProjectSettings,
|
||||
};
|
||||
expect(emptyStateHelper(emptyStateMeta)).toEqual(emptyStateMessages[expectedMessage]);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
|
@ -36,11 +36,26 @@ describe('Description component', () => {
|
|||
$('.issuable-meta .flash-container').remove();
|
||||
});
|
||||
|
||||
it('animates description changes', () => {
|
||||
it('doesnt animate first description changes', () => {
|
||||
vm.descriptionHtml = 'changed';
|
||||
|
||||
return vm.$nextTick().then(() => {
|
||||
expect(
|
||||
vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'),
|
||||
).toBeFalsy();
|
||||
jest.runAllTimers();
|
||||
return vm.$nextTick();
|
||||
});
|
||||
});
|
||||
|
||||
it('animates description changes on live update', () => {
|
||||
vm.descriptionHtml = 'changed';
|
||||
return vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
vm.descriptionHtml = 'changed second time';
|
||||
return vm.$nextTick();
|
||||
})
|
||||
.then(() => {
|
||||
expect(
|
||||
vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'),
|
||||
|
|
|
@ -2,99 +2,139 @@
|
|||
|
||||
exports[`packages_list_row renders 1`] = `
|
||||
<div
|
||||
class="gl-responsive-table-row"
|
||||
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
|
||||
data-qa-selector="packages-row"
|
||||
>
|
||||
<div
|
||||
class="table-section section-50 d-flex flex-md-column justify-content-between flex-wrap"
|
||||
class="gl-display-flex gl-align-items-center gl-py-5"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="d-flex align-items-center mr-2"
|
||||
class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-fill-1"
|
||||
>
|
||||
<gl-link-stub
|
||||
class="text-dark font-weight-bold mb-md-1"
|
||||
data-qa-selector="package_link"
|
||||
href="foo"
|
||||
<div
|
||||
class="gl-display-flex gl-flex-direction-column gl-justify-content-space-between gl-xs-mb-3"
|
||||
>
|
||||
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6"
|
||||
>
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-mr-3"
|
||||
>
|
||||
<gl-link-stub
|
||||
class="gl-text-body"
|
||||
data-qa-selector="package_link"
|
||||
href="foo"
|
||||
>
|
||||
|
||||
Test package
|
||||
|
||||
</gl-link-stub>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="d-flex text-secondary text-truncate mt-md-2"
|
||||
>
|
||||
<span>
|
||||
1.0.0
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<gl-icon-stub
|
||||
class="text-secondary ml-2 mr-1"
|
||||
name="review-list"
|
||||
size="16"
|
||||
/>
|
||||
</gl-link-stub>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<gl-link-stub
|
||||
class="text-secondary"
|
||||
data-testid="packages-row-project"
|
||||
href="/foo/bar/baz"
|
||||
<div
|
||||
class="gl-text-gray-500 gl-mt-1 gl-min-h-6"
|
||||
>
|
||||
<div
|
||||
class="gl-display-flex"
|
||||
>
|
||||
<span>
|
||||
1.0.0
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center"
|
||||
>
|
||||
<gl-icon-stub
|
||||
class="gl-ml-3 gl-mr-2"
|
||||
name="review-list"
|
||||
size="16"
|
||||
/>
|
||||
|
||||
<gl-link-stub
|
||||
class="gl-text-body"
|
||||
data-testid="packages-row-project"
|
||||
href="/foo/bar/baz"
|
||||
>
|
||||
|
||||
|
||||
</gl-link-stub>
|
||||
|
||||
</gl-link-stub>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="d-flex align-items-center"
|
||||
data-testid="package-type"
|
||||
>
|
||||
<gl-icon-stub
|
||||
class="gl-ml-3 gl-mr-2"
|
||||
name="package"
|
||||
size="16"
|
||||
/>
|
||||
|
||||
<span>
|
||||
Maven
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="d-flex align-items-center"
|
||||
data-testid="package-type"
|
||||
class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500"
|
||||
>
|
||||
<gl-icon-stub
|
||||
class="text-secondary ml-2 mr-1"
|
||||
name="package"
|
||||
size="16"
|
||||
/>
|
||||
<div
|
||||
class="gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6"
|
||||
>
|
||||
<publish-method-stub
|
||||
packageentity="[object Object]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span>
|
||||
Maven
|
||||
</span>
|
||||
<div
|
||||
class="gl-mt-1 gl-min-h-6"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Created %{timestamp}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="table-section d-flex flex-md-column justify-content-between align-items-md-end section-40"
|
||||
>
|
||||
<publish-method-stub
|
||||
packageentity="[object Object]"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="text-secondary order-0 order-md-1 mt-md-2"
|
||||
class="gl-w-9 gl-display-none gl-display-sm-flex gl-justify-content-end gl-pr-1"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Created %{timestamp}"
|
||||
<gl-button-stub
|
||||
aria-label="Remove package"
|
||||
category="primary"
|
||||
data-testid="action-delete"
|
||||
icon="remove"
|
||||
size="medium"
|
||||
title="Remove package"
|
||||
variant="danger"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="table-section section-10 d-flex justify-content-end"
|
||||
class="gl-display-flex"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-label="Remove package"
|
||||
category="primary"
|
||||
data-testid="action-delete"
|
||||
icon="remove"
|
||||
size="medium"
|
||||
title="Remove package"
|
||||
variant="danger"
|
||||
<div
|
||||
class="gl-w-7"
|
||||
/>
|
||||
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="gl-w-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`publish_method renders 1`] = `
|
||||
<div
|
||||
class="d-flex align-items-center text-secondary order-1 order-md-0 mb-md-1"
|
||||
class="d-flex align-items-center order-1 order-md-0 mb-md-1"
|
||||
>
|
||||
<gl-icon-stub
|
||||
class="mr-1"
|
||||
|
@ -10,11 +10,11 @@ exports[`publish_method renders 1`] = `
|
|||
size="16"
|
||||
/>
|
||||
|
||||
<strong
|
||||
class="mr-1 text-dark"
|
||||
<span
|
||||
class="mr-1"
|
||||
>
|
||||
branch-name
|
||||
</strong>
|
||||
</span>
|
||||
|
||||
<gl-icon-stub
|
||||
class="mr-1"
|
||||
|
@ -30,7 +30,7 @@ exports[`publish_method renders 1`] = `
|
|||
</gl-link-stub>
|
||||
|
||||
<clipboard-button-stub
|
||||
cssclass="border-0 text-secondary py-0 px-1"
|
||||
cssclass="border-0 py-0 px-1"
|
||||
text="sha-baz"
|
||||
title="Copy commit SHA"
|
||||
tooltipplacement="top"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import PackagesListRow from '~/packages/shared/components/package_list_row.vue';
|
||||
import PackageTags from '~/packages/shared/components/package_tags.vue';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import { packageList } from '../../mock_data';
|
||||
|
||||
describe('packages_list_row', () => {
|
||||
|
@ -17,14 +18,12 @@ describe('packages_list_row', () => {
|
|||
const mountComponent = ({
|
||||
isGroup = false,
|
||||
packageEntity = packageWithoutTags,
|
||||
shallow = true,
|
||||
showPackageType = true,
|
||||
disableDelete = false,
|
||||
} = {}) => {
|
||||
const mountFunc = shallow ? shallowMount : mount;
|
||||
|
||||
wrapper = mountFunc(PackagesListRow, {
|
||||
wrapper = shallowMount(PackagesListRow, {
|
||||
store,
|
||||
stubs: { ListItem },
|
||||
propsData: {
|
||||
packageLink: 'foo',
|
||||
packageEntity,
|
||||
|
@ -92,15 +91,14 @@ describe('packages_list_row', () => {
|
|||
});
|
||||
|
||||
describe('delete event', () => {
|
||||
beforeEach(() => mountComponent({ packageEntity: packageWithoutTags, shallow: false }));
|
||||
beforeEach(() => mountComponent({ packageEntity: packageWithoutTags }));
|
||||
|
||||
it('emits the packageToDelete event when the delete button is clicked', () => {
|
||||
findDeleteButton().trigger('click');
|
||||
it('emits the packageToDelete event when the delete button is clicked', async () => {
|
||||
findDeleteButton().vm.$emit('click');
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.emitted('packageToDelete')).toBeTruthy();
|
||||
expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.emitted('packageToDelete')).toBeTruthy();
|
||||
expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -54,7 +54,6 @@ describe('delete_button', () => {
|
|||
mountComponent({ disabled: true });
|
||||
expect(findButton().attributes()).toMatchObject({
|
||||
'aria-label': 'Foo title',
|
||||
category: 'secondary',
|
||||
icon: 'remove',
|
||||
title: 'Foo title',
|
||||
variant: 'danger',
|
||||
|
|
|
@ -115,7 +115,6 @@ describe('Tags List', () => {
|
|||
|
||||
// The list has only two tags and for some reasons .at(-1) does not work
|
||||
expect(rows.at(1).attributes()).toMatchObject({
|
||||
last: 'true',
|
||||
isdesktop: 'true',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@ import { GlIcon, GlSprintf } from '@gitlab/ui';
|
|||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import Component from '~/registry/explorer/components/list_page/image_list_row.vue';
|
||||
import ListItem from '~/registry/explorer/components/list_item.vue';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import DeleteButton from '~/registry/explorer/components/delete_button.vue';
|
||||
import {
|
||||
ROW_SCHEDULED_FOR_DELETION,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import RealDeleteModal from '~/registry/explorer/components/details_page/delete_modal.vue';
|
||||
import RealListItem from '~/registry/explorer/components/list_item.vue';
|
||||
import RealListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
|
||||
export const GlModal = {
|
||||
template: '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-ok"></slot></div>',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import component from '~/registry/explorer/components/list_item.vue';
|
||||
import component from '~/vue_shared/components/registry/list_item.vue';
|
||||
|
||||
describe('list item', () => {
|
||||
let wrapper;
|
||||
|
@ -34,7 +34,7 @@ describe('list item', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
it.each`
|
||||
describe.each`
|
||||
slotName | finderFunction
|
||||
${'left-primary'} | ${findLeftPrimarySlot}
|
||||
${'left-secondary'} | ${findLeftSecondarySlot}
|
||||
|
@ -42,10 +42,18 @@ describe('list item', () => {
|
|||
${'right-secondary'} | ${findRightSecondarySlot}
|
||||
${'left-action'} | ${findLeftActionSlot}
|
||||
${'right-action'} | ${findRightActionSlot}
|
||||
`('has a $slotName slot', ({ finderFunction }) => {
|
||||
mountComponent();
|
||||
`('$slotName slot', ({ finderFunction, slotName }) => {
|
||||
it('exist when the slot is filled', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(finderFunction().exists()).toBe(true);
|
||||
expect(finderFunction().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not exist when the slot is empty', () => {
|
||||
mountComponent({}, { [slotName]: '' });
|
||||
|
||||
expect(finderFunction().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each`
|
||||
|
@ -106,51 +114,22 @@ describe('list item', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('first prop', () => {
|
||||
it('when is true displays a double top border', () => {
|
||||
mountComponent({ first: true });
|
||||
describe('borders and selection', () => {
|
||||
it.each`
|
||||
first | selected | shouldHave | shouldNotHave
|
||||
${true} | ${true} | ${['gl-bg-blue-50', 'gl-border-blue-200']} | ${['gl-border-t-transparent', 'gl-border-t-gray-100']}
|
||||
${false} | ${true} | ${['gl-bg-blue-50', 'gl-border-blue-200']} | ${['gl-border-t-transparent', 'gl-border-t-gray-100']}
|
||||
${true} | ${false} | ${['gl-border-b-gray-100']} | ${['gl-bg-blue-50', 'gl-border-blue-200']}
|
||||
${false} | ${false} | ${['gl-border-b-gray-100']} | ${['gl-bg-blue-50', 'gl-border-blue-200']}
|
||||
`(
|
||||
'when first is $first and selected is $selected',
|
||||
({ first, selected, shouldHave, shouldNotHave }) => {
|
||||
mountComponent({ first, selected });
|
||||
|
||||
expect(wrapper.classes('gl-border-t-2')).toBe(true);
|
||||
});
|
||||
expect(wrapper.classes()).toEqual(expect.arrayContaining(shouldHave));
|
||||
|
||||
it('when is false display a single top border', () => {
|
||||
mountComponent({ first: false });
|
||||
|
||||
expect(wrapper.classes('gl-border-t-1')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('last prop', () => {
|
||||
it('when is true displays a double bottom border', () => {
|
||||
mountComponent({ last: true });
|
||||
|
||||
expect(wrapper.classes('gl-border-b-2')).toBe(true);
|
||||
});
|
||||
|
||||
it('when is false display a single bottom border', () => {
|
||||
mountComponent({ last: false });
|
||||
|
||||
expect(wrapper.classes('gl-border-b-1')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selected prop', () => {
|
||||
it('when true applies the selected border and background', () => {
|
||||
mountComponent({ selected: true });
|
||||
|
||||
expect(wrapper.classes()).toEqual(
|
||||
expect.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
|
||||
);
|
||||
expect(wrapper.classes()).toEqual(expect.not.arrayContaining(['gl-border-gray-100']));
|
||||
});
|
||||
|
||||
it('when false applies the default border', () => {
|
||||
mountComponent({ selected: false });
|
||||
|
||||
expect(wrapper.classes()).toEqual(
|
||||
expect.not.arrayContaining(['gl-bg-blue-50', 'gl-border-blue-200']),
|
||||
);
|
||||
expect(wrapper.classes()).toEqual(expect.arrayContaining(['gl-border-gray-100']));
|
||||
});
|
||||
expect(wrapper.classes()).toEqual(expect.not.arrayContaining(shouldNotHave));
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -73,6 +73,39 @@ RSpec.describe SearchHelper do
|
|||
expect(result.keys).to match_array(%i[category id label url avatar_url])
|
||||
end
|
||||
|
||||
it 'includes the first 5 of the users recent issues' do
|
||||
recent_issues = instance_double(::Gitlab::Search::RecentIssues)
|
||||
expect(::Gitlab::Search::RecentIssues).to receive(:new).with(user: user).and_return(recent_issues)
|
||||
project1 = create(:project, :with_avatar, namespace: user.namespace)
|
||||
project2 = create(:project, namespace: user.namespace)
|
||||
issue1 = create(:issue, title: 'issue 1', project: project1)
|
||||
issue2 = create(:issue, title: 'issue 2', project: project2)
|
||||
|
||||
other_issues = create_list(:issue, 5)
|
||||
|
||||
expect(recent_issues).to receive(:search).with('the search term').and_return(Issue.id_in_ordered([issue1.id, issue2.id, *other_issues.map(&:id)]))
|
||||
|
||||
results = search_autocomplete_opts("the search term")
|
||||
|
||||
expect(results.count).to eq(5)
|
||||
|
||||
expect(results[0]).to include({
|
||||
category: 'Recent issues',
|
||||
id: issue1.id,
|
||||
label: 'issue 1',
|
||||
url: Gitlab::Routing.url_helpers.project_issue_path(issue1.project, issue1),
|
||||
avatar_url: project1.avatar_url
|
||||
})
|
||||
|
||||
expect(results[1]).to include({
|
||||
category: 'Recent issues',
|
||||
id: issue2.id,
|
||||
label: 'issue 2',
|
||||
url: Gitlab::Routing.url_helpers.project_issue_path(issue2.project, issue2),
|
||||
avatar_url: '' # This project didn't have an avatar so set this to ''
|
||||
})
|
||||
end
|
||||
|
||||
it "does not include the public group" do
|
||||
group = create(:group)
|
||||
expect(search_autocomplete_opts(group.name).size).to eq(0)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue