Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-05 15:09:46 +00:00
parent 5147cd60f1
commit 01034c2c45
63 changed files with 1019 additions and 374 deletions

View file

@ -37,6 +37,7 @@ AllCops:
- 'file_hooks/**/*'
- 'workhorse/**/*'
- 'spec/support/*.git/**/*' # e.g. spec/support/gitlab-git-test.git
- 'db/ci_migrate/*.rb' # since the `db/ci_migrate` is a symlinked to `db/migrate`
CacheRootDirectory: tmp
MaxFilesInCache: 25000

View file

@ -1 +1 @@
6430f0f4df82aecc0b282d8fb620d1d9219a6aee
59dfc252c79b7f9d290a3ede54c9ba8a3b12d4bd

View file

@ -339,7 +339,7 @@ gem 'warning', '~> 1.2.0'
group :development do
gem 'lefthook', '~> 0.7.0', require: false
gem 'solargraph', '~> 0.42', require: false
gem 'solargraph', '~> 0.43', require: false
gem 'letter_opener_web', '~> 1.4.0'

View file

@ -1206,7 +1206,7 @@ GEM
slack-messenger (2.3.4)
snowplow-tracker (0.6.1)
contracts (~> 0.7, <= 0.11)
solargraph (0.42.3)
solargraph (0.43.0)
backport (~> 1.2)
benchmark
bundler (>= 1.17.2)
@ -1631,7 +1631,7 @@ DEPENDENCIES
simplecov-cobertura (~> 1.3.1)
slack-messenger (~> 2.3.4)
snowplow-tracker (~> 0.6.1)
solargraph (~> 0.42)
solargraph (~> 0.43)
spamcheck (~> 0.1.0)
spring (~> 2.1.0)
spring-commands-rspec (~> 1.0.4)

View file

@ -9,8 +9,7 @@ import { toNumber, omit } from 'lodash';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { scrollToElement, historyPushState } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { setUrlParams, urlParamsToObject, getParameterByName } from '~/lib/utils/url_utility';
import { setUrlParams, queryToObject, getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import initManualOrdering from '~/manual_ordering';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
@ -264,8 +263,7 @@ export default {
});
},
getQueryObject() {
// eslint-disable-next-line import/no-deprecated
return urlParamsToObject(window.location.search);
return queryToObject(window.location.search, { gatherArrays: true });
},
onPaginate(newPage) {
if (newPage === this.page) return;

View file

@ -13,6 +13,7 @@ import {
scrollUp,
} from '~/lib/utils/scroll_utils';
import { __ } from '~/locale';
import { reportToSentry } from '../utils';
import * as types from './mutation_types';
export const init = ({ dispatch }, { endpoint, logState, pagePath }) => {
@ -175,11 +176,14 @@ export const fetchTrace = ({ dispatch, state }) =>
dispatch('startPollingTrace');
}
})
.catch((e) =>
e.response.status === httpStatusCodes.FORBIDDEN
? dispatch('receiveTraceUnauthorizedError')
: dispatch('receiveTraceError'),
);
.catch((e) => {
if (e.response.status === httpStatusCodes.FORBIDDEN) {
dispatch('receiveTraceUnauthorizedError');
} else {
reportToSentry('job_actions', e);
dispatch('receiveTraceError');
}
});
export const startPollingTrace = ({ dispatch, commit }) => {
const traceTimeout = setTimeout(() => {

View file

@ -1,3 +1,5 @@
import * as Sentry from '@sentry/browser';
/**
* capture anything starting with http:// or https://
* https?:\/\/
@ -10,3 +12,10 @@
*/
export const linkRegex = /(https?:\/\/[^"<>()\\^`{|}\s]+[^"<>()\\^`{|}\s.,:;!?])/g;
export default { linkRegex };
export const reportToSentry = (component, failureType) => {
Sentry.withScope((scope) => {
scope.setTag('component', component);
Sentry.captureException(failureType);
});
};

View file

@ -1,9 +1,12 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { mapGetters, mapState } from 'vuex';
import { s__ } from '~/locale';
import { TrackingActions, TrackingLabels } from '~/packages/details/constants';
import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
import {
TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
export default {
@ -14,9 +17,25 @@ export default {
GlLink,
GlSprintf,
},
inject: ['composerHelpPath', 'composerConfigRepositoryName', 'composerPath', 'groupListUrl'],
props: {
packageEntity: {
type: Object,
required: true,
},
},
computed: {
...mapState(['composerHelpPath']),
...mapGetters(['composerRegistryInclude', 'composerPackageInclude', 'groupExists']),
composerRegistryInclude() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `composer config repositories.${this.composerConfigRepositoryName} '{"type": "composer", "url": "${this.composerPath}"}'`;
},
composerPackageInclude() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `composer req ${[this.packageEntity.name]}:${this.packageEntity.version}`;
},
groupExists() {
return this.groupListUrl?.length > 0;
},
},
i18n: {
registryInclude: s__('PackageRegistry|Add composer registry'),
@ -27,8 +46,11 @@ export default {
'PackageRegistry|For more information on Composer packages in GitLab, %{linkStart}see the documentation.%{linkEnd}',
),
},
trackingActions: { ...TrackingActions },
TrackingLabels,
tracking: {
TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
},
installOptions: [{ value: 'composer', label: s__('PackageRegistry|Show Composer commands') }],
};
</script>
@ -41,8 +63,8 @@ export default {
:label="$options.i18n.registryInclude"
:instruction="composerRegistryInclude"
:copy-text="$options.i18n.copyRegistryInclude"
:tracking-action="$options.trackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
data-testid="registry-include"
/>
@ -50,8 +72,8 @@ export default {
:label="$options.i18n.packageInclude"
:instruction="composerPackageInclude"
:copy-text="$options.i18n.copyPackageInclude"
:tracking-action="$options.trackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
data-testid="package-include"
/>
<span data-testid="help-text">

View file

@ -1,9 +1,12 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { mapGetters, mapState } from 'vuex';
import { s__ } from '~/locale';
import { TrackingActions, TrackingLabels } from '~/packages/details/constants';
import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
import {
TRACKING_ACTION_COPY_CONAN_COMMAND,
TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
export default {
@ -14,17 +17,34 @@ export default {
GlLink,
GlSprintf,
},
inject: ['conanHelpPath', 'conanPath'],
props: {
packageEntity: {
type: Object,
required: true,
},
},
computed: {
...mapState(['conanHelpPath']),
...mapGetters(['conanInstallationCommand', 'conanSetupCommand']),
conanInstallationCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `conan install ${this.packageEntity.name} --remote=gitlab`;
},
conanSetupCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `conan remote add gitlab ${this.conanPath}`;
},
},
i18n: {
helpText: s__(
'PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}.',
),
},
trackingActions: { ...TrackingActions },
TrackingLabels,
tracking: {
TRACKING_ACTION_COPY_CONAN_COMMAND,
TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
},
installOptions: [{ value: 'conan', label: s__('PackageRegistry|Show Conan commands') }],
};
</script>
@ -37,8 +57,8 @@ export default {
:label="s__('PackageRegistry|Conan Command')"
:instruction="conanInstallationCommand"
:copy-text="s__('PackageRegistry|Copy Conan Command')"
:tracking-action="$options.trackingActions.COPY_CONAN_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_CONAN_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
/>
<h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
@ -47,8 +67,8 @@ export default {
:label="s__('PackageRegistry|Add Conan Remote')"
:instruction="conanSetupCommand"
:copy-text="s__('PackageRegistry|Copy Conan Setup Command')"
:tracking-action="$options.trackingActions.COPY_CONAN_SETUP_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">

View file

@ -1,6 +1,12 @@
<script>
import { PackageType, TERRAFORM_PACKAGE_TYPE } from '~/packages/shared/constants';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
import {
PACKAGE_TYPE_CONAN,
PACKAGE_TYPE_MAVEN,
PACKAGE_TYPE_NPM,
PACKAGE_TYPE_NUGET,
PACKAGE_TYPE_PYPI,
PACKAGE_TYPE_COMPOSER,
} from '~/packages_and_registries/package_registry/constants';
import ComposerInstallation from './composer_installation.vue';
import ConanInstallation from './conan_installation.vue';
import MavenInstallation from './maven_installation.vue';
@ -11,33 +17,22 @@ import PypiInstallation from './pypi_installation.vue';
export default {
name: 'InstallationCommands',
components: {
[PackageType.CONAN]: ConanInstallation,
[PackageType.MAVEN]: MavenInstallation,
[PackageType.NPM]: NpmInstallation,
[PackageType.NUGET]: NugetInstallation,
[PackageType.PYPI]: PypiInstallation,
[PackageType.COMPOSER]: ComposerInstallation,
[TERRAFORM_PACKAGE_TYPE]: TerraformInstallation,
[PACKAGE_TYPE_CONAN]: ConanInstallation,
[PACKAGE_TYPE_MAVEN]: MavenInstallation,
[PACKAGE_TYPE_NPM]: NpmInstallation,
[PACKAGE_TYPE_NUGET]: NugetInstallation,
[PACKAGE_TYPE_PYPI]: PypiInstallation,
[PACKAGE_TYPE_COMPOSER]: ComposerInstallation,
},
props: {
packageEntity: {
type: Object,
required: true,
},
npmPath: {
type: String,
required: false,
default: '',
},
npmHelpPath: {
type: String,
required: false,
default: '',
},
},
computed: {
installationComponent() {
return this.$options.components[this.packageEntity.package_type];
return this.$options.components[this.packageEntity.packageType];
},
},
};
@ -45,11 +40,6 @@ export default {
<template>
<div v-if="installationComponent">
<component
:is="installationComponent"
:name="packageEntity.name"
:registry-url="npmPath"
:help-url="npmHelpPath"
/>
<component :is="installationComponent" :package-entity="packageEntity" />
</div>
</template>

View file

@ -1,9 +1,18 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { mapGetters, mapState } from 'vuex';
import { s__ } from '~/locale';
import { TrackingActions, TrackingLabels } from '~/packages/details/constants';
import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
import {
TRACKING_ACTION_COPY_MAVEN_XML,
TRACKING_ACTION_COPY_MAVEN_COMMAND,
TRACKING_ACTION_COPY_MAVEN_SETUP,
TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND,
TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND,
TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
TRACKING_LABEL_MAVEN_INSTALLATION,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
export default {
@ -14,22 +23,80 @@ export default {
GlLink,
GlSprintf,
},
inject: ['mavenHelpPath', 'mavenPath'],
props: {
packageEntity: {
type: Object,
required: true,
},
},
data() {
return {
instructionType: 'maven',
};
},
computed: {
...mapState(['mavenHelpPath']),
...mapGetters([
'mavenInstallationXml',
'mavenInstallationCommand',
'mavenSetupXml',
'gradleGroovyInstalCommand',
'gradleGroovyAddSourceCommand',
'gradleKotlinInstalCommand',
'gradleKotlinAddSourceCommand',
]),
appGroup() {
return this.packageEntity.metadata.appGroup;
},
appName() {
return this.packageEntity.metadata.appName;
},
appVersion() {
return this.packageEntity.metadata.appVersion;
},
mavenInstallationXml() {
return `<dependency>
<groupId>${this.appGroup}</groupId>
<artifactId>${this.appName}</artifactId>
<version>${this.appVersion}</version>
</dependency>`;
},
mavenInstallationCommand() {
return `mvn dependency:get -Dartifact=${this.appGroup}:${this.appName}:${this.appVersion}`;
},
mavenSetupXml() {
return `<repositories>
<repository>
<id>gitlab-maven</id>
<url>${this.mavenPath}</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
<url>${this.mavenPath}</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
<url>${this.mavenPath}</url>
</snapshotRepository>
</distributionManagement>`;
},
gradleGroovyInstalCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `implementation '${this.appGroup}:${this.appName}:${this.appVersion}'`;
},
gradleGroovyAddSourceCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `maven {
url '${this.mavenPath}'
}`;
},
gradleKotlinInstalCommand() {
return `implementation("${this.appGroup}:${this.appName}:${this.appVersion}")`;
},
gradleKotlinAddSourceCommand() {
return `maven("${this.mavenPath}")`;
},
showMaven() {
return this.instructionType === 'maven';
},
@ -48,8 +115,18 @@ export default {
'PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}.',
),
},
trackingActions: { ...TrackingActions },
TrackingLabels,
tracking: {
TRACKING_ACTION_COPY_MAVEN_XML,
TRACKING_ACTION_COPY_MAVEN_COMMAND,
TRACKING_ACTION_COPY_MAVEN_SETUP,
TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND,
TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND,
TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
TRACKING_LABEL_MAVEN_INSTALLATION,
},
installOptions: [
{ value: 'maven', label: s__('PackageRegistry|Maven XML') },
{ value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') },
@ -78,8 +155,8 @@ export default {
<code-instruction
:instruction="mavenInstallationXml"
:copy-text="s__('PackageRegistry|Copy Maven XML')"
:tracking-action="$options.trackingActions.COPY_MAVEN_XML"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_XML"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
multiline
/>
@ -87,8 +164,8 @@ export default {
:label="s__('PackageRegistry|Maven Command')"
:instruction="mavenInstallationCommand"
:copy-text="s__('PackageRegistry|Copy Maven command')"
:tracking-action="$options.trackingActions.COPY_MAVEN_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
/>
<h3 class="gl-font-lg">{{ s__('PackageRegistry|Registry setup') }}</h3>
@ -102,8 +179,8 @@ export default {
<code-instruction
:instruction="mavenSetupXml"
:copy-text="s__('PackageRegistry|Copy Maven registry XML')"
:tracking-action="$options.trackingActions.COPY_MAVEN_SETUP"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_SETUP"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
multiline
/>
<gl-sprintf :message="$options.i18n.helpText">
@ -118,15 +195,15 @@ export default {
:label="s__('PackageRegistry|Gradle Groovy DSL install command')"
:instruction="gradleGroovyInstalCommand"
:copy-text="s__('PackageRegistry|Copy Gradle Groovy DSL install command')"
:tracking-action="$options.trackingActions.COPY_GRADLE_INSTALL_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
/>
<code-instruction
:label="s__('PackageRegistry|Add Gradle Groovy DSL repository command')"
:instruction="gradleGroovyAddSourceCommand"
:copy-text="s__('PackageRegistry|Copy add Gradle Groovy DSL repository command')"
:tracking-action="$options.trackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
multiline
/>
</template>
@ -136,15 +213,15 @@ export default {
:label="s__('PackageRegistry|Gradle Kotlin DSL install command')"
:instruction="gradleKotlinInstalCommand"
:copy-text="s__('PackageRegistry|Copy Gradle Kotlin DSL install command')"
:tracking-action="$options.trackingActions.COPY_KOTLIN_INSTALL_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
/>
<code-instruction
:label="s__('PackageRegistry|Add Gradle Kotlin DSL repository command')"
:instruction="gradleKotlinAddSourceCommand"
:copy-text="s__('PackageRegistry|Copy add Gradle Kotlin DSL repository command')"
:tracking-action="$options.trackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
multiline
/>
</template>

View file

@ -10,7 +10,6 @@ export const PACKAGE_TYPE_RUBYGEMS = 'RUBYGEMS';
export const PACKAGE_TYPE_GENERIC = 'GENERIC';
export const PACKAGE_TYPE_DEBIAN = 'DEBIAN';
export const PACKAGE_TYPE_HELM = 'HELM';
export const PACKAGE_TYPE_TERRAFORM = 'terraform_module';
export const DELETE_PACKAGE_TRACKING_ACTION = 'delete_package';
export const REQUEST_DELETE_PACKAGE_TRACKING_ACTION = 'request_delete_package';
@ -20,6 +19,46 @@ export const DELETE_PACKAGE_FILE_TRACKING_ACTION = 'delete_package_file';
export const REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'request_delete_package_file';
export const CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'cancel_delete_package_file';
export const TRACKING_LABEL_CODE_INSTRUCTION = 'code_instruction';
export const TRACKING_LABEL_CONAN_INSTALLATION = 'conan_installation';
export const TRACKING_LABEL_MAVEN_INSTALLATION = 'maven_installation';
export const TRACKING_LABEL_NPM_INSTALLATION = 'npm_installation';
export const TRACKING_LABEL_NUGET_INSTALLATION = 'nuget_installation';
export const TRACKING_LABEL_PYPI_INSTALLATION = 'pypi_installation';
export const TRACKING_LABEL_COMPOSER_INSTALLATION = 'composer_installation';
export const TRACKING_ACTION_INSTALLATION = 'installation';
export const TRACKING_ACTION_REGISTRY_SETUP = 'registry_setup';
export const TRACKING_ACTION_COPY_CONAN_COMMAND = 'copy_conan_command';
export const TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND = 'copy_conan_setup_command';
export const TRACKING_ACTION_COPY_MAVEN_XML = 'copy_maven_xml';
export const TRACKING_ACTION_COPY_MAVEN_COMMAND = 'copy_maven_command';
export const TRACKING_ACTION_COPY_MAVEN_SETUP = 'copy_maven_setup_xml';
export const TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND = 'copy_gradle_install_command';
export const TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND =
'copy_gradle_add_to_source_command';
export const TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND = 'copy_kotlin_install_command';
export const TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND =
'copy_kotlin_add_to_source_command';
export const TRACKING_ACTION_COPY_NPM_INSTALL_COMMAND = 'copy_npm_install_command';
export const TRACKING_ACTION_COPY_NPM_SETUP_COMMAND = 'copy_npm_setup_command';
export const TRACKING_ACTION_COPY_YARN_INSTALL_COMMAND = 'copy_yarn_install_command';
export const TRACKING_ACTION_COPY_YARN_SETUP_COMMAND = 'copy_yarn_setup_command';
export const TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND = 'copy_nuget_install_command';
export const TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND = 'copy_nuget_setup_command';
export const TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND = 'copy_pip_install_command';
export const TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND = 'copy_pypi_setup_command';
export const TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND =
'copy_composer_registry_include_command';
export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND =
'copy_composer_package_include_command';
export const TrackingCategories = {
[PACKAGE_TYPE_MAVEN]: 'MavenPackages',
[PACKAGE_TYPE_NPM]: 'NpmPackages',

View file

@ -7,6 +7,7 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants';
import DismissPipelineGraphCallout from '../../graphql/mutations/dismiss_pipeline_notification.graphql';
import getPipelineQuery from '../../graphql/queries/get_pipeline_header_data.query.graphql';
import { reportToSentry, reportMessageToSentry } from '../../utils';
import { listByLayers } from '../parsing_utils';
import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants';
@ -51,6 +52,7 @@ export default {
alertType: null,
callouts: [],
currentViewType: STAGE_VIEW,
canRefetchHeaderPipeline: false,
pipeline: null,
pipelineLayers: null,
showAlert: false,
@ -78,6 +80,26 @@ export default {
);
},
},
headerPipeline: {
query: getPipelineQuery,
// this query is already being called in header_component.vue, which shares the same cache as this component
// the skip here is to prevent sending double network requests on page load
skip() {
return !this.canRefetchHeaderPipeline;
},
variables() {
return {
fullPath: this.pipelineProjectPath,
iid: this.pipelineIid,
};
},
update(data) {
return data.project?.pipeline || {};
},
error() {
this.reportFailure({ type: LOAD_FAILURE, skipSentry: true });
},
},
pipeline: {
context() {
return getQueryHeaders(this.graphqlResourceEtag);
@ -217,6 +239,10 @@ export default {
},
refreshPipelineGraph() {
this.$apollo.queries.pipeline.refetch();
// this will update the status in header_component since they share the same cache
this.canRefetchHeaderPipeline = true;
this.$apollo.queries.headerPipeline.refetch();
},
/* eslint-disable @gitlab/require-i18n-strings */
reportFailure({ type, err = 'No error string passed.', skipSentry = false }) {

View file

@ -143,13 +143,6 @@ export default {
return cancelable && userPermissions.updatePipeline;
},
},
watch: {
isFinished(finished) {
if (finished) {
this.$apollo.queries.pipeline.stopPolling();
}
},
},
methods: {
reportFailure(errorType) {
this.failureType = errorType;
@ -218,7 +211,7 @@ export default {
};
</script>
<template>
<div class="pipeline-header-container">
<div class="js-pipeline-header-container">
<gl-alert v-if="hasError" :variant="failure.variant">{{ failure.text }}</gl-alert>
<ci-header
v-if="shouldRenderContent"

View file

@ -54,6 +54,8 @@ class ProjectsController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def new
return access_denied! unless current_user.can_create_project?
@namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
return access_denied! if @namespace && !can?(current_user, :create_projects, @namespace)

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Resolvers
class PaginatedTreeResolver < BaseResolver
type Types::Tree::TreeType.connection_type, null: true
extension Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension
calls_gitaly!
argument :path, GraphQL::Types::String,
required: false,
default_value: '', # root of the repository
description: 'The path to get the tree for. Default value is the root of the repository.'
argument :ref, GraphQL::Types::String,
required: false,
default_value: :head,
description: 'The commit ref to get the tree for. Default value is HEAD.'
argument :recursive, GraphQL::Types::Boolean,
required: false,
default_value: false,
description: 'Used to get a recursive tree. Default is false.'
alias_method :repository, :object
def resolve(**args)
return unless repository.exists?
cursor = args.delete(:after)
pagination_params = {
limit: @field.max_page_size || 100,
page_token: cursor
}
tree = repository.tree(args[:ref], args[:path], recursive: args[:recursive], pagination_params: pagination_params)
next_cursor = tree.cursor&.next_cursor
Gitlab::Graphql::ExternallyPaginatedArray.new(cursor, next_cursor, *tree)
rescue Gitlab::Git::CommandError => e
raise Gitlab::Graphql::Errors::ArgumentError, e
end
def self.field_options
super.merge(connection: false) # we manage the pagination manually, so opt out of the connection field extension
end
end
end

View file

@ -14,6 +14,10 @@ module Types
description: 'Indicates a corresponding Git repository exists on disk.'
field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true,
description: 'Tree of the repository.'
field :paginated_tree, Types::Tree::TreeType.connection_type, null: true, resolver: Resolvers::PaginatedTreeResolver, calls_gitaly: true,
max_page_size: 100,
description: 'Paginated tree of the repository.',
feature_flag: :paginated_tree_graphql_query
field :blobs, Types::Repository::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true,
description: 'Blobs contained within the repository'
field :branch_names, [GraphQL::Types::String], null: true, calls_gitaly: true,

View file

@ -267,7 +267,11 @@ module Nav
builder.add_primary_menu_item(id: 'your', title: _('Your projects'), href: dashboard_projects_path)
builder.add_primary_menu_item(id: 'starred', title: _('Starred projects'), href: starred_dashboard_projects_path)
builder.add_primary_menu_item(id: 'explore', title: _('Explore projects'), href: explore_root_path)
builder.add_secondary_menu_item(id: 'create', title: _('Create new project'), href: new_project_path)
if current_user.can_create_project?
builder.add_secondary_menu_item(id: 'create', title: _('Create new project'), href: new_project_path)
end
builder.build
end

View file

@ -527,6 +527,12 @@ class IssuableBaseService < ::BaseProjectService
def allowed_update_params(params)
params
end
def update_issuable_sla(issuable)
return unless issuable_sla = issuable.issuable_sla
issuable_sla.update(issuable_closed: issuable.closed?)
end
end
IssuableBaseService.prepend_mod_with('IssuableBaseService')

View file

@ -32,7 +32,7 @@ module Issues
notification_service.async.close_issue(issue, current_user, { closed_via: closed_via }) if notifications
todo_service.close_issue(issue, current_user)
resolve_alert(issue)
perform_incident_management_actions(issue)
execute_hooks(issue, 'close')
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
@ -51,6 +51,10 @@ module Issues
private
def perform_incident_management_actions(issue)
resolve_alert(issue)
end
def close_external_issue(issue, closed_via)
return unless project.external_issue_tracker&.support_close_issue?
@ -89,3 +93,5 @@ module Issues
end
end
end
Issues::CloseService.prepend_mod_with('Issues::CloseService')

View file

@ -9,6 +9,7 @@ module Issues
event_service.reopen_issue(issue, current_user)
create_note(issue, 'reopened')
notification_service.async.reopen_issue(issue, current_user)
perform_incident_management_actions(issue)
execute_hooks(issue, 'reopen')
invalidate_cache_counts(issue, users: issue.assignees)
issue.update_project_counter_caches
@ -21,8 +22,13 @@ module Issues
private
def perform_incident_management_actions(issue)
end
def create_note(issue, state = issue.state)
SystemNoteService.change_status(issue, issue.project, current_user, state, nil)
end
end
end
Issues::ReopenService.prepend_mod_with('Issues::ReopenService')

View file

@ -6,7 +6,8 @@
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
= _('Render diagrams in your documents using PlantUML.')
= link_to _('Learn more.'), help_page_path('administration/integration/plantuml.md'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-plantuml-settings'), html: { class: 'fieldset-form', id: 'plantuml-settings' } do |f|
= form_errors(@application_setting) if expanded
@ -20,8 +21,6 @@
= f.label :plantuml_url, _('PlantUML URL'), class: 'label-bold'
= f.text_field :plantuml_url, class: 'form-control gl-form-input', placeholder: 'http://your-plantuml-instance:8080'
.form-text.text-muted
Allow rendering of
= link_to "PlantUML", "http://plantuml.com"
diagrams in Asciidoc documents using an external PlantUML service.
= _('The hostname of your PlantUML server.')
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -0,0 +1,8 @@
---
name: paginated_tree_graphql_query
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66751
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337214
milestone: '14.2'
type: development
group: group::source code
default_enabled: false

View file

@ -1,7 +0,0 @@
# frozen_string_literal: true
ci_db_config = Gitlab::Application.config.database_configuration[Rails.env]["ci"]
if ci_db_config.present?
raise "migrations_paths setting for ci database must be `db/ci_migrate`" unless ci_db_config["migrations_paths"] == 'db/ci_migrate'
end

1
db/ci_migrate Symbolic link
View file

@ -0,0 +1 @@
migrate

View file

@ -1,31 +0,0 @@
# frozen_string_literal: true
class CreateCiInstanceVariablesOnCi < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
unless table_exists?(:ci_instance_variables)
create_table :ci_instance_variables do |t|
t.integer :variable_type, null: false, limit: 2, default: 1
t.boolean :masked, default: false, allow_null: false
t.boolean :protected, default: false, allow_null: false
t.text :key, null: false
t.text :encrypted_value
t.text :encrypted_value_iv
t.index [:key], name: 'index_ci_instance_variables_on_key', unique: true, using: :btree
end
end
add_text_limit(:ci_instance_variables, :key, 255)
# Use constraint_name generated from db/migrate/20200625193358_increase_size_on_instance_level_variable_values.rb
add_text_limit(:ci_instance_variables, :encrypted_value, 13_579, constraint_name: 'check_956afd70f1')
add_text_limit(:ci_instance_variables, :encrypted_value_iv, 255)
end
def down
drop_table :ci_instance_variables
end
end

View file

@ -1 +0,0 @@
1b74312f59f6f8937cd0dd754d22dc72e9bdc7302e6254a2fda5762afebe303c

View file

@ -1,45 +0,0 @@
CREATE TABLE ar_internal_metadata (
key character varying NOT NULL,
value character varying,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);
CREATE TABLE ci_instance_variables (
id bigint NOT NULL,
variable_type smallint DEFAULT 1 NOT NULL,
masked boolean DEFAULT false,
protected boolean DEFAULT false,
key text NOT NULL,
encrypted_value text,
encrypted_value_iv text,
CONSTRAINT check_07a45a5bcb CHECK ((char_length(encrypted_value_iv) <= 255)),
CONSTRAINT check_5aede12208 CHECK ((char_length(key) <= 255)),
CONSTRAINT check_956afd70f1 CHECK ((char_length(encrypted_value) <= 13579))
);
CREATE SEQUENCE ci_instance_variables_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_instance_variables_id_seq OWNED BY ci_instance_variables.id;
CREATE TABLE schema_migrations (
version character varying NOT NULL
);
ALTER TABLE ONLY ci_instance_variables ALTER COLUMN id SET DEFAULT nextval('ci_instance_variables_id_seq'::regclass);
ALTER TABLE ONLY ar_internal_metadata
ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key);
ALTER TABLE ONLY ci_instance_variables
ADD CONSTRAINT ci_instance_variables_pkey PRIMARY KEY (id);
ALTER TABLE ONLY schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
CREATE UNIQUE INDEX index_ci_instance_variables_on_key ON ci_instance_variables USING btree (key);

1
db/ci_structure.sql Symbolic link
View file

@ -0,0 +1 @@
structure.sql

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
class AddLabelAppliedIssuableClosedToIssuableSla < ActiveRecord::Migration[6.1]
def change
add_column :issuable_slas, :label_applied, :boolean, default: false, null: false
add_column :issuable_slas, :issuable_closed, :boolean, default: false, null: false
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddIndexForLabelAppliedToIssuableSla < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
INDEX_NAME = 'index_issuable_slas_on_due_at_id_label_applied_issuable_closed'
def up
add_concurrent_index :issuable_slas, [:due_at, :id], name: INDEX_NAME, where: 'label_applied = FALSE AND issuable_closed = FALSE'
end
def down
remove_concurrent_index_by_name :issuable_slas, INDEX_NAME
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
class ConfirmSecurityBot < ActiveRecord::Migration[6.0]
class User < ActiveRecord::Base
self.table_name = 'users'
SECURITY_BOT_TYPE = 8
end
def up
User.where(user_type: User::SECURITY_BOT_TYPE, confirmed_at: nil)
.update_all(confirmed_at: Time.current)
end
# no-op
# Security Bot should be always confirmed
def down
end
end

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
class UpdateIssuableSlasWhereIssueClosed < ActiveRecord::Migration[6.1]
ISSUE_CLOSED_STATUS = 2
class IssuableSla < ActiveRecord::Base
include EachBatch
self.table_name = 'issuable_slas'
belongs_to :issue, class_name: 'Issue'
end
class Issue < ActiveRecord::Base
self.table_name = 'issues'
has_one :issuable_sla, class_name: 'IssuableSla'
end
def up
IssuableSla.each_batch(of: 50) do |relation|
relation.joins(:issue)
.where(issues: { state_id: ISSUE_CLOSED_STATUS } )
.update_all(issuable_closed: true)
end
end
def down
# no-op
end
end

View file

@ -0,0 +1 @@
f3959b7a6f7ac95019f2f85c6383ddd11294562e94936ef3b5704bd4de7c5910

View file

@ -0,0 +1 @@
344736284dc18b5f7516ec2062bef99b2444ae31720691e56b4e8687d5566b31

View file

@ -0,0 +1 @@
dd3b35b87c2f015895d807ede2521c9672fb41ec7a3b0b1a2f7abdc009950b6e

View file

@ -0,0 +1 @@
8522eaf951d87de04aea82fe8e1a9577e6665c8d08245282239476e49b02bc7d

View file

@ -14200,7 +14200,9 @@ ALTER SEQUENCE issuable_severities_id_seq OWNED BY issuable_severities.id;
CREATE TABLE issuable_slas (
id bigint NOT NULL,
issue_id bigint NOT NULL,
due_at timestamp with time zone NOT NULL
due_at timestamp with time zone NOT NULL,
label_applied boolean DEFAULT false NOT NULL,
issuable_closed boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE issuable_slas_id_seq
@ -24047,6 +24049,8 @@ CREATE INDEX index_issuable_metric_images_on_issue_id ON issuable_metric_images
CREATE UNIQUE INDEX index_issuable_severities_on_issue_id ON issuable_severities USING btree (issue_id);
CREATE INDEX index_issuable_slas_on_due_at_id_label_applied_issuable_closed ON issuable_slas USING btree (due_at, id) WHERE ((label_applied = false) AND (issuable_closed = false));
CREATE UNIQUE INDEX index_issuable_slas_on_issue_id ON issuable_slas USING btree (issue_id);
CREATE INDEX index_issue_assignees_on_user_id ON issue_assignees USING btree (user_id);

View file

@ -7039,6 +7039,29 @@ The edge type for [`Todo`](#todo).
| <a id="todoedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="todoedgenode"></a>`node` | [`Todo`](#todo) | The item at the end of the edge. |
#### `TreeConnection`
The connection type for [`Tree`](#tree).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="treeconnectionedges"></a>`edges` | [`[TreeEdge]`](#treeedge) | A list of edges. |
| <a id="treeconnectionnodes"></a>`nodes` | [`[Tree]`](#tree) | A list of nodes. |
| <a id="treeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `TreeEdge`
The edge type for [`Tree`](#tree).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="treeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="treeedgenode"></a>`node` | [`Tree`](#tree) | The item at the end of the edge. |
#### `TreeEntryConnection`
The connection type for [`TreeEntry`](#treeentry).
@ -12714,6 +12737,24 @@ Returns [`[String!]`](#string).
| <a id="repositorybranchnamesoffset"></a>`offset` | [`Int!`](#int) | The number of branch names to skip. |
| <a id="repositorybranchnamessearchpattern"></a>`searchPattern` | [`String!`](#string) | The pattern to search for branch names by. |
##### `Repository.paginatedTree`
Paginated tree of the repository. Available only when feature flag `paginated_tree_graphql_query` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
Returns [`TreeConnection`](#treeconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="repositorypaginatedtreepath"></a>`path` | [`String`](#string) | The path to get the tree for. Default value is the root of the repository. |
| <a id="repositorypaginatedtreerecursive"></a>`recursive` | [`Boolean`](#boolean) | Used to get a recursive tree. Default is false. |
| <a id="repositorypaginatedtreeref"></a>`ref` | [`String`](#string) | The commit ref to get the tree for. Default value is HEAD. |
##### `Repository.tree`
Tree of the repository.

View file

@ -61,7 +61,6 @@ development:
adapter: postgresql
encoding: unicode
database: gitlabhq_development_ci
migrations_paths: db/ci_migrate
host: /path/to/gdk/postgresql
pool: 10
prepared_statements: false
@ -82,7 +81,6 @@ test: &test
adapter: postgresql
encoding: unicode
database: gitlabhq_test_ci
migrations_paths: db/ci_migrate
host: /path/to/gdk/postgresql
pool: 10
prepared_statements: false

View file

@ -133,7 +133,8 @@ To specify your pronouns:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25742) in GitLab 14.2.
You can add your name pronunciation to your GitLab account. This will be displayed in your profile, below your name.
You can add your name pronunciation to your GitLab account. This is displayed in your profile, below
your name.
To add your name pronunciation:

View file

@ -6,17 +6,14 @@ module Gitlab
class Context
attr_reader :connection
DEFAULT_SCHEMA_MIGRATIONS_PATH = "db/schema_migrations"
def initialize(connection)
@connection = connection
end
def schema_directory
@schema_directory ||=
if ActiveRecord::Base.configurations.primary?(database_name)
File.join(db_dir, 'schema_migrations')
else
File.join(db_dir, "#{database_name}_schema_migrations")
end
@schema_directory ||= Rails.root.join(database_schema_migrations_path).to_s
end
def versions_to_create
@ -32,8 +29,8 @@ module Gitlab
@database_name ||= @connection.pool.db_config.name
end
def db_dir
@db_dir ||= Rails.application.config.paths["db"].first
def database_schema_migrations_path
@connection.pool.db_config.configuration_hash[:schema_migrations_path] || DEFAULT_SCHEMA_MIGRATIONS_PATH
end
end
end

View file

@ -5,7 +5,7 @@ module Gitlab
module Docs
class Renderer
include Gitlab::Usage::Docs::Helper
DICTIONARY_PATH = Rails.root.join('doc', 'development', 'usage_ping')
DICTIONARY_PATH = Rails.root.join('doc', 'development', 'service_ping')
TEMPLATE_PATH = Rails.root.join('lib', 'gitlab', 'usage', 'docs', 'templates', 'default.md.haml')
def initialize(metrics_definitions)

View file

@ -3342,9 +3342,6 @@ msgstr ""
msgid "Allow public access to pipelines and job details, including output logs and artifacts."
msgstr ""
msgid "Allow rendering of PlantUML diagrams in Asciidoc documents."
msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
@ -27724,6 +27721,9 @@ msgstr ""
msgid "Rename/Move"
msgstr ""
msgid "Render diagrams in your documents using PlantUML."
msgstr ""
msgid "Renew subscription"
msgstr ""
@ -33016,6 +33016,9 @@ msgstr ""
msgid "The group_project_ids parameter is only allowed for a group"
msgstr ""
msgid "The hostname of your PlantUML server."
msgstr ""
msgid "The hostname of your Snowplow collector."
msgstr ""

View file

@ -57,7 +57,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.208.0",
"@gitlab/svgs": "1.209.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "32.0.0",
"@gitlab/visual-review-tools": "1.6.1",

View file

@ -42,6 +42,32 @@ RSpec.describe ProjectsController do
expect(response).not_to render_template('new')
end
end
context 'when user is an external user' do
let_it_be(:user) { create(:user, external: true) }
it 'responds with status 404' do
group.add_owner(user)
get :new, params: { namespace_id: group.id }
expect(response).to have_gitlab_http_status(:not_found)
expect(response).not_to render_template('new')
end
end
context 'when user is a group guest' do
let_it_be(:user) { create(:user) }
it 'responds with status 404' do
group.add_guest(user)
get :new, params: { namespace_id: group.id }
expect(response).to have_gitlab_http_status(:not_found)
expect(response).not_to render_template('new')
end
end
end
end
end

View file

@ -240,10 +240,14 @@ RSpec.describe 'Pipeline', :js do
end
end
it 'is possible to retry the success job' do
it 'is possible to retry the success job', :sidekiq_might_not_need_inline do
find('#ci-badge-build .ci-action-icon-container').click
wait_for_requests
expect(page).not_to have_content('Retry job')
within('.js-pipeline-header-container') do
expect(page).to have_selector('.js-ci-status-icon-running')
end
end
end
@ -282,10 +286,14 @@ RSpec.describe 'Pipeline', :js do
end
end
it 'is possible to retry the failed build' do
it 'is possible to retry the failed build', :sidekiq_might_not_need_inline do
find('#ci-badge-test .ci-action-icon-container').click
wait_for_requests
expect(page).not_to have_content('Retry job')
within('.js-pipeline-header-container') do
expect(page).to have_selector('.js-ci-status-icon-running')
end
end
it 'includes the failure reason' do
@ -308,10 +316,14 @@ RSpec.describe 'Pipeline', :js do
end
end
it 'is possible to play the manual job' do
it 'is possible to play the manual job', :sidekiq_might_not_need_inline do
find('#ci-badge-manual-build .ci-action-icon-container').click
wait_for_requests
expect(page).not_to have_content('Play job')
within('.js-pipeline-header-container') do
expect(page).to have_selector('.js-ci-status-icon-running')
end
end
end
@ -411,11 +423,18 @@ RSpec.describe 'Pipeline', :js do
context 'when retrying' do
before do
find('[data-testid="retryPipeline"]').click
wait_for_requests
end
it 'does not show a "Retry" button', :sidekiq_might_not_need_inline do
expect(page).not_to have_content('Retry')
end
it 'shows running status in pipeline header', :sidekiq_might_not_need_inline do
within('.js-pipeline-header-container') do
expect(page).to have_selector('.js-ci-status-icon-running')
end
end
end
end
@ -770,7 +789,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as created' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('pending')
end
@ -795,7 +814,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as pending' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('running')
end
@ -817,7 +836,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as created' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('pending')
end
@ -842,7 +861,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as pending' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('running')
end
@ -871,7 +890,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as waiting for resource' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('waiting')
end
@ -893,7 +912,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as waiting for resource' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('waiting')
end
@ -914,7 +933,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as pending' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('running')
end
@ -936,7 +955,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as pending' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('running')
end
@ -959,7 +978,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as waiting for resource' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('waiting')
end
@ -981,7 +1000,7 @@ RSpec.describe 'Pipeline', :js do
it 'shows deploy job as waiting for resource' do
subject
within('.pipeline-header-container') do
within('.js-pipeline-header-container') do
expect(page).to have_content('waiting')
end

View file

@ -9,7 +9,7 @@ exports[`ConanInstallation renders all the messages 1`] = `
<code-instruction-stub
copytext="Copy Conan Command"
instruction="foo/command"
instruction="conan install @gitlab-org/package-15 --remote=gitlab"
label="Conan Command"
trackingaction="copy_conan_command"
trackinglabel="code_instruction"
@ -23,7 +23,7 @@ exports[`ConanInstallation renders all the messages 1`] = `
<code-instruction-stub
copytext="Copy Conan Setup Command"
instruction="foo/setup"
instruction="conan remote add gitlab conanPath"
label="Add Conan Remote"
trackingaction="copy_conan_setup_command"
trackinglabel="code_instruction"

View file

@ -10,7 +10,7 @@ exports[`MavenInstallation groovy renders all the messages 1`] = `
<code-instruction-stub
class="gl-mb-5"
copytext="Copy Gradle Groovy DSL install command"
instruction="foo/gradle/groovy/install"
instruction="implementation 'appGroup:appName:appVersion'"
label="Gradle Groovy DSL install command"
trackingaction="copy_gradle_install_command"
trackinglabel="code_instruction"
@ -18,7 +18,9 @@ exports[`MavenInstallation groovy renders all the messages 1`] = `
<code-instruction-stub
copytext="Copy add Gradle Groovy DSL repository command"
instruction="foo/gradle/groovy/add/source"
instruction="maven {
url 'mavenPath'
}"
label="Add Gradle Groovy DSL repository command"
multiline="true"
trackingaction="copy_gradle_add_to_source_command"
@ -37,7 +39,7 @@ exports[`MavenInstallation kotlin renders all the messages 1`] = `
<code-instruction-stub
class="gl-mb-5"
copytext="Copy Gradle Kotlin DSL install command"
instruction="foo/gradle/kotlin/install"
instruction="implementation(\\"appGroup:appName:appVersion\\")"
label="Gradle Kotlin DSL install command"
trackingaction="copy_kotlin_install_command"
trackinglabel="code_instruction"
@ -45,7 +47,7 @@ exports[`MavenInstallation kotlin renders all the messages 1`] = `
<code-instruction-stub
copytext="Copy add Gradle Kotlin DSL repository command"
instruction="foo/gradle/kotlin/add/source"
instruction="maven(\\"mavenPath\\")"
label="Add Gradle Kotlin DSL repository command"
multiline="true"
trackingaction="copy_kotlin_add_to_source_command"
@ -69,7 +71,11 @@ exports[`MavenInstallation maven renders all the messages 1`] = `
<code-instruction-stub
copytext="Copy Maven XML"
instruction="foo/xml"
instruction="<dependency>
<groupId>appGroup</groupId>
<artifactId>appName</artifactId>
<version>appVersion</version>
</dependency>"
label=""
multiline="true"
trackingaction="copy_maven_xml"
@ -78,7 +84,7 @@ exports[`MavenInstallation maven renders all the messages 1`] = `
<code-instruction-stub
copytext="Copy Maven command"
instruction="foo/command"
instruction="mvn dependency:get -Dartifact=appGroup:appName:appVersion"
label="Maven Command"
trackingaction="copy_maven_command"
trackinglabel="code_instruction"
@ -98,7 +104,24 @@ exports[`MavenInstallation maven renders all the messages 1`] = `
<code-instruction-stub
copytext="Copy Maven registry XML"
instruction="foo/setup"
instruction="<repositories>
<repository>
<id>gitlab-maven</id>
<url>mavenPath</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
<url>mavenPath</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
<url>mavenPath</url>
</snapshotRepository>
</distributionManagement>"
label=""
multiline="true"
trackingaction="copy_maven_setup_xml"

View file

@ -1,44 +1,35 @@
import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { registryUrl as composerHelpPath } from 'jest/packages/details/mock_data';
import { composerPackage as packageEntity } from 'jest/packages/mock_data';
import { TrackingActions } from '~/packages/details/constants';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { packageData } from 'jest/packages_and_registries/package_registry/mock_data';
import ComposerInstallation from '~/packages_and_registries/package_registry/components/details/composer_installation.vue';
import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
import {
TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
PACKAGE_TYPE_COMPOSER,
} from '~/packages_and_registries/package_registry/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_COMPOSER };
describe('ComposerInstallation', () => {
let wrapper;
let store;
const composerRegistryIncludeStr = 'foo/registry';
const composerPackageIncludeStr = 'foo/package';
const createStore = (groupExists = true) => {
store = new Vuex.Store({
state: { packageEntity, composerHelpPath },
getters: {
composerRegistryInclude: () => composerRegistryIncludeStr,
composerPackageInclude: () => composerPackageIncludeStr,
groupExists: () => groupExists,
},
});
};
const findRootNode = () => wrapper.find('[data-testid="root-node"]');
const findRegistryInclude = () => wrapper.find('[data-testid="registry-include"]');
const findPackageInclude = () => wrapper.find('[data-testid="package-include"]');
const findHelpText = () => wrapper.find('[data-testid="help-text"]');
const findHelpLink = () => wrapper.find(GlLink);
const findRootNode = () => wrapper.findByTestId('root-node');
const findRegistryInclude = () => wrapper.findByTestId('registry-include');
const findPackageInclude = () => wrapper.findByTestId('package-include');
const findHelpText = () => wrapper.findByTestId('help-text');
const findHelpLink = () => wrapper.findComponent(GlLink);
const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
function createComponent() {
wrapper = shallowMount(ComposerInstallation, {
localVue,
store,
function createComponent(groupListUrl = 'groupListUrl') {
wrapper = shallowMountExtended(ComposerInstallation, {
provide: {
composerHelpPath: 'composerHelpPath',
composerConfigRepositoryName: 'composerConfigRepositoryName',
composerPath: 'composerPath',
groupListUrl,
},
propsData: { packageEntity },
stubs: {
GlSprintf,
},
@ -51,7 +42,6 @@ describe('ComposerInstallation', () => {
describe('install command switch', () => {
it('has the installation title component', () => {
createStore();
createComponent();
expect(findInstallationTitle().exists()).toBe(true);
@ -64,7 +54,6 @@ describe('ComposerInstallation', () => {
describe('registry include command', () => {
beforeEach(() => {
createStore();
createComponent();
});
@ -72,9 +61,9 @@ describe('ComposerInstallation', () => {
const registryIncludeCommand = findRegistryInclude();
expect(registryIncludeCommand.exists()).toBe(true);
expect(registryIncludeCommand.props()).toMatchObject({
instruction: composerRegistryIncludeStr,
instruction: `composer config repositories.composerConfigRepositoryName '{"type": "composer", "url": "composerPath"}'`,
copyText: 'Copy registry include',
trackingAction: TrackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
trackingAction: TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
});
});
@ -85,7 +74,6 @@ describe('ComposerInstallation', () => {
describe('package include command', () => {
beforeEach(() => {
createStore();
createComponent();
});
@ -93,9 +81,9 @@ describe('ComposerInstallation', () => {
const registryIncludeCommand = findPackageInclude();
expect(registryIncludeCommand.exists()).toBe(true);
expect(registryIncludeCommand.props()).toMatchObject({
instruction: composerPackageIncludeStr,
instruction: 'composer req @gitlab-org/package-15:1.0.0',
copyText: 'Copy require package include',
trackingAction: TrackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
trackingAction: TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
});
});
@ -108,7 +96,7 @@ describe('ComposerInstallation', () => {
'For more information on Composer packages in GitLab, see the documentation.',
);
expect(findHelpLink().attributes()).toMatchObject({
href: composerHelpPath,
href: 'composerHelpPath',
target: '_blank',
});
});
@ -116,15 +104,13 @@ describe('ComposerInstallation', () => {
describe('root node', () => {
it('is normally rendered', () => {
createStore();
createComponent();
expect(findRootNode().exists()).toBe(true);
});
it('is not rendered when the group does not exist', () => {
createStore(false);
createComponent();
createComponent('');
expect(findRootNode().exists()).toBe(false);
});

View file

@ -1,38 +1,27 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { registryUrl as conanPath } from 'jest/packages/details/mock_data';
import { conanPackage as packageEntity } from 'jest/packages/mock_data';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { packageData } from 'jest/packages_and_registries/package_registry/mock_data';
import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue';
import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
import { PACKAGE_TYPE_CONAN } from '~/packages_and_registries/package_registry/constants';
import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const packageEntity = { ...packageData(), packageType: PACKAGE_TYPE_CONAN };
describe('ConanInstallation', () => {
let wrapper;
const conanInstallationCommandStr = 'foo/command';
const conanSetupCommandStr = 'foo/setup';
const store = new Vuex.Store({
state: {
packageEntity,
conanPath,
},
getters: {
conanInstallationCommand: () => conanInstallationCommandStr,
conanSetupCommand: () => conanSetupCommandStr,
},
});
const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions);
const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
function createComponent() {
wrapper = shallowMount(ConanInstallation, {
localVue,
store,
wrapper = shallowMountExtended(ConanInstallation, {
provide: {
conanHelpPath: 'conanHelpPath',
conanPath: 'conanPath',
},
propsData: {
packageEntity,
},
});
}
@ -60,13 +49,17 @@ describe('ConanInstallation', () => {
describe('installation commands', () => {
it('renders the correct command', () => {
expect(findCodeInstructions().at(0).props('instruction')).toBe(conanInstallationCommandStr);
expect(findCodeInstructions().at(0).props('instruction')).toBe(
'conan install @gitlab-org/package-15 --remote=gitlab',
);
});
});
describe('setup commands', () => {
it('renders the correct command', () => {
expect(findCodeInstructions().at(1).props('instruction')).toBe(conanSetupCommandStr);
expect(findCodeInstructions().at(1).props('instruction')).toBe(
'conan remote add gitlab conanPath',
);
});
});
});

View file

@ -1,14 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import {
conanPackage,
mavenPackage,
npmPackage,
nugetPackage,
pypiPackage,
composerPackage,
terraformModule,
} from 'jest/packages/mock_data';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
import { packageData } from 'jest/packages_and_registries/package_registry/mock_data';
import ComposerInstallation from '~/packages_and_registries/package_registry/components/details/composer_installation.vue';
import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue';
import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue';
@ -17,6 +8,21 @@ import MavenInstallation from '~/packages_and_registries/package_registry/compon
import NpmInstallation from '~/packages_and_registries/package_registry/components/details/npm_installation.vue';
import NugetInstallation from '~/packages_and_registries/package_registry/components/details/nuget_installation.vue';
import PypiInstallation from '~/packages_and_registries/package_registry/components/details/pypi_installation.vue';
import {
PACKAGE_TYPE_CONAN,
PACKAGE_TYPE_MAVEN,
PACKAGE_TYPE_NPM,
PACKAGE_TYPE_NUGET,
PACKAGE_TYPE_PYPI,
PACKAGE_TYPE_COMPOSER,
} from '~/packages_and_registries/package_registry/constants';
const conanPackage = { ...packageData(), packageType: PACKAGE_TYPE_CONAN };
const mavenPackage = { ...packageData(), packageType: PACKAGE_TYPE_MAVEN };
const npmPackage = { ...packageData(), packageType: PACKAGE_TYPE_NPM };
const nugetPackage = { ...packageData(), packageType: PACKAGE_TYPE_NUGET };
const pypiPackage = { ...packageData(), packageType: PACKAGE_TYPE_PYPI };
const composerPackage = { ...packageData(), packageType: PACKAGE_TYPE_COMPOSER };
describe('InstallationCommands', () => {
let wrapper;
@ -33,7 +39,6 @@ describe('InstallationCommands', () => {
const nugetInstallation = () => wrapper.find(NugetInstallation);
const pypiInstallation = () => wrapper.find(PypiInstallation);
const composerInstallation = () => wrapper.find(ComposerInstallation);
const terraformInstallation = () => wrapper.findComponent(TerraformInstallation);
afterEach(() => {
wrapper.destroy();
@ -48,9 +53,8 @@ describe('InstallationCommands', () => {
${nugetPackage} | ${nugetInstallation}
${pypiPackage} | ${pypiInstallation}
${composerPackage} | ${composerInstallation}
${terraformModule} | ${terraformInstallation}
`('renders', ({ packageEntity, selector }) => {
it(`${packageEntity.package_type} instructions exist`, () => {
it(`${packageEntity.packageType} instructions exist`, () => {
createComponent({ packageEntity });
expect(selector()).toExist();

View file

@ -1,50 +1,79 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import { registryUrl as mavenPath } from 'jest/packages/details/mock_data';
import { mavenPackage as packageEntity } from 'jest/packages/mock_data';
import { TrackingActions } from '~/packages/details/constants';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
packageData,
mavenMetadata,
} from 'jest/packages_and_registries/package_registry/mock_data';
import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
import MavenInstallation from '~/packages_and_registries/package_registry/components/details/maven_installation.vue';
import {
TRACKING_ACTION_COPY_MAVEN_XML,
TRACKING_ACTION_COPY_MAVEN_COMMAND,
TRACKING_ACTION_COPY_MAVEN_SETUP,
TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND,
TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND,
TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
PACKAGE_TYPE_MAVEN,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('MavenInstallation', () => {
let wrapper;
const xmlCodeBlock = 'foo/xml';
const mavenCommandStr = 'foo/command';
const mavenSetupXml = 'foo/setup';
const gradleGroovyInstallCommandText = 'foo/gradle/groovy/install';
const gradleGroovyAddSourceCommandText = 'foo/gradle/groovy/add/source';
const gradleKotlinInstallCommandText = 'foo/gradle/kotlin/install';
const gradleKotlinAddSourceCommandText = 'foo/gradle/kotlin/add/source';
const packageEntity = {
...packageData(),
packageType: PACKAGE_TYPE_MAVEN,
metadata: mavenMetadata(),
};
const store = new Vuex.Store({
state: {
packageEntity,
mavenPath,
},
getters: {
mavenInstallationXml: () => xmlCodeBlock,
mavenInstallationCommand: () => mavenCommandStr,
mavenSetupXml: () => mavenSetupXml,
gradleGroovyInstalCommand: () => gradleGroovyInstallCommandText,
gradleGroovyAddSourceCommand: () => gradleGroovyAddSourceCommandText,
gradleKotlinInstalCommand: () => gradleKotlinInstallCommandText,
gradleKotlinAddSourceCommand: () => gradleKotlinAddSourceCommandText,
},
});
const mavenHelpPath = 'mavenHelpPath';
const mavenPath = 'mavenPath';
const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
const xmlCodeBlock = `<dependency>
<groupId>appGroup</groupId>
<artifactId>appName</artifactId>
<version>appVersion</version>
</dependency>`;
const mavenCommandStr = 'mvn dependency:get -Dartifact=appGroup:appName:appVersion';
const mavenSetupXml = `<repositories>
<repository>
<id>gitlab-maven</id>
<url>${mavenPath}</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
<url>${mavenPath}</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
<url>${mavenPath}</url>
</snapshotRepository>
</distributionManagement>`;
const gradleGroovyInstallCommandText = `implementation 'appGroup:appName:appVersion'`;
const gradleGroovyAddSourceCommandText = `maven {
url '${mavenPath}'
}`;
const gradleKotlinInstallCommandText = `implementation("appGroup:appName:appVersion")`;
const gradleKotlinAddSourceCommandText = `maven("${mavenPath}")`;
const findCodeInstructions = () => wrapper.findAllComponents(CodeInstructions);
const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
function createComponent({ data = {} } = {}) {
wrapper = shallowMount(MavenInstallation, {
localVue,
store,
wrapper = shallowMountExtended(MavenInstallation, {
provide: {
mavenHelpPath,
mavenPath,
},
propsData: {
packageEntity,
},
data() {
return data;
},
@ -98,7 +127,7 @@ describe('MavenInstallation', () => {
expect(findCodeInstructions().at(0).props()).toMatchObject({
instruction: xmlCodeBlock,
multiline: true,
trackingAction: TrackingActions.COPY_MAVEN_XML,
trackingAction: TRACKING_ACTION_COPY_MAVEN_XML,
});
});
@ -106,7 +135,7 @@ describe('MavenInstallation', () => {
expect(findCodeInstructions().at(1).props()).toMatchObject({
instruction: mavenCommandStr,
multiline: false,
trackingAction: TrackingActions.COPY_MAVEN_COMMAND,
trackingAction: TRACKING_ACTION_COPY_MAVEN_COMMAND,
});
});
});
@ -116,7 +145,7 @@ describe('MavenInstallation', () => {
expect(findCodeInstructions().at(2).props()).toMatchObject({
instruction: mavenSetupXml,
multiline: true,
trackingAction: TrackingActions.COPY_MAVEN_SETUP,
trackingAction: TRACKING_ACTION_COPY_MAVEN_SETUP,
});
});
});
@ -136,7 +165,7 @@ describe('MavenInstallation', () => {
expect(findCodeInstructions().at(0).props()).toMatchObject({
instruction: gradleGroovyInstallCommandText,
multiline: false,
trackingAction: TrackingActions.COPY_GRADLE_INSTALL_COMMAND,
trackingAction: TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND,
});
});
});
@ -146,7 +175,7 @@ describe('MavenInstallation', () => {
expect(findCodeInstructions().at(1).props()).toMatchObject({
instruction: gradleGroovyAddSourceCommandText,
multiline: true,
trackingAction: TrackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
trackingAction: TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
});
});
});
@ -166,7 +195,7 @@ describe('MavenInstallation', () => {
expect(findCodeInstructions().at(0).props()).toMatchObject({
instruction: gradleKotlinInstallCommandText,
multiline: false,
trackingAction: TrackingActions.COPY_KOTLIN_INSTALL_COMMAND,
trackingAction: TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND,
});
});
});
@ -176,7 +205,7 @@ describe('MavenInstallation', () => {
expect(findCodeInstructions().at(1).props()).toMatchObject({
instruction: gradleKotlinAddSourceCommandText,
multiline: true,
trackingAction: TrackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
trackingAction: TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
});
});
});

View file

@ -18,6 +18,8 @@ import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.
import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
import * as parsingUtils from '~/pipelines/components/parsing_utils';
import getPipelineHeaderData from '~/pipelines/graphql/queries/get_pipeline_header_data.query.graphql';
import { mockRunningPipelineHeaderData } from '../mock_data';
import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data';
const defaultProvide = {
@ -72,8 +74,10 @@ describe('Pipeline graph wrapper', () => {
} = {}) => {
const callouts = mapCallouts(calloutsList);
const getUserCalloutsHandler = jest.fn().mockResolvedValue(mockCalloutsResponse(callouts));
const getPipelineHeaderDataHandler = jest.fn().mockResolvedValue(mockRunningPipelineHeaderData);
const requestHandlers = [
[getPipelineHeaderData, getPipelineHeaderDataHandler],
[getPipelineDetails, getPipelineDetailsHandler],
[getUserCallouts, getUserCalloutsHandler],
];
@ -111,6 +115,11 @@ describe('Pipeline graph wrapper', () => {
createComponentWithApollo();
expect(getGraph().exists()).toBe(false);
});
it('skips querying headerPipeline', () => {
createComponentWithApollo();
expect(wrapper.vm.$apollo.queries.headerPipeline.skip).toBe(true);
});
});
describe('when data has loaded', () => {
@ -190,12 +199,15 @@ describe('Pipeline graph wrapper', () => {
describe('when refresh action is emitted', () => {
beforeEach(async () => {
createComponentWithApollo();
jest.spyOn(wrapper.vm.$apollo.queries.headerPipeline, 'refetch');
jest.spyOn(wrapper.vm.$apollo.queries.pipeline, 'refetch');
await wrapper.vm.$nextTick();
getGraph().vm.$emit('refreshPipelineGraph');
});
it('calls refetch', () => {
expect(wrapper.vm.$apollo.queries.headerPipeline.skip).toBe(false);
expect(wrapper.vm.$apollo.queries.headerPipeline.refetch).toHaveBeenCalled();
expect(wrapper.vm.$apollo.queries.pipeline.refetch).toHaveBeenCalled();
});
});

View file

@ -99,24 +99,6 @@ describe('Pipeline details header', () => {
);
});
describe('polling', () => {
it('is stopped when pipeline is finished', async () => {
wrapper = createComponent({ ...mockRunningPipelineHeader });
await wrapper.setData({
pipeline: { ...mockCancelledPipelineHeader },
});
expect(wrapper.vm.$apollo.queries.pipeline.stopPolling).toHaveBeenCalled();
});
it('is not stopped when pipeline is not finished', () => {
wrapper = createComponent();
expect(wrapper.vm.$apollo.queries.pipeline.stopPolling).not.toHaveBeenCalled();
});
});
describe('actions', () => {
describe('Retry action', () => {
beforeEach(() => {

View file

@ -127,6 +127,28 @@ export const mockSuccessfulPipelineHeader = {
},
};
export const mockRunningPipelineHeaderData = {
data: {
project: {
pipeline: {
...mockRunningPipelineHeader,
iid: '28',
user: {
name: 'Foo',
username: 'foobar',
webPath: '/foo',
email: 'foo@bar.com',
avatarUrl: 'link',
status: null,
__typename: 'UserCore',
},
__typename: 'Pipeline',
},
__typename: 'Project',
},
},
};
export const stageReply = {
name: 'deploy',
title: 'deploy: running',

View file

@ -0,0 +1,102 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::PaginatedTreeResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:repository) { project.repository }
specify do
expect(described_class).to have_nullable_graphql_type(Types::Tree::TreeType.connection_type)
end
describe '#resolve', :aggregate_failures do
subject { resolve_repository(args, opts) }
let(:args) { { ref: 'master' } }
let(:opts) { {} }
let(:start_cursor) { subject.start_cursor }
let(:end_cursor) { subject.end_cursor }
let(:items) { subject.items }
let(:entries) { items.first.entries }
it 'resolves to a collection with a tree object' do
expect(items.first).to be_an_instance_of(Tree)
expect(start_cursor).to be_nil
expect(end_cursor).to be_blank
expect(entries.count).to eq(repository.tree.entries.count)
end
context 'with recursive option' do
let(:args) { super().merge(recursive: true) }
it 'resolve to a recursive tree' do
expect(entries[4].path).to eq('files/html')
end
end
context 'with limited max_page_size' do
let(:opts) { { max_page_size: 5 } }
it 'resolves to a pagination collection with a tree object' do
expect(items.first).to be_an_instance_of(Tree)
expect(start_cursor).to be_nil
expect(end_cursor).to be_present
expect(entries.count).to eq(5)
end
end
context 'when repository does not exist' do
before do
allow(repository).to receive(:exists?).and_return(false)
end
it 'returns nil' do
is_expected.to be(nil)
end
end
describe 'Cursor pagination' do
context 'when cursor is invalid' do
let(:args) { super().merge(after: 'invalid') }
it { expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) }
end
it 'returns all tree entries during cursor pagination' do
cursor = nil
expected_entries = repository.tree.entries.map(&:path)
collected_entries = []
loop do
result = resolve_repository(args.merge(after: cursor), max_page_size: 10)
collected_entries += result.items.first.entries.map(&:path)
expect(result.start_cursor).to eq(cursor)
cursor = result.end_cursor
break if cursor.blank?
end
expect(collected_entries).to match_array(expected_entries)
end
end
end
def resolve_repository(args, opts = {})
field_options = described_class.field_options.merge(
owner: resolver_parent,
name: 'field_value'
).merge(opts)
field = ::Types::BaseField.new(**field_options)
resolve_field(field, repository, args: args, object_type: resolver_parent)
end
end

View file

@ -11,6 +11,8 @@ RSpec.describe GitlabSchema.types['Repository'] do
specify { expect(described_class).to have_graphql_field(:tree) }
specify { expect(described_class).to have_graphql_field(:paginated_tree, calls_gitaly?: true, max_page_size: 100) }
specify { expect(described_class).to have_graphql_field(:exists, calls_gitaly?: true, complexity: 2) }
specify { expect(described_class).to have_graphql_field(:blobs) }

View file

@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::SchemaMigrations::Context do
let(:connection) { ActiveRecord::Base.connection }
let(:connection_class) { ActiveRecord::Base }
let(:connection) { connection_class.connection }
let(:context) { described_class.new(connection) }
@ -12,13 +13,65 @@ RSpec.describe Gitlab::Database::SchemaMigrations::Context do
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/schema_migrations'))
end
context 'multiple databases' do
let(:connection) { Ci::CiDatabaseRecord.connection }
context 'CI database' do
let(:connection_class) { Ci::CiDatabaseRecord }
it 'returns a directory path that is database specific' do
skip_if_multiple_databases_not_setup
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations'))
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/schema_migrations'))
end
end
context 'multiple databases' do
let(:connection_class) do
Class.new(::ApplicationRecord) do
self.abstract_class = true
def self.name
'Gitlab::Database::SchemaMigrations::Context::TestConnection'
end
end
end
let(:configuration_overrides) { {} }
before do
connection_class.establish_connection(
ActiveRecord::Base
.connection_pool
.db_config
.configuration_hash
.merge(configuration_overrides)
)
end
after do
connection_class.remove_connection
end
context 'when `schema_migrations_path` is configured as string' do
let(:configuration_overrides) do
{ "schema_migrations_path" => "db/ci_schema_migrations" }
end
it 'returns a configured directory path that' do
skip_if_multiple_databases_not_setup
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations'))
end
end
context 'when `schema_migrations_path` is configured as symbol' do
let(:configuration_overrides) do
{ schema_migrations_path: "db/ci_schema_migrations" }
end
it 'returns a configured directory path that' do
skip_if_multiple_databases_not_setup
expect(context.schema_directory).to eq(File.join(Rails.root, 'db/ci_schema_migrations'))
end
end
end
end

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!('update_issuable_slas_where_issue_closed')
RSpec.describe UpdateIssuableSlasWhereIssueClosed, :migration do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:issues) { table(:issues) }
let(:issuable_slas) { table(:issuable_slas) }
let(:issue_params) { { title: 'title', project_id: project.id } }
let(:issue_closed_state) { 2 }
let!(:namespace) { namespaces.create!(name: 'foo', path: 'foo') }
let!(:project) { projects.create!(namespace_id: namespace.id) }
let!(:issue_open) { issues.create!(issue_params) }
let!(:issue_closed) { issues.create!(issue_params.merge(state_id: issue_closed_state)) }
let!(:issuable_sla_open_issue) { issuable_slas.create!(issue_id: issue_open.id, due_at: Time.now) }
let!(:issuable_sla_closed_issue) { issuable_slas.create!(issue_id: issue_closed.id, due_at: Time.now) }
it 'sets the issuable_closed attribute to false' do
expect(issuable_sla_open_issue.issuable_closed).to eq(false)
expect(issuable_sla_closed_issue.issuable_closed).to eq(false)
migrate!
expect(issuable_sla_open_issue.reload.issuable_closed).to eq(false)
expect(issuable_sla_closed_issue.reload.issuable_closed).to eq(true)
end
end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ConfirmSecurityBot, :migration do
let(:users) { table(:users) }
let(:user_type) { 8 }
context 'when bot is not created' do
it 'skips migration' do
migrate!
bot = users.find_by(user_type: user_type)
expect(bot).to be_nil
end
end
context 'when bot is confirmed' do
let(:bot) { table(:users).create!(user_type: user_type, confirmed_at: Time.current, projects_limit: 1) }
it 'skips migration' do
expect { migrate! }.not_to change { bot.reload.confirmed_at }
end
end
context 'when bot is not confirmed' do
let(:bot) { table(:users).create!(user_type: user_type, projects_limit: 1) }
it 'update confirmed_at' do
freeze_time do
expect { migrate! }.to change { bot.reload.confirmed_at }.from(nil).to(Time.current)
end
end
end
end

View file

@ -83,4 +83,26 @@ RSpec.describe 'getting a repository in a project' do
expect(graphql_data['project']['repository']).to be_nil
end
end
context 'when paginated tree requested' do
let(:fields) do
%(
paginatedTree {
nodes {
trees {
nodes {
path
}
}
}
}
)
end
it 'returns paginated tree' do
post_graphql(query, current_user: current_user)
expect(graphql_data['project']['repository']['paginatedTree']).to be_present
end
end
end

View file

@ -222,7 +222,7 @@ RSpec.describe Issues::CloseService do
it 'verifies the number of queries' do
recorded = ActiveRecord::QueryRecorder.new { close_issue }
expected_queries = 24
expected_queries = 25
expect(recorded.count).to be <= expected_queries
expect(recorded.cached_count).to eq(0)

View file

@ -898,10 +898,10 @@
stylelint-declaration-strict-value "1.7.7"
stylelint-scss "3.18.0"
"@gitlab/svgs@1.208.0":
version "1.208.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.208.0.tgz#e298b4d38ac38b7186000cc21bafebebe0766a83"
integrity sha512-dK2fXLCPOg0bchIqiNpVPY8RmEIpmR9wo/BHeLpf8NTcEjtAok87hJ+cwySbDvKeu10bSePsgl8NcST61GlNaA==
"@gitlab/svgs@1.209.0":
version "1.209.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.209.0.tgz#06020b74668df17b9bdaa33b561b4800ca34cc10"
integrity sha512-V6NaXDhu899tNCuU4+VepY7Rv2Ge3ViOctrUAS3NQtGcXV1THDhGAg6+OCFpgHr1fhmxYNwpxQ8OTh+HaPvtIA==
"@gitlab/tributejs@1.0.0":
version "1.0.0"