diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-23 12:09:05 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-23 12:09:05 +0000 |
commit | ab8eecd62cc11a31568b25304f5fd31c8b7f437f (patch) | |
tree | b73b765c3cea414112840fd8041c62f886d8ce53 /app | |
parent | 00a889ea7a115ebbda95a071dd630f93b79261e3 (diff) | |
download | gitlab-ce-ab8eecd62cc11a31568b25304f5fd31c8b7f437f.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
77 files changed, 1464 insertions, 187 deletions
diff --git a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue index 2accb46db60..751f3e9639d 100644 --- a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue +++ b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue @@ -41,7 +41,7 @@ export default { }; }, update(data) { - return data?.projects?.nodes.filter((project) => !project.repository.empty) ?? []; + return data?.projects?.nodes.filter((project) => !project.repository?.empty) ?? []; }, result() { this.initialProjectsLoading = false; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue new file mode 100644 index 00000000000..89add0a0d31 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue @@ -0,0 +1,94 @@ +<script> +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { PackageType } from '~/packages/shared/constants'; +import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; + +export default { + i18n: { + sourceText: s__('PackageRegistry|Source project located at %{link}'), + licenseText: s__('PackageRegistry|License information located at %{link}'), + recipeText: s__('PackageRegistry|Recipe: %{recipe}'), + appGroup: s__('PackageRegistry|App group: %{group}'), + appName: s__('PackageRegistry|App name: %{name}'), + }, + components: { + DetailsRow, + GlLink, + GlSprintf, + }, + props: { + packageEntity: { + type: Object, + required: true, + }, + }, + computed: { + showMetadata() { + const visibilityConditions = { + [PackageType.NUGET]: this.packageEntity.nuget_metadatum, + [PackageType.CONAN]: this.packageEntity.conan_metadatum, + [PackageType.MAVEN]: this.packageEntity.maven_metadatum, + }; + return visibilityConditions[this.packageEntity.package_type]; + }, + }, +}; +</script> + +<template> + <div v-if="showMetadata"> + <h3 class="gl-font-lg" data-testid="title">{{ __('Additional Metadata') }}</h3> + + <div class="gl-bg-gray-50 gl-inset-border-1-gray-100 gl-rounded-base" data-testid="main"> + <template v-if="packageEntity.nuget_metadatum"> + <details-row icon="project" padding="gl-p-4" dashed data-testid="nuget-source"> + <gl-sprintf :message="$options.i18n.sourceText"> + <template #link> + <gl-link :href="packageEntity.nuget_metadatum.project_url" target="_blank">{{ + packageEntity.nuget_metadatum.project_url + }}</gl-link> + </template> + </gl-sprintf> + </details-row> + <details-row icon="license" padding="gl-p-4" data-testid="nuget-license"> + <gl-sprintf :message="$options.i18n.licenseText"> + <template #link> + <gl-link :href="packageEntity.nuget_metadatum.license_url" target="_blank">{{ + packageEntity.nuget_metadatum.license_url + }}</gl-link> + </template> + </gl-sprintf> + </details-row> + </template> + + <details-row + v-else-if="packageEntity.conan_metadatum" + icon="information-o" + padding="gl-p-4" + data-testid="conan-recipe" + > + <gl-sprintf :message="$options.i18n.recipeText"> + <template #recipe>{{ packageEntity.name }}</template> + </gl-sprintf> + </details-row> + + <template v-else-if="packageEntity.maven_metadatum"> + <details-row icon="information-o" padding="gl-p-4" dashed data-testid="maven-app"> + <gl-sprintf :message="$options.i18n.appName"> + <template #name> + <strong>{{ packageEntity.maven_metadatum.app_name }}</strong> + </template> + </gl-sprintf> + </details-row> + <details-row icon="information-o" padding="gl-p-4" data-testid="maven-group"> + <gl-sprintf :message="$options.i18n.appGroup"> + <template #group> + <strong>{{ packageEntity.maven_metadatum.app_group }}</strong> + </template> + </gl-sprintf> + </details-row> + </template> + </div> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue new file mode 100644 index 00000000000..b3979a620f0 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue @@ -0,0 +1,65 @@ +<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 CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; + +export default { + name: 'ComposerInstallation', + components: { + InstallationTitle, + CodeInstruction, + GlLink, + GlSprintf, + }, + computed: { + ...mapState(['composerHelpPath']), + ...mapGetters(['composerRegistryInclude', 'composerPackageInclude', 'groupExists']), + }, + i18n: { + registryInclude: s__('PackageRegistry|Add composer registry'), + copyRegistryInclude: s__('PackageRegistry|Copy registry include'), + packageInclude: s__('PackageRegistry|Install package version'), + copyPackageInclude: s__('PackageRegistry|Copy require package include'), + infoLine: s__( + 'PackageRegistry|For more information on Composer packages in GitLab, %{linkStart}see the documentation.%{linkEnd}', + ), + }, + trackingActions: { ...TrackingActions }, + TrackingLabels, + installOptions: [{ value: 'composer', label: s__('PackageRegistry|Show Composer commands') }], +}; +</script> + +<template> + <div v-if="groupExists" data-testid="root-node"> + <installation-title package-type="composer" :options="$options.installOptions" /> + + <code-instruction + :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" + data-testid="registry-include" + /> + + <code-instruction + :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" + data-testid="package-include" + /> + <span data-testid="help-text"> + <gl-sprintf :message="$options.i18n.infoLine"> + <template #link="{ content }"> + <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </span> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue new file mode 100644 index 00000000000..59b446e46b5 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue @@ -0,0 +1,59 @@ +<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 CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; + +export default { + name: 'ConanInstallation', + components: { + InstallationTitle, + CodeInstruction, + GlLink, + GlSprintf, + }, + computed: { + ...mapState(['conanHelpPath']), + ...mapGetters(['conanInstallationCommand', 'conanSetupCommand']), + }, + i18n: { + helpText: s__( + 'PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}.', + ), + }, + trackingActions: { ...TrackingActions }, + TrackingLabels, + installOptions: [{ value: 'conan', label: s__('PackageRegistry|Show Conan commands') }], +}; +</script> + +<template> + <div> + <installation-title package-type="conan" :options="$options.installOptions" /> + + <code-instruction + :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" + /> + + <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3> + + <code-instruction + :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" + /> + <gl-sprintf :message="$options.i18n.helpText"> + <template #link="{ content }"> + <gl-link :href="conanHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/dependency_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/dependency_row.vue new file mode 100644 index 00000000000..1a2202b23c8 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/dependency_row.vue @@ -0,0 +1,35 @@ +<script> +export default { + name: 'DependencyRow', + props: { + dependency: { + type: Object, + required: true, + }, + }, + computed: { + showVersion() { + return Boolean(this.dependency.version_pattern); + }, + }, +}; +</script> + +<template> + <div class="gl-responsive-table-row"> + <div class="table-section section-50"> + <strong class="gl-text-body">{{ dependency.name }}</strong> + <span v-if="dependency.target_framework" data-testid="target-framework" + >({{ dependency.target_framework }})</span + > + </div> + + <div + v-if="showVersion" + class="table-section section-50 gl-display-flex gl-md-justify-content-end" + data-testid="version-pattern" + > + <span class="gl-text-body">{{ dependency.version_pattern }}</span> + </div> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/file_sha.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/file_sha.vue new file mode 100644 index 00000000000..a25839be7e1 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/file_sha.vue @@ -0,0 +1,41 @@ +<script> +import { s__ } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; + +export default { + name: 'FileSha', + components: { + DetailsRow, + ClipboardButton, + }, + props: { + sha: { + type: String, + required: true, + }, + title: { + type: String, + required: true, + }, + }, + i18n: { + copyButtonTitle: s__('PackageRegistry|Copy SHA'), + }, +}; +</script> + +<template> + <details-row dashed> + <div class="gl-px-4"> + {{ title }}: + {{ sha }} + <clipboard-button + :text="sha" + :title="$options.i18n.copyButtonTitle" + category="tertiary" + size="small" + /> + </div> + </details-row> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue new file mode 100644 index 00000000000..9ebfbbbf9e5 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue @@ -0,0 +1,55 @@ +<script> +import { PackageType, TERRAFORM_PACKAGE_TYPE } from '~/packages/shared/constants'; +import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue'; +import ComposerInstallation from './composer_installation.vue'; +import ConanInstallation from './conan_installation.vue'; +import MavenInstallation from './maven_installation.vue'; +import NpmInstallation from './npm_installation.vue'; +import NugetInstallation from './nuget_installation.vue'; +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, + }, + 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]; + }, + }, +}; +</script> + +<template> + <div v-if="installationComponent"> + <component + :is="installationComponent" + :name="packageEntity.name" + :registry-url="npmPath" + :help-url="npmHelpPath" + /> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_title.vue new file mode 100644 index 00000000000..43133bf7825 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_title.vue @@ -0,0 +1,38 @@ +<script> +import PersistedDropdownSelection from '~/vue_shared/components/registry/persisted_dropdown_selection.vue'; + +export default { + name: 'InstallationTitle', + components: { + PersistedDropdownSelection, + }, + props: { + packageType: { + type: String, + required: true, + }, + options: { + type: Array, + required: true, + }, + }, + computed: { + storageKey() { + return `package_${this.packageType}_installation_instructions`; + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center"> + <h3 class="gl-font-lg">{{ __('Installation') }}</h3> + <div> + <persisted-dropdown-selection + :storage-key="storageKey" + :options="options" + @change="$emit('change', $event)" + /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue new file mode 100644 index 00000000000..b035e557d21 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue @@ -0,0 +1,152 @@ +<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 CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; + +export default { + name: 'MavenInstallation', + components: { + InstallationTitle, + CodeInstruction, + GlLink, + GlSprintf, + }, + data() { + return { + instructionType: 'maven', + }; + }, + computed: { + ...mapState(['mavenHelpPath']), + ...mapGetters([ + 'mavenInstallationXml', + 'mavenInstallationCommand', + 'mavenSetupXml', + 'gradleGroovyInstalCommand', + 'gradleGroovyAddSourceCommand', + 'gradleKotlinInstalCommand', + 'gradleKotlinAddSourceCommand', + ]), + showMaven() { + return this.instructionType === 'maven'; + }, + showGroovy() { + return this.instructionType === 'groovy'; + }, + }, + i18n: { + xmlText: s__( + `PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block.`, + ), + setupText: s__( + `PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file.`, + ), + helpText: s__( + 'PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}.', + ), + }, + trackingActions: { ...TrackingActions }, + TrackingLabels, + installOptions: [ + { value: 'maven', label: s__('PackageRegistry|Maven XML') }, + { value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') }, + { value: 'kotlin', label: s__('PackageRegistry|Gradle Kotlin DSL') }, + ], +}; +</script> + +<template> + <div> + <installation-title + package-type="maven" + :options="$options.installOptions" + @change="instructionType = $event" + /> + + <template v-if="showMaven"> + <p> + <gl-sprintf :message="$options.i18n.xmlText"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + + <code-instruction + :instruction="mavenInstallationXml" + :copy-text="s__('PackageRegistry|Copy Maven XML')" + :tracking-action="$options.trackingActions.COPY_MAVEN_XML" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + multiline + /> + + <code-instruction + :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" + /> + + <h3 class="gl-font-lg">{{ s__('PackageRegistry|Registry setup') }}</h3> + <p> + <gl-sprintf :message="$options.i18n.setupText"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + <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" + multiline + /> + <gl-sprintf :message="$options.i18n.helpText"> + <template #link="{ content }"> + <gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </template> + <template v-else-if="showGroovy"> + <code-instruction + class="gl-mb-5" + :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" + /> + <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" + multiline + /> + </template> + <template v-else> + <code-instruction + class="gl-mb-5" + :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" + /> + <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" + multiline + /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue new file mode 100644 index 00000000000..c178d3e97e9 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue @@ -0,0 +1,103 @@ +<script> +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { mapGetters, mapState } from 'vuex'; +import { s__ } from '~/locale'; +import { NpmManager, TrackingActions, TrackingLabels } from '~/packages/details/constants'; +import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue'; +import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; + +export default { + name: 'NpmInstallation', + components: { + InstallationTitle, + CodeInstruction, + GlLink, + GlSprintf, + }, + data() { + return { + instructionType: 'npm', + }; + }, + computed: { + ...mapState(['npmHelpPath']), + ...mapGetters(['npmInstallationCommand', 'npmSetupCommand']), + npmCommand() { + return this.npmInstallationCommand(NpmManager.NPM); + }, + npmSetup() { + return this.npmSetupCommand(NpmManager.NPM); + }, + yarnCommand() { + return this.npmInstallationCommand(NpmManager.YARN); + }, + yarnSetupCommand() { + return this.npmSetupCommand(NpmManager.YARN); + }, + showNpm() { + return this.instructionType === 'npm'; + }, + }, + i18n: { + helpText: s__( + 'PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more.', + ), + }, + trackingActions: { ...TrackingActions }, + TrackingLabels, + installOptions: [ + { value: 'npm', label: s__('PackageRegistry|Show NPM commands') }, + { value: 'yarn', label: s__('PackageRegistry|Show Yarn commands') }, + ], +}; +</script> + +<template> + <div> + <installation-title + package-type="npm" + :options="$options.installOptions" + @change="instructionType = $event" + /> + + <code-instruction + v-if="showNpm" + :instruction="npmCommand" + :copy-text="s__('PackageRegistry|Copy npm command')" + :tracking-action="$options.trackingActions.COPY_NPM_INSTALL_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + + <code-instruction + v-else + :instruction="yarnCommand" + :copy-text="s__('PackageRegistry|Copy yarn command')" + :tracking-action="$options.trackingActions.COPY_YARN_INSTALL_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + + <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3> + + <code-instruction + v-if="showNpm" + :instruction="npmSetup" + :copy-text="s__('PackageRegistry|Copy npm setup command')" + :tracking-action="$options.trackingActions.COPY_NPM_SETUP_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + + <code-instruction + v-else + :instruction="yarnSetupCommand" + :copy-text="s__('PackageRegistry|Copy yarn setup command')" + :tracking-action="$options.trackingActions.COPY_YARN_SETUP_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + + <gl-sprintf :message="$options.i18n.helpText"> + <template #link="{ content }"> + <gl-link :href="npmHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue new file mode 100644 index 00000000000..84493790b4d --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue @@ -0,0 +1,58 @@ +<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 CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; + +export default { + name: 'NugetInstallation', + components: { + InstallationTitle, + CodeInstruction, + GlLink, + GlSprintf, + }, + computed: { + ...mapState(['nugetHelpPath']), + ...mapGetters(['nugetInstallationCommand', 'nugetSetupCommand']), + }, + i18n: { + helpText: s__( + 'PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}.', + ), + }, + trackingActions: { ...TrackingActions }, + TrackingLabels, + installOptions: [{ value: 'nuget', label: s__('PackageRegistry|Show Nuget commands') }], +}; +</script> + +<template> + <div> + <installation-title package-type="nuget" :options="$options.installOptions" /> + + <code-instruction + :label="s__('PackageRegistry|NuGet Command')" + :instruction="nugetInstallationCommand" + :copy-text="s__('PackageRegistry|Copy NuGet Command')" + :tracking-action="$options.trackingActions.COPY_NUGET_INSTALL_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3> + + <code-instruction + :label="s__('PackageRegistry|Add NuGet Source')" + :instruction="nugetSetupCommand" + :copy-text="s__('PackageRegistry|Copy NuGet Setup Command')" + :tracking-action="$options.trackingActions.COPY_NUGET_SETUP_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + <gl-sprintf :message="$options.i18n.helpText"> + <template #link="{ content }"> + <gl-link :href="nugetHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue new file mode 100644 index 00000000000..3c2876e902b --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue @@ -0,0 +1,165 @@ +<script> +import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui'; +import { last } from 'lodash'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; +import { __ } from '~/locale'; +import FileSha from '~/packages_and_registries/package_registry/components/details/file_sha.vue'; +import Tracking from '~/tracking'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; + +export default { + name: 'PackageFiles', + components: { + GlLink, + GlTable, + GlIcon, + GlDropdown, + GlDropdownItem, + GlButton, + FileIcon, + TimeAgoTooltip, + FileSha, + }, + mixins: [Tracking.mixin()], + props: { + packageFiles: { + type: Array, + required: false, + default: () => [], + }, + canDelete: { + type: Boolean, + default: false, + required: false, + }, + }, + computed: { + filesTableRows() { + return this.packageFiles.map((pf) => ({ + ...pf, + size: this.formatSize(pf.size), + pipeline: last(pf.pipelines), + })); + }, + showCommitColumn() { + return this.filesTableRows.some((row) => Boolean(row.pipeline?.id)); + }, + filesTableHeaderFields() { + return [ + { + key: 'name', + label: __('Name'), + }, + { + key: 'commit', + label: __('Commit'), + hide: !this.showCommitColumn, + }, + { + key: 'size', + label: __('Size'), + }, + { + key: 'created', + label: __('Created'), + class: 'gl-text-right', + }, + { + key: 'actions', + label: '', + hide: !this.canDelete, + class: 'gl-text-right', + tdClass: 'gl-w-4', + }, + ].filter((c) => !c.hide); + }, + }, + methods: { + formatSize(size) { + return numberToHumanSize(size); + }, + hasDetails(item) { + return item.file_sha256 || item.file_md5 || item.file_sha1; + }, + }, + i18n: { + deleteFile: __('Delete file'), + }, +}; +</script> + +<template> + <div> + <h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3> + <gl-table + :fields="filesTableHeaderFields" + :items="filesTableRows" + :tbody-tr-attr="{ 'data-testid': 'file-row' }" + > + <template #cell(name)="{ item, toggleDetails, detailsShowing }"> + <gl-button + v-if="hasDetails(item)" + :icon="detailsShowing ? 'angle-up' : 'angle-down'" + :aria-label="detailsShowing ? __('Collapse') : __('Expand')" + category="tertiary" + size="small" + @click="toggleDetails" + /> + <gl-link + :href="item.download_path" + class="gl-text-gray-500" + data-testid="download-link" + @click="$emit('download-file')" + > + <file-icon + :file-name="item.file_name" + css-classes="gl-relative file-icon" + class="gl-mr-1 gl-relative" + /> + <span>{{ item.file_name }}</span> + </gl-link> + </template> + + <template #cell(commit)="{ item }"> + <gl-link + v-if="item.pipeline && item.pipeline.project" + :href="item.pipeline.project.commit_url" + class="gl-text-gray-500" + data-testid="commit-link" + >{{ item.pipeline.git_commit_message }}</gl-link + > + </template> + + <template #cell(created)="{ item }"> + <time-ago-tooltip :time="item.created_at" /> + </template> + + <template #cell(actions)="{ item }"> + <gl-dropdown category="tertiary" right> + <template #button-content> + <gl-icon name="ellipsis_v" /> + </template> + <gl-dropdown-item data-testid="delete-file" @click="$emit('delete-file', item)"> + {{ $options.i18n.deleteFile }} + </gl-dropdown-item> + </gl-dropdown> + </template> + + <template #row-details="{ item }"> + <div + class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100" + > + <file-sha + v-if="item.file_sha256" + data-testid="sha-256" + title="SHA-256" + :sha="item.file_sha256" + /> + <file-sha v-if="item.file_md5" data-testid="md5" title="MD5" :sha="item.file_md5" /> + <file-sha v-if="item.file_sha1" data-testid="sha-1" title="SHA-1" :sha="item.file_sha1" /> + </div> + </template> + </gl-table> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue new file mode 100644 index 00000000000..0d7a73c12f1 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue @@ -0,0 +1,167 @@ +<script> +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { first } from 'lodash'; +import { truncateSha } from '~/lib/utils/text_utility'; +import { s__, n__ } from '~/locale'; +import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants'; +import HistoryItem from '~/vue_shared/components/registry/history_item.vue'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; + +export default { + name: 'PackageHistory', + i18n: { + createdOn: s__('PackageRegistry|%{name} version %{version} was first created %{datetime}'), + createdByCommitText: s__('PackageRegistry|Created by commit %{link} on branch %{branch}'), + createdByPipelineText: s__( + 'PackageRegistry|Built by pipeline %{link} triggered %{datetime} by %{author}', + ), + publishText: s__('PackageRegistry|Published to the %{project} Package Registry %{datetime}'), + combinedUpdateText: s__( + 'PackageRegistry|Package updated by commit %{link} on branch %{branch}, built by pipeline %{pipeline}, and published to the registry %{datetime}', + ), + archivedPipelineMessageSingular: s__('PackageRegistry|Package has %{number} archived update'), + archivedPipelineMessagePlural: s__('PackageRegistry|Package has %{number} archived updates'), + }, + components: { + GlLink, + GlSprintf, + HistoryItem, + TimeAgoTooltip, + }, + props: { + packageEntity: { + type: Object, + required: true, + }, + projectName: { + type: String, + required: true, + }, + }, + data() { + return { + showDescription: false, + }; + }, + computed: { + pipelines() { + return this.packageEntity.pipelines || []; + }, + firstPipeline() { + return first(this.pipelines); + }, + lastPipelines() { + return this.pipelines.slice(1).slice(-HISTORY_PIPELINES_LIMIT); + }, + showPipelinesInfo() { + return Boolean(this.firstPipeline?.id); + }, + archiviedLines() { + return Math.max(this.pipelines.length - HISTORY_PIPELINES_LIMIT - 1, 0); + }, + archivedPipelineMessage() { + return n__( + this.$options.i18n.archivedPipelineMessageSingular, + this.$options.i18n.archivedPipelineMessagePlural, + this.archiviedLines, + ); + }, + }, + methods: { + truncate(value) { + return truncateSha(value); + }, + }, +}; +</script> + +<template> + <div class="issuable-discussion"> + <h3 class="gl-font-lg" data-testid="title">{{ __('History') }}</h3> + <ul class="timeline main-notes-list notes gl-mb-4" data-testid="timeline"> + <history-item icon="clock" data-testid="created-on"> + <gl-sprintf :message="$options.i18n.createdOn"> + <template #name> + <strong>{{ packageEntity.name }}</strong> + </template> + <template #version> + <strong>{{ packageEntity.version }}</strong> + </template> + <template #datetime> + <time-ago-tooltip :time="packageEntity.created_at" /> + </template> + </gl-sprintf> + </history-item> + + <template v-if="showPipelinesInfo"> + <!-- FIRST PIPELINE BLOCK --> + <history-item icon="commit" data-testid="first-pipeline-commit"> + <gl-sprintf :message="$options.i18n.createdByCommitText"> + <template #link> + <gl-link :href="firstPipeline.project.commit_url" + >#{{ truncate(firstPipeline.sha) }}</gl-link + > + </template> + <template #branch> + <strong>{{ firstPipeline.ref }}</strong> + </template> + </gl-sprintf> + </history-item> + <history-item icon="pipeline" data-testid="first-pipeline-pipeline"> + <gl-sprintf :message="$options.i18n.createdByPipelineText"> + <template #link> + <gl-link :href="firstPipeline.project.pipeline_url">#{{ firstPipeline.id }}</gl-link> + </template> + <template #datetime> + <time-ago-tooltip :time="firstPipeline.created_at" /> + </template> + <template #author>{{ firstPipeline.user.name }}</template> + </gl-sprintf> + </history-item> + </template> + + <!-- PUBLISHED LINE --> + <history-item icon="package" data-testid="published"> + <gl-sprintf :message="$options.i18n.publishText"> + <template #project> + <strong>{{ projectName }}</strong> + </template> + <template #datetime> + <time-ago-tooltip :time="packageEntity.created_at" /> + </template> + </gl-sprintf> + </history-item> + + <history-item v-if="archiviedLines" icon="history" data-testid="archived"> + <gl-sprintf :message="archivedPipelineMessage"> + <template #number> + <strong>{{ archiviedLines }}</strong> + </template> + </gl-sprintf> + </history-item> + + <!-- PIPELINES LIST ENTRIES --> + <history-item + v-for="pipeline in lastPipelines" + :key="pipeline.id" + icon="pencil" + data-testid="pipeline-entry" + > + <gl-sprintf :message="$options.i18n.combinedUpdateText"> + <template #link> + <gl-link :href="pipeline.project.commit_url">#{{ truncate(pipeline.sha) }}</gl-link> + </template> + <template #branch> + <strong>{{ pipeline.ref }}</strong> + </template> + <template #pipeline> + <gl-link :href="pipeline.project.pipeline_url">#{{ pipeline.id }}</gl-link> + </template> + <template #datetime> + <time-ago-tooltip :time="pipeline.created_at" /> + </template> + </gl-sprintf> + </history-item> + </ul> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue new file mode 100644 index 00000000000..21eba44a720 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue @@ -0,0 +1,71 @@ +<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 CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; + +export default { + name: 'PyPiInstallation', + components: { + InstallationTitle, + CodeInstruction, + GlLink, + GlSprintf, + }, + computed: { + ...mapState(['pypiHelpPath']), + ...mapGetters(['pypiPipCommand', 'pypiSetupCommand']), + }, + i18n: { + setupText: s__( + `PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file.`, + ), + helpText: s__( + 'PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}.', + ), + }, + trackingActions: { ...TrackingActions }, + TrackingLabels, + installOptions: [{ value: 'pypi', label: s__('PackageRegistry|Show PyPi commands') }], +}; +</script> + +<template> + <div> + <installation-title package-type="pypi" :options="$options.installOptions" /> + + <code-instruction + :label="s__('PackageRegistry|Pip Command')" + :instruction="pypiPipCommand" + :copy-text="s__('PackageRegistry|Copy Pip command')" + data-testid="pip-command" + :tracking-action="$options.trackingActions.COPY_PIP_INSTALL_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + + <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3> + <p> + <gl-sprintf :message="$options.i18n.setupText"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + + <code-instruction + :instruction="pypiSetupCommand" + :copy-text="s__('PackageRegistry|Copy .pypirc content')" + data-testid="pypi-setup-content" + multiline + :tracking-action="$options.trackingActions.COPY_PYPI_SETUP_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + <gl-sprintf :message="$options.i18n.helpText"> + <template #link="{ content }"> + <gl-link :href="pypiHelpPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> +</template> diff --git a/app/assets/javascripts/pages/jira_connect/branches/new/index.js b/app/assets/javascripts/pages/jira_connect/branches/new/index.js new file mode 100644 index 00000000000..f8c3ec63f1f --- /dev/null +++ b/app/assets/javascripts/pages/jira_connect/branches/new/index.js @@ -0,0 +1,3 @@ +import initJiraConnectBranches from '~/jira_connect/branches'; + +initJiraConnectBranches(); diff --git a/app/assets/javascripts/security_configuration/components/auto_dev_ops_enabled_alert.vue b/app/assets/javascripts/security_configuration/components/auto_dev_ops_enabled_alert.vue new file mode 100644 index 00000000000..7192108f7c5 --- /dev/null +++ b/app/assets/javascripts/security_configuration/components/auto_dev_ops_enabled_alert.vue @@ -0,0 +1,30 @@ +<script> +import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +export default { + components: { + GlAlert, + GlLink, + GlSprintf, + }, + inject: ['autoDevopsHelpPagePath'], + i18n: { + body: s__( + 'AutoDevopsAlert|Security testing tools enabled with %{linkStart}Auto DevOps%{linkEnd}', + ), + }, +}; +</script> + +<template> + <gl-alert variant="success" @dismiss="$emit('dismiss')"> + <gl-sprintf :message="$options.i18n.body"> + <template #link="{ content }"> + <gl-link :href="autoDevopsHelpPagePath"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </gl-alert> +</template> diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index d80c67da8b1..fda18fb8009 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -305,3 +305,6 @@ export const featureToMutationMap = { }), }, }; + +export const AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY = + 'security_configuration_auto_devops_enabled_dismissed_projects'; diff --git a/app/assets/javascripts/security_configuration/components/redesigned_app.vue b/app/assets/javascripts/security_configuration/components/redesigned_app.vue index 915da378a4f..6c70a8c33db 100644 --- a/app/assets/javascripts/security_configuration/components/redesigned_app.vue +++ b/app/assets/javascripts/security_configuration/components/redesigned_app.vue @@ -1,8 +1,11 @@ <script> import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui'; import { __, s__ } from '~/locale'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; import AutoDevOpsAlert from './auto_dev_ops_alert.vue'; +import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue'; +import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants'; import FeatureCard from './feature_card.vue'; import SectionLayout from './section_layout.vue'; import UpgradeBanner from './upgrade_banner.vue'; @@ -25,16 +28,19 @@ export const i18n = { export default { i18n, components: { - GlTab, + AutoDevOpsAlert, + AutoDevOpsEnabledAlert, + FeatureCard, GlLink, - GlTabs, GlSprintf, - FeatureCard, + GlTab, + GlTabs, + LocalStorageSync, SectionLayout, UpgradeBanner, - AutoDevOpsAlert, UserCalloutDismisser, }, + inject: ['projectPath'], props: { augmentedSecurityFeatures: { type: Array, @@ -70,6 +76,11 @@ export default { default: '', }, }, + data() { + return { + autoDevopsEnabledAlertDismissedProjects: [], + }; + }, computed: { canUpgrade() { return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some( @@ -82,12 +93,32 @@ export default { shouldShowDevopsAlert() { return !this.autoDevopsEnabled && !this.gitlabCiPresent && this.canEnableAutoDevops; }, + shouldShowAutoDevopsEnabledAlert() { + return ( + this.autoDevopsEnabled && + !this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectPath) + ); + }, }, + methods: { + dismissAutoDevopsEnabledAlert() { + const dismissedProjects = new Set(this.autoDevopsEnabledAlertDismissedProjects); + dismissedProjects.add(this.projectPath); + this.autoDevopsEnabledAlertDismissedProjects = Array.from(dismissedProjects); + }, + }, + autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY, }; </script> <template> <article> + <local-storage-sync + v-model="autoDevopsEnabledAlertDismissedProjects" + :storage-key="$options.autoDevopsEnabledAlertStorageKey" + as-json + /> + <user-callout-dismisser v-if="shouldShowDevopsAlert" feature-name="security_configuration_devops_alert" @@ -105,8 +136,14 @@ export default { </template> </user-callout-dismisser> - <gl-tabs content-class="gl-pt-6"> + <gl-tabs content-class="gl-pt-0"> <gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting"> + <auto-dev-ops-enabled-alert + v-if="shouldShowAutoDevopsEnabledAlert" + class="gl-mt-3" + @dismiss="dismissAutoDevopsEnabledAlert" + /> + <section-layout :heading="$options.i18n.securityTesting"> <template #description> <p> diff --git a/app/assets/javascripts/security_configuration/components/section_layout.vue b/app/assets/javascripts/security_configuration/components/section_layout.vue index e351f9b9d8d..1fe8dd862a0 100644 --- a/app/assets/javascripts/security_configuration/components/section_layout.vue +++ b/app/assets/javascripts/security_configuration/components/section_layout.vue @@ -11,7 +11,7 @@ export default { </script> <template> - <div class="row gl-line-height-20"> + <div class="row gl-line-height-20 gl-pt-6"> <div class="col-lg-4"> <h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2> <slot name="description"></slot> diff --git a/app/controllers/groups/dependency_proxy_for_containers_controller.rb b/app/controllers/groups/dependency_proxy_for_containers_controller.rb index e2c104f88a4..e341b9d75e0 100644 --- a/app/controllers/groups/dependency_proxy_for_containers_controller.rb +++ b/app/controllers/groups/dependency_proxy_for_containers_controller.rb @@ -4,6 +4,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro include DependencyProxy::Auth include DependencyProxy::GroupAccess include SendFileUpload + include ::PackagesHelper # for event tracking before_action :ensure_token_granted! before_action :ensure_feature_enabled! @@ -22,6 +23,8 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro response.headers['Etag'] = "\"#{result[:manifest].digest}\"" content_type = result[:manifest].content_type + event_name = tracking_event_name(object_type: :manifest, from_cache: result[:from_cache]) + track_package_event(event_name, :dependency_proxy, namespace: group, user: current_user) send_upload( result[:manifest].file, proxy: true, @@ -38,6 +41,8 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro .new(group, image, token, params[:sha]).execute if result[:status] == :success + event_name = tracking_event_name(object_type: :blob, from_cache: result[:from_cache]) + track_package_event(event_name, :dependency_proxy, namespace: group, user: current_user) send_upload(result[:blob].file) else head result[:http_status] @@ -54,6 +59,13 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro params[:tag] end + def tracking_event_name(object_type:, from_cache:) + event_name = "pull_#{object_type}" + event_name = "#{event_name}_from_cache" if from_cache + + event_name + end + def dependency_proxy @dependency_proxy ||= group.dependency_proxy_setting || group.create_dependency_proxy_setting diff --git a/app/controllers/jira_connect/app_descriptor_controller.rb b/app/controllers/jira_connect/app_descriptor_controller.rb index 0de42ad2452..ec3327cc20e 100644 --- a/app/controllers/jira_connect/app_descriptor_controller.rb +++ b/app/controllers/jira_connect/app_descriptor_controller.rb @@ -44,27 +44,14 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController def modules modules = { - jiraDevelopmentTool: { - key: 'gitlab-development-tool', - application: { - value: 'GitLab' - }, - name: { - value: 'GitLab' - }, - url: HOME_URL, - logoUrl: logo_url, - capabilities: %w(branch commit pull_request) - }, postInstallPage: { key: 'gitlab-configuration', - name: { - value: 'GitLab Configuration' - }, + name: { value: 'GitLab Configuration' }, url: relative_to_base_path(jira_connect_subscriptions_path) } } + modules.merge!(development_tool_module) modules.merge!(build_information_module) modules.merge!(deployment_information_module) modules.merge!(feature_flag_module) @@ -76,6 +63,29 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController view_context.image_url('gitlab_logo.png') end + # See https://developer.atlassian.com/cloud/jira/software/modules/development-tool/ + def development_tool_module + actions = {} + + if JiraConnect::BranchesController.feature_enabled?(current_user) + actions[:createBranch] = { + templateUrl: new_jira_connect_branch_url + '?issue_key={issue.key}&issue_summary={issue.summary}' + } + end + + { + jiraDevelopmentTool: { + actions: actions, + key: 'gitlab-development-tool', + application: { value: 'GitLab' }, + name: { value: 'GitLab' }, + url: HOME_URL, + logoUrl: logo_url, + capabilities: %w(branch commit pull_request) + } + } + end + # See: https://developer.atlassian.com/cloud/jira/software/modules/deployment/ def deployment_information_module { @@ -92,9 +102,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController { jiraFeatureFlagInfoProvider: common_module_properties.merge( actions: {}, # TODO: create, link and list feature flags https://gitlab.com/gitlab-org/gitlab/-/issues/297386 - name: { - value: 'GitLab Feature Flags' - }, + name: { value: 'GitLab Feature Flags' }, key: 'gitlab-feature-flags' ) } diff --git a/app/controllers/jira_connect/branches_controller.rb b/app/controllers/jira_connect/branches_controller.rb new file mode 100644 index 00000000000..7d7faae62a5 --- /dev/null +++ b/app/controllers/jira_connect/branches_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# NOTE: This controller does not inherit from JiraConnect::ApplicationController +# because we don't receive a JWT for this action, so we rely on standard GitLab authentication. +class JiraConnect::BranchesController < ApplicationController + before_action :feature_enabled! + + feature_category :integrations + + def new + return unless params[:issue_key].present? + + @branch_name = Issue.to_branch_name( + params[:issue_key], + params[:issue_summary] + ) + end + + def self.feature_enabled?(user) + Feature.enabled?(:jira_connect_create_branch, user, default_enabled: :yaml) + end + + private + + def feature_enabled! + render_404 unless self.class.feature_enabled?(current_user) + end +end diff --git a/app/graphql/mutations/packages/destroy_file.rb b/app/graphql/mutations/packages/destroy_file.rb new file mode 100644 index 00000000000..35a486666d5 --- /dev/null +++ b/app/graphql/mutations/packages/destroy_file.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Mutations + module Packages + class DestroyFile < ::Mutations::BaseMutation + graphql_name 'DestroyPackageFile' + + authorize :destroy_package + + argument :id, + ::Types::GlobalIDType[::Packages::PackageFile], + required: true, + description: 'ID of the Package file.' + + def resolve(id:) + package_file = authorized_find!(id: id) + + if package_file.destroy + return { errors: [] } + end + + { errors: package_file.errors.full_messages } + end + + private + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Packages::PackageFile].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index df693fafbb9..812943e0a1e 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -107,6 +107,7 @@ module Types mount_mutation Mutations::Namespace::PackageSettings::Update mount_mutation Mutations::UserCallouts::Create mount_mutation Mutations::Packages::Destroy + mount_mutation Mutations::Packages::DestroyFile mount_mutation Mutations::Echo end end diff --git a/app/graphql/types/query_complexity_type.rb b/app/graphql/types/query_complexity_type.rb index 82809fac22f..3f58a15aef7 100644 --- a/app/graphql/types/query_complexity_type.rb +++ b/app/graphql/types/query_complexity_type.rb @@ -9,7 +9,7 @@ module Types alias_method :query, :object - field :limit, GraphQL::INT_TYPE, + field :limit, GraphQL::Types::Int, null: true, method: :max_complexity, see: { @@ -18,7 +18,7 @@ module Types }, description: 'GraphQL query complexity limit.' - field :score, GraphQL::INT_TYPE, + field :score, GraphQL::Types::Int, null: true, description: 'GraphQL query complexity score.' diff --git a/app/graphql/types/release_asset_link_shared_input_arguments.rb b/app/graphql/types/release_asset_link_shared_input_arguments.rb index 4aa247e47cc..37a6cdd55c9 100644 --- a/app/graphql/types/release_asset_link_shared_input_arguments.rb +++ b/app/graphql/types/release_asset_link_shared_input_arguments.rb @@ -5,15 +5,15 @@ module Types extend ActiveSupport::Concern included do - argument :name, GraphQL::STRING_TYPE, + argument :name, GraphQL::Types::String, required: true, description: 'Name of the asset link.' - argument :url, GraphQL::STRING_TYPE, + argument :url, GraphQL::Types::String, required: true, description: 'URL of the asset link.' - argument :direct_asset_path, GraphQL::STRING_TYPE, + argument :direct_asset_path, GraphQL::Types::String, required: false, as: :filepath, description: 'Relative path for a direct asset link.' diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb index 829e7e246db..02961f2f73f 100644 --- a/app/graphql/types/release_asset_link_type.rb +++ b/app/graphql/types/release_asset_link_type.rb @@ -7,20 +7,20 @@ module Types authorize :read_release - field :id, GraphQL::ID_TYPE, null: false, + field :id, GraphQL::Types::ID, null: false, description: 'ID of the link.' - field :name, GraphQL::STRING_TYPE, null: true, + field :name, GraphQL::Types::String, null: true, description: 'Name of the link.' - field :url, GraphQL::STRING_TYPE, null: true, + field :url, GraphQL::Types::String, null: true, description: 'URL of the link.' field :link_type, Types::ReleaseAssetLinkTypeEnum, null: true, description: 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`.' - field :external, GraphQL::BOOLEAN_TYPE, null: true, method: :external?, + field :external, GraphQL::Types::Boolean, null: true, method: :external?, description: 'Indicates the link points to an external resource.' - field :direct_asset_url, GraphQL::STRING_TYPE, null: true, + field :direct_asset_url, GraphQL::Types::String, null: true, description: 'Direct asset URL of the link.' - field :direct_asset_path, GraphQL::STRING_TYPE, null: true, method: :filepath, + field :direct_asset_path, GraphQL::Types::String, null: true, method: :filepath, description: 'Relative path for the direct asset link.' def direct_asset_url diff --git a/app/graphql/types/release_assets_type.rb b/app/graphql/types/release_assets_type.rb index d847d9842d5..ea6ee0b5fd9 100644 --- a/app/graphql/types/release_assets_type.rb +++ b/app/graphql/types/release_assets_type.rb @@ -11,7 +11,7 @@ module Types present_using ReleasePresenter - field :count, GraphQL::INT_TYPE, null: true, method: :assets_count, + field :count, GraphQL::Types::Int, null: true, method: :assets_count, description: 'Number of assets of the release.' field :links, Types::ReleaseAssetLinkType.connection_type, null: true, method: :sorted_links, description: 'Asset links of the release.' diff --git a/app/graphql/types/release_links_type.rb b/app/graphql/types/release_links_type.rb index a51b80e1e13..7830e29f3cd 100644 --- a/app/graphql/types/release_links_type.rb +++ b/app/graphql/types/release_links_type.rb @@ -10,20 +10,20 @@ module Types present_using ReleasePresenter - field :self_url, GraphQL::STRING_TYPE, null: true, + field :self_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the release.' - field :edit_url, GraphQL::STRING_TYPE, null: true, + field :edit_url, GraphQL::Types::String, null: true, description: "HTTP URL of the release's edit page.", authorize: :update_release - field :opened_merge_requests_url, GraphQL::STRING_TYPE, null: true, + field :opened_merge_requests_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the merge request page, filtered by this release and `state=open`.' - field :merged_merge_requests_url, GraphQL::STRING_TYPE, null: true, + field :merged_merge_requests_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the merge request page , filtered by this release and `state=merged`.' - field :closed_merge_requests_url, GraphQL::STRING_TYPE, null: true, + field :closed_merge_requests_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the merge request page , filtered by this release and `state=closed`.' - field :opened_issues_url, GraphQL::STRING_TYPE, null: true, + field :opened_issues_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the issues page, filtered by this release and `state=open`.' - field :closed_issues_url, GraphQL::STRING_TYPE, null: true, + field :closed_issues_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the issues page, filtered by this release and `state=closed`.' end end diff --git a/app/graphql/types/release_source_type.rb b/app/graphql/types/release_source_type.rb index 10fc202514d..fd29a69d72a 100644 --- a/app/graphql/types/release_source_type.rb +++ b/app/graphql/types/release_source_type.rb @@ -7,9 +7,9 @@ module Types authorize :download_code - field :format, GraphQL::STRING_TYPE, null: true, + field :format, GraphQL::Types::String, null: true, description: 'Format of the source.' - field :url, GraphQL::STRING_TYPE, null: true, + field :url, GraphQL::Types::String, null: true, description: 'Download URL of the source.' end end diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb index 81813a10a3e..5e8f00b2b0a 100644 --- a/app/graphql/types/release_type.rb +++ b/app/graphql/types/release_type.rb @@ -13,22 +13,22 @@ module Types present_using ReleasePresenter - field :tag_name, GraphQL::STRING_TYPE, null: true, method: :tag, + field :tag_name, GraphQL::Types::String, null: true, method: :tag, description: 'Name of the tag associated with the release.', authorize: :download_code - field :tag_path, GraphQL::STRING_TYPE, null: true, + field :tag_path, GraphQL::Types::String, null: true, description: 'Relative web path to the tag associated with the release.', authorize: :download_code - field :description, GraphQL::STRING_TYPE, null: true, + field :description, GraphQL::Types::String, null: true, description: 'Description (also known as "release notes") of the release.' markdown_field :description_html, null: true - field :name, GraphQL::STRING_TYPE, null: true, + field :name, GraphQL::Types::String, null: true, description: 'Name of the release.' field :created_at, Types::TimeType, null: true, description: 'Timestamp of when the release was created.' field :released_at, Types::TimeType, null: true, description: 'Timestamp of when the release was released.' - field :upcoming_release, GraphQL::BOOLEAN_TYPE, null: true, method: :upcoming_release?, + field :upcoming_release, GraphQL::Types::Boolean, null: true, method: :upcoming_release?, description: 'Indicates the release is an upcoming release.' field :assets, Types::ReleaseAssetsType, null: true, method: :itself, description: 'Assets of the release.' diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb index 8ed97d7e663..b6a1a91fd7a 100644 --- a/app/graphql/types/repository/blob_type.rb +++ b/app/graphql/types/repository/blob_type.rb @@ -8,67 +8,67 @@ module Types graphql_name 'RepositoryBlob' - field :id, GraphQL::ID_TYPE, null: false, + field :id, GraphQL::Types::ID, null: false, description: 'ID of the blob.' - field :oid, GraphQL::STRING_TYPE, null: false, method: :id, + field :oid, GraphQL::Types::String, null: false, method: :id, description: 'OID of the blob.' - field :path, GraphQL::STRING_TYPE, null: false, + field :path, GraphQL::Types::String, null: false, description: 'Path of the blob.' - field :name, GraphQL::STRING_TYPE, + field :name, GraphQL::Types::String, description: 'Blob name.', null: true - field :mode, type: GraphQL::STRING_TYPE, + field :mode, type: GraphQL::Types::String, description: 'Blob mode.', null: true - field :lfs_oid, GraphQL::STRING_TYPE, null: true, + field :lfs_oid, GraphQL::Types::String, null: true, calls_gitaly: true, description: 'LFS OID of the blob.' - field :web_path, GraphQL::STRING_TYPE, null: true, + field :web_path, GraphQL::Types::String, null: true, description: 'Web path of the blob.' - field :ide_edit_path, GraphQL::STRING_TYPE, null: true, + field :ide_edit_path, GraphQL::Types::String, null: true, description: 'Web path to edit this blob in the Web IDE.' - field :fork_and_edit_path, GraphQL::STRING_TYPE, null: true, + field :fork_and_edit_path, GraphQL::Types::String, null: true, description: 'Web path to edit this blob using a forked project.' - field :ide_fork_and_edit_path, GraphQL::STRING_TYPE, null: true, + field :ide_fork_and_edit_path, GraphQL::Types::String, null: true, description: 'Web path to edit this blob in the Web IDE using a forked project.' - field :size, GraphQL::INT_TYPE, null: true, + field :size, GraphQL::Types::Int, null: true, description: 'Size (in bytes) of the blob.' - field :raw_size, GraphQL::INT_TYPE, null: true, + field :raw_size, GraphQL::Types::Int, null: true, description: 'Size (in bytes) of the blob, or the blob target if stored externally.' - field :raw_blob, GraphQL::STRING_TYPE, null: true, method: :data, + field :raw_blob, GraphQL::Types::String, null: true, method: :data, description: 'The raw content of the blob.' - field :raw_text_blob, GraphQL::STRING_TYPE, null: true, method: :text_only_data, + field :raw_text_blob, GraphQL::Types::String, null: true, method: :text_only_data, description: 'The raw content of the blob, if the blob is text data.' - field :stored_externally, GraphQL::BOOLEAN_TYPE, null: true, method: :stored_externally?, + field :stored_externally, GraphQL::Types::Boolean, null: true, method: :stored_externally?, description: "Whether the blob's content is stored externally (for instance, in LFS)." - field :edit_blob_path, GraphQL::STRING_TYPE, null: true, + field :edit_blob_path, GraphQL::Types::String, null: true, description: 'Web path to edit the blob in the old-style editor.' - field :raw_path, GraphQL::STRING_TYPE, null: true, + field :raw_path, GraphQL::Types::String, null: true, description: 'Web path to download the raw blob.' - field :external_storage_url, GraphQL::STRING_TYPE, null: true, + field :external_storage_url, GraphQL::Types::String, null: true, description: 'Web path to download the raw blob via external storage, if enabled.' - field :replace_path, GraphQL::STRING_TYPE, null: true, + field :replace_path, GraphQL::Types::String, null: true, description: 'Web path to replace the blob content.' - field :file_type, GraphQL::STRING_TYPE, null: true, + field :file_type, GraphQL::Types::String, null: true, description: 'The expected format of the blob based on the extension.' field :simple_viewer, type: Types::BlobViewerType, @@ -79,12 +79,12 @@ module Types description: 'Blob content rich viewer.', null: true - field :plain_data, GraphQL::STRING_TYPE, + field :plain_data, GraphQL::Types::String, description: 'Blob plain highlighted data.', null: true, calls_gitaly: true - field :can_modify_blob, GraphQL::BOOLEAN_TYPE, null: true, method: :can_modify_blob?, + field :can_modify_blob, GraphQL::Types::Boolean, null: true, method: :can_modify_blob?, calls_gitaly: true, description: 'Whether the current user can modify the blob.' diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb index 9d896888fa7..466e3e45cb9 100644 --- a/app/graphql/types/repository_type.rb +++ b/app/graphql/types/repository_type.rb @@ -6,20 +6,20 @@ module Types authorize :download_code - field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, + field :root_ref, GraphQL::Types::String, null: true, calls_gitaly: true, description: 'Default branch of the repository.' - field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true, + field :empty, GraphQL::Types::Boolean, null: false, method: :empty?, calls_gitaly: true, description: 'Indicates repository has no visible content.' - field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists?, calls_gitaly: true, + field :exists, GraphQL::Types::Boolean, null: false, method: :exists?, calls_gitaly: true, 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 :blobs, Types::Repository::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true, description: 'Blobs contained within the repository' - field :branch_names, [GraphQL::STRING_TYPE], null: true, calls_gitaly: true, + field :branch_names, [GraphQL::Types::String], null: true, calls_gitaly: true, complexity: 170, description: 'Names of branches available in this repository that match the search pattern.', resolver: Resolvers::RepositoryBranchNamesResolver - field :disk_path, GraphQL::STRING_TYPE, + field :disk_path, GraphQL::Types::String, description: 'Shows a disk path of the repository.', null: true, authorize: :read_storage_disk_path diff --git a/app/graphql/types/resolvable_interface.rb b/app/graphql/types/resolvable_interface.rb index a9d745c2bc1..42784aa5e00 100644 --- a/app/graphql/types/resolvable_interface.rb +++ b/app/graphql/types/resolvable_interface.rb @@ -16,10 +16,10 @@ module Types Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.resolved_by_id).find end - field :resolved, GraphQL::BOOLEAN_TYPE, null: false, + field :resolved, GraphQL::Types::Boolean, null: false, description: 'Indicates if the object is resolved.', method: :resolved? - field :resolvable, GraphQL::BOOLEAN_TYPE, null: false, + field :resolvable, GraphQL::Types::Boolean, null: false, description: 'Indicates if the object can be resolved.', method: :resolvable? field :resolved_at, Types::TimeType, null: true, diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb index 7606bdbc46d..c345aea08bd 100644 --- a/app/graphql/types/snippet_type.rb +++ b/app/graphql/types/snippet_type.rb @@ -17,7 +17,7 @@ module Types description: 'ID of the snippet.', null: false - field :title, GraphQL::STRING_TYPE, + field :title, GraphQL::Types::String, description: 'Title of the snippet.', null: false @@ -33,11 +33,11 @@ module Types description: 'The owner of the snippet.', null: true - field :file_name, GraphQL::STRING_TYPE, + field :file_name, GraphQL::Types::String, description: 'File Name of the snippet.', null: true - field :description, GraphQL::STRING_TYPE, + field :description, GraphQL::Types::String, description: 'Description of the snippet.', null: true @@ -53,11 +53,11 @@ module Types description: 'Timestamp this snippet was updated.', null: false - field :web_url, type: GraphQL::STRING_TYPE, + field :web_url, type: GraphQL::Types::String, description: 'Web URL of the snippet.', null: false - field :raw_url, type: GraphQL::STRING_TYPE, + field :raw_url, type: GraphQL::Types::String, description: 'Raw URL of the snippet.', null: false @@ -67,12 +67,12 @@ module Types null: true, resolver: Resolvers::Snippets::BlobsResolver - field :ssh_url_to_repo, type: GraphQL::STRING_TYPE, + field :ssh_url_to_repo, type: GraphQL::Types::String, description: 'SSH URL to the snippet repository.', calls_gitaly: true, null: true - field :http_url_to_repo, type: GraphQL::STRING_TYPE, + field :http_url_to_repo, type: GraphQL::Types::String, description: 'HTTP URL to the snippet repository.', calls_gitaly: true, null: true diff --git a/app/graphql/types/snippets/blob_action_input_type.rb b/app/graphql/types/snippets/blob_action_input_type.rb index 13eade3dcc4..45dc4be8451 100644 --- a/app/graphql/types/snippets/blob_action_input_type.rb +++ b/app/graphql/types/snippets/blob_action_input_type.rb @@ -10,15 +10,15 @@ module Types description: 'Type of input action.', required: true - argument :previous_path, GraphQL::STRING_TYPE, + argument :previous_path, GraphQL::Types::String, description: 'Previous path of the snippet file.', required: false - argument :file_path, GraphQL::STRING_TYPE, + argument :file_path, GraphQL::Types::String, description: 'Path of the snippet file.', required: true - argument :content, GraphQL::STRING_TYPE, + argument :content, GraphQL::Types::String, description: 'Snippet file content.', required: false end diff --git a/app/graphql/types/snippets/blob_type.rb b/app/graphql/types/snippets/blob_type.rb index 1335838935e..d5da271d936 100644 --- a/app/graphql/types/snippets/blob_type.rb +++ b/app/graphql/types/snippets/blob_type.rb @@ -8,36 +8,36 @@ module Types description 'Represents the snippet blob' present_using SnippetBlobPresenter - field :rich_data, GraphQL::STRING_TYPE, + field :rich_data, GraphQL::Types::String, description: 'Blob highlighted data.', null: true - field :plain_data, GraphQL::STRING_TYPE, + field :plain_data, GraphQL::Types::String, description: 'Blob plain highlighted data.', null: true - field :raw_plain_data, GraphQL::STRING_TYPE, + field :raw_plain_data, GraphQL::Types::String, description: 'The raw content of the blob, if the blob is text data.', null: true - field :raw_path, GraphQL::STRING_TYPE, + field :raw_path, GraphQL::Types::String, description: 'Blob raw content endpoint path.', null: false - field :size, GraphQL::INT_TYPE, + field :size, GraphQL::Types::Int, description: 'Blob size.', null: false - field :binary, GraphQL::BOOLEAN_TYPE, + field :binary, GraphQL::Types::Boolean, description: 'Shows whether the blob is binary.', method: :binary?, null: false - field :name, GraphQL::STRING_TYPE, + field :name, GraphQL::Types::String, description: 'Blob name.', null: true - field :path, GraphQL::STRING_TYPE, + field :path, GraphQL::Types::String, description: 'Blob path.', null: true @@ -49,15 +49,15 @@ module Types description: 'Blob content rich viewer.', null: true - field :mode, type: GraphQL::STRING_TYPE, + field :mode, type: GraphQL::Types::String, description: 'Blob mode.', null: true - field :external_storage, type: GraphQL::STRING_TYPE, + field :external_storage, type: GraphQL::Types::String, description: 'Blob external storage.', null: true - field :rendered_as_text, type: GraphQL::BOOLEAN_TYPE, + field :rendered_as_text, type: GraphQL::Types::Boolean, description: 'Shows whether the blob is rendered as text.', method: :rendered_as_text?, null: false diff --git a/app/graphql/types/task_completion_status.rb b/app/graphql/types/task_completion_status.rb index 6837256f202..3aa19ff9413 100644 --- a/app/graphql/types/task_completion_status.rb +++ b/app/graphql/types/task_completion_status.rb @@ -8,9 +8,9 @@ module Types graphql_name 'TaskCompletionStatus' description 'Completion status of tasks' - field :count, GraphQL::INT_TYPE, null: false, + field :count, GraphQL::Types::Int, null: false, description: 'Number of total tasks.' - field :completed_count, GraphQL::INT_TYPE, null: false, + field :completed_count, GraphQL::Types::Int, null: false, description: 'Number of completed tasks.' end # rubocop: enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/terraform/state_type.rb b/app/graphql/types/terraform/state_type.rb index 9e2c47a9ece..cbd5aeaeef9 100644 --- a/app/graphql/types/terraform/state_type.rb +++ b/app/graphql/types/terraform/state_type.rb @@ -9,11 +9,11 @@ module Types connection_type_class(Types::CountableConnectionType) - field :id, GraphQL::ID_TYPE, + field :id, GraphQL::Types::ID, null: false, description: 'ID of the Terraform state.' - field :name, GraphQL::STRING_TYPE, + field :name, GraphQL::Types::String, null: false, description: 'Name of the Terraform state.' diff --git a/app/graphql/types/terraform/state_version_type.rb b/app/graphql/types/terraform/state_version_type.rb index 2cd2ec8dcda..545b3c0044d 100644 --- a/app/graphql/types/terraform/state_version_type.rb +++ b/app/graphql/types/terraform/state_version_type.rb @@ -9,7 +9,7 @@ module Types authorize :read_terraform_state - field :id, GraphQL::ID_TYPE, + field :id, GraphQL::Types::ID, null: false, description: 'ID of the Terraform state version.' @@ -17,7 +17,7 @@ module Types null: true, description: 'The user that created this version.' - field :download_path, GraphQL::STRING_TYPE, + field :download_path, GraphQL::Types::String, null: true, description: "URL for downloading the version's JSON file." @@ -25,7 +25,7 @@ module Types null: true, description: 'The job that created this version.' - field :serial, GraphQL::INT_TYPE, + field :serial, GraphQL::Types::Int, null: true, description: 'Serial number of the version.', method: :version diff --git a/app/graphql/types/timelog_type.rb b/app/graphql/types/timelog_type.rb index 925a522629e..fb5d4ca2dc0 100644 --- a/app/graphql/types/timelog_type.rb +++ b/app/graphql/types/timelog_type.rb @@ -12,7 +12,7 @@ module Types description: 'Timestamp of when the time tracked was spent at.' field :time_spent, - GraphQL::INT_TYPE, + GraphQL::Types::Int, null: false, description: 'The time spent displayed in seconds.' diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb index 3b983060de2..24c110ce09b 100644 --- a/app/graphql/types/todo_type.rb +++ b/app/graphql/types/todo_type.rb @@ -9,7 +9,7 @@ module Types authorize :read_todo - field :id, GraphQL::ID_TYPE, + field :id, GraphQL::Types::ID, description: 'ID of the to-do item.', null: false @@ -35,7 +35,7 @@ module Types description: 'Target type of the to-do item.', null: false - field :body, GraphQL::STRING_TYPE, + field :body, GraphQL::Types::String, description: 'Body of the to-do item.', null: false, calls_gitaly: true # TODO This is only true when `target_type` is `Commit`. See https://gitlab.com/gitlab-org/gitlab/issues/34757#note_234752665 diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb index d192c8d3c57..bb15d91a62f 100644 --- a/app/graphql/types/tree/blob_type.rb +++ b/app/graphql/types/tree/blob_type.rb @@ -10,14 +10,14 @@ module Types graphql_name 'Blob' - field :web_url, GraphQL::STRING_TYPE, null: true, + field :web_url, GraphQL::Types::String, null: true, description: 'Web URL of the blob.' - field :web_path, GraphQL::STRING_TYPE, null: true, + field :web_path, GraphQL::Types::String, null: true, description: 'Web path of the blob.' - field :lfs_oid, GraphQL::STRING_TYPE, null: true, + field :lfs_oid, GraphQL::Types::String, null: true, calls_gitaly: true, description: 'LFS ID of the blob.' - field :mode, GraphQL::STRING_TYPE, null: true, + field :mode, GraphQL::Types::String, null: true, description: 'Blob mode in numeric format.' def lfs_oid diff --git a/app/graphql/types/tree/entry_type.rb b/app/graphql/types/tree/entry_type.rb index c0150b77c55..1c612f91a5b 100644 --- a/app/graphql/types/tree/entry_type.rb +++ b/app/graphql/types/tree/entry_type.rb @@ -4,17 +4,17 @@ module Types module EntryType include Types::BaseInterface - field :id, GraphQL::ID_TYPE, null: false, + field :id, GraphQL::Types::ID, null: false, description: 'ID of the entry.' - field :sha, GraphQL::STRING_TYPE, null: false, + field :sha, GraphQL::Types::String, null: false, description: 'Last commit SHA for the entry.', method: :id - field :name, GraphQL::STRING_TYPE, null: false, + field :name, GraphQL::Types::String, null: false, description: 'Name of the entry.' field :type, Tree::TypeEnum, null: false, description: 'Type of tree entry.' - field :path, GraphQL::STRING_TYPE, null: false, + field :path, GraphQL::Types::String, null: false, description: 'Path of the entry.' - field :flat_path, GraphQL::STRING_TYPE, null: false, + field :flat_path, GraphQL::Types::String, null: false, description: 'Flat path of the entry.' end end diff --git a/app/graphql/types/tree/submodule_type.rb b/app/graphql/types/tree/submodule_type.rb index 519e866ebb0..05d8c1a951a 100644 --- a/app/graphql/types/tree/submodule_type.rb +++ b/app/graphql/types/tree/submodule_type.rb @@ -8,9 +8,9 @@ module Types graphql_name 'Submodule' - field :web_url, type: GraphQL::STRING_TYPE, null: true, + field :web_url, type: GraphQL::Types::String, null: true, description: 'Web URL for the sub-module.' - field :tree_url, type: GraphQL::STRING_TYPE, null: true, + field :tree_url, type: GraphQL::Types::String, null: true, description: 'Tree URL for the sub-module.' end # rubocop: enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/tree/tree_entry_type.rb b/app/graphql/types/tree/tree_entry_type.rb index daf4b5421fb..998b3617574 100644 --- a/app/graphql/types/tree/tree_entry_type.rb +++ b/app/graphql/types/tree/tree_entry_type.rb @@ -11,9 +11,9 @@ module Types graphql_name 'TreeEntry' description 'Represents a directory' - field :web_url, GraphQL::STRING_TYPE, null: true, + field :web_url, GraphQL::Types::String, null: true, description: 'Web URL for the tree entry (directory).' - field :web_path, GraphQL::STRING_TYPE, null: true, + field :web_path, GraphQL::Types::String, null: true, description: 'Web path for the tree entry (directory).' end # rubocop: enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb index e5abc033155..045f612691b 100644 --- a/app/graphql/types/user_interface.rb +++ b/app/graphql/types/user_interface.rb @@ -14,20 +14,20 @@ module Types method: :itself field :id, - type: GraphQL::ID_TYPE, + type: GraphQL::Types::ID, null: false, description: 'ID of the user.' field :bot, - type: GraphQL::BOOLEAN_TYPE, + type: GraphQL::Types::Boolean, null: false, description: 'Indicates if the user is a bot.', method: :bot? field :username, - type: GraphQL::STRING_TYPE, + type: GraphQL::Types::String, null: false, description: 'Username of the user. Unique within this instance of GitLab.' field :name, - type: GraphQL::STRING_TYPE, + type: GraphQL::Types::String, null: false, description: 'Human-readable name of the user.' field :state, @@ -35,24 +35,24 @@ module Types null: false, description: 'State of the user.' field :email, - type: GraphQL::STRING_TYPE, + type: GraphQL::Types::String, null: true, description: 'User email.', method: :public_email, deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' } field :public_email, - type: GraphQL::STRING_TYPE, + type: GraphQL::Types::String, null: true, description: "User's public email." field :avatar_url, - type: GraphQL::STRING_TYPE, + type: GraphQL::Types::String, null: true, description: "URL of the user's avatar." field :web_url, - type: GraphQL::STRING_TYPE, + type: GraphQL::Types::String, null: false, description: 'Web URL of the user.' field :web_path, - type: GraphQL::STRING_TYPE, + type: GraphQL::Types::String, null: false, description: 'Web path of the user.' field :todos, @@ -70,7 +70,7 @@ module Types null: true, description: 'User status.' field :location, - type: ::GraphQL::STRING_TYPE, + type: ::GraphQL::Types::String, null: true, description: 'The location of the user.' field :project_memberships, diff --git a/app/graphql/types/user_merge_request_interaction_type.rb b/app/graphql/types/user_merge_request_interaction_type.rb index b9ff489e0d6..ff6e83efbb2 100644 --- a/app/graphql/types/user_merge_request_interaction_type.rb +++ b/app/graphql/types/user_merge_request_interaction_type.rb @@ -13,14 +13,14 @@ module Types authorize :read_merge_request field :can_merge, - type: ::GraphQL::BOOLEAN_TYPE, + type: ::GraphQL::Types::Boolean, null: false, calls_gitaly: true, method: :can_merge?, description: 'Whether this user can merge this merge request.' field :can_update, - type: ::GraphQL::BOOLEAN_TYPE, + type: ::GraphQL::Types::Boolean, null: false, method: :can_update?, description: 'Whether this user can update this merge request.' @@ -31,13 +31,13 @@ module Types description: 'The state of the review by this user.' field :reviewed, - type: ::GraphQL::BOOLEAN_TYPE, + type: ::GraphQL::Types::Boolean, null: false, method: :reviewed?, description: 'Whether this user has provided a review for this merge request.' field :approved, - type: ::GraphQL::BOOLEAN_TYPE, + type: ::GraphQL::Types::Boolean, null: false, method: :approved?, description: 'Whether this user has approved this merge request.' diff --git a/app/graphql/types/user_status_type.rb b/app/graphql/types/user_status_type.rb index c1a736a3722..61abec0ba96 100644 --- a/app/graphql/types/user_status_type.rb +++ b/app/graphql/types/user_status_type.rb @@ -7,9 +7,9 @@ module Types markdown_field :message_html, null: true, description: 'HTML of the user status message' - field :message, GraphQL::STRING_TYPE, null: true, + field :message, GraphQL::Types::String, null: true, description: 'User status message.' - field :emoji, GraphQL::STRING_TYPE, null: true, + field :emoji, GraphQL::Types::String, null: true, description: 'String representation of emoji.' field :availability, Types::AvailabilityEnum, null: false, description: 'User availability status.' diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb index ceeb178e9c2..13b24e099c9 100644 --- a/app/mailers/previews/notify_preview.rb +++ b/app/mailers/previews/notify_preview.rb @@ -246,7 +246,7 @@ class NotifyPreview < ActionMailer::Preview def cleanup email = nil - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases email = yield raise ActiveRecord::Rollback end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 02ebcfa7c0a..7d34198653a 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -627,7 +627,7 @@ class ApplicationSetting < ApplicationRecord # prevent this from happening, we do a sanity check that the # primary key constraint is present before inserting a new entry. def self.check_schema! - return if ActiveRecord::Base.connection.primary_key(self.table_name).present? + return if connection.primary_key(self.table_name).present? raise "The `#{self.table_name}` table is missing a primary key constraint in the database schema" end diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb index f114094d69c..a54de3c82d1 100644 --- a/app/models/internal_id.rb +++ b/app/models/internal_id.rb @@ -83,7 +83,7 @@ class InternalId < ApplicationRecord self.internal_id_transactions_total.increment( operation: operation, usage: usage.to_s, - in_transaction: ActiveRecord::Base.connection.transaction_open?.to_s + in_transaction: ActiveRecord::Base.connection.transaction_open?.to_s # rubocop: disable Database/MultipleDatabases ) end @@ -317,7 +317,7 @@ class InternalId < ApplicationRecord stmt.set(arel_table[:last_value] => new_value) stmt.wheres = InternalId.filter_by(scope, usage).arel.constraints - ActiveRecord::Base.connection.insert(stmt, 'Update InternalId', 'last_value') + ActiveRecord::Base.connection.insert(stmt, 'Update InternalId', 'last_value') # rubocop: disable Database/MultipleDatabases end def create_record!(subject, scope, usage, init) diff --git a/app/models/issue.rb b/app/models/issue.rb index d1ef84a8a97..f72e8fdb138 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -317,6 +317,21 @@ class Issue < ApplicationRecord ) end + def self.to_branch_name(*args) + branch_name = args.map(&:to_s).each_with_index.map do |arg, i| + arg.parameterize(preserve_case: i == 0).presence + end.compact.join('-') + + if branch_name.length > 100 + truncated_string = branch_name[0, 100] + # Delete everything dangling after the last hyphen so as not to risk + # existence of unintended words in the branch name due to mid-word split. + branch_name = truncated_string.sub(/-[^-]*\Z/, '') + end + + branch_name + end + # Temporary disable moving null elements because of performance problems # For more information check https://gitlab.com/gitlab-com/gl-infra/production/-/issues/4321 def check_repositioning_allowed! @@ -384,16 +399,7 @@ class Issue < ApplicationRecord if self.confidential? "#{iid}-confidential-issue" else - branch_name = "#{iid}-#{title.parameterize}" - - if branch_name.length > 100 - truncated_string = branch_name[0, 100] - # Delete everything dangling after the last hyphen so as not to risk - # existence of unintended words in the branch name due to mid-word split. - branch_name = truncated_string[0, truncated_string.rindex("-")] - end - - branch_name + self.class.to_branch_name(iid, title) end end diff --git a/app/models/packages/event.rb b/app/models/packages/event.rb index a1eb7120117..bb2c33594e5 100644 --- a/app/models/packages/event.rb +++ b/app/models/packages/event.rb @@ -4,7 +4,7 @@ class Packages::Event < ApplicationRecord belongs_to :package, optional: true UNIQUE_EVENTS_ALLOWED = %i[push_package delete_package pull_package pull_symbol_package push_symbol_package].freeze - EVENT_SCOPES = ::Packages::Package.package_types.merge(container: 1000, tag: 1001).freeze + EVENT_SCOPES = ::Packages::Package.package_types.merge(container: 1000, tag: 1001, dependency_proxy: 1002).freeze EVENT_PREFIX = "i_package" @@ -23,7 +23,11 @@ class Packages::Event < ApplicationRecord list_tags: 9, cli_metadata: 10, pull_symbol_package: 11, - push_symbol_package: 12 + push_symbol_package: 12, + pull_manifest: 13, + pull_manifest_from_cache: 14, + pull_blob: 15, + pull_blob_from_cache: 16 } enum originator_type: { user: 0, deploy_token: 1, guest: 2 } diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb index 142eebca2e3..e756e8c14d8 100644 --- a/app/services/auto_merge/base_service.rb +++ b/app/services/auto_merge/base_service.rb @@ -6,7 +6,7 @@ module AutoMerge include MergeRequests::AssignsMergeParams def execute(merge_request) - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases register_auto_merge_parameters!(merge_request) yield if block_given? end @@ -29,7 +29,7 @@ module AutoMerge end def cancel(merge_request) - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases clear_auto_merge_parameters!(merge_request) yield if block_given? end @@ -41,7 +41,7 @@ module AutoMerge end def abort(merge_request, reason) - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases clear_auto_merge_parameters!(merge_request) yield if block_given? end diff --git a/app/services/ci/delete_unit_tests_service.rb b/app/services/ci/delete_unit_tests_service.rb index 28f96351175..230661a107d 100644 --- a/app/services/ci/delete_unit_tests_service.rb +++ b/app/services/ci/delete_unit_tests_service.rb @@ -23,7 +23,7 @@ module Ci def delete_batch!(klass) deleted = 0 - ActiveRecord::Base.transaction do + klass.transaction do ids = klass.deletable.lock('FOR UPDATE SKIP LOCKED').limit(BATCH_SIZE).pluck(:id) break if ids.empty? diff --git a/app/services/ci/unlock_artifacts_service.rb b/app/services/ci/unlock_artifacts_service.rb index 07faf90dd6d..7c169cb8395 100644 --- a/app/services/ci/unlock_artifacts_service.rb +++ b/app/services/ci/unlock_artifacts_service.rb @@ -17,7 +17,7 @@ module Ci SQL loop do - break if ActiveRecord::Base.connection.exec_query(query).empty? + break if Ci::Pipeline.connection.exec_query(query).empty? end end diff --git a/app/services/dependency_proxy/find_or_create_blob_service.rb b/app/services/dependency_proxy/find_or_create_blob_service.rb index bd06f9d7628..f3dbf31dcdb 100644 --- a/app/services/dependency_proxy/find_or_create_blob_service.rb +++ b/app/services/dependency_proxy/find_or_create_blob_service.rb @@ -10,10 +10,12 @@ module DependencyProxy end def execute + from_cache = true file_name = @blob_sha.sub('sha256:', '') + '.gz' blob = @group.dependency_proxy_blobs.find_or_build(file_name) unless blob.persisted? + from_cache = false result = DependencyProxy::DownloadBlobService .new(@image, @blob_sha, @token).execute @@ -28,7 +30,7 @@ module DependencyProxy blob.save! end - success(blob: blob) + success(blob: blob, from_cache: from_cache) end private diff --git a/app/services/dependency_proxy/find_or_create_manifest_service.rb b/app/services/dependency_proxy/find_or_create_manifest_service.rb index ee608d715aa..0eb990ab7f8 100644 --- a/app/services/dependency_proxy/find_or_create_manifest_service.rb +++ b/app/services/dependency_proxy/find_or_create_manifest_service.rb @@ -17,10 +17,10 @@ module DependencyProxy head_result = DependencyProxy::HeadManifestService.new(@image, @tag, @token).execute - return success(manifest: @manifest) if cached_manifest_matches?(head_result) + return success(manifest: @manifest, from_cache: true) if cached_manifest_matches?(head_result) pull_new_manifest - respond + respond(from_cache: false) rescue Timeout::Error, *Gitlab::HTTP::HTTP_ERRORS respond end @@ -44,9 +44,9 @@ module DependencyProxy @manifest && @manifest.digest == head_result[:digest] && @manifest.content_type == head_result[:content_type] end - def respond + def respond(from_cache: true) if @manifest.persisted? - success(manifest: @manifest) + success(manifest: @manifest, from_cache: from_cache) else error('Failed to download the manifest from the external registry', 503) end diff --git a/app/services/deployments/update_environment_service.rb b/app/services/deployments/update_environment_service.rb index 6f85779c285..83c37257297 100644 --- a/app/services/deployments/update_environment_service.rb +++ b/app/services/deployments/update_environment_service.rb @@ -26,7 +26,7 @@ module Deployments end def update_environment(deployment) - ActiveRecord::Base.transaction do + ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases # Renew attributes at update renew_external_url renew_auto_stop_in diff --git a/app/services/design_management/copy_design_collection/copy_service.rb b/app/services/design_management/copy_design_collection/copy_service.rb index b40f6a81174..0bec6de1dee 100644 --- a/app/services/design_management/copy_design_collection/copy_service.rb +++ b/app/services/design_management/copy_design_collection/copy_service.rb @@ -36,7 +36,7 @@ module DesignManagement with_temporary_branch do copy_commits! - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do design_ids = copy_designs! version_ids = copy_versions! copy_actions!(design_ids, version_ids) diff --git a/app/services/feature_flags/create_service.rb b/app/services/feature_flags/create_service.rb index 5c87af561d5..5111f914447 100644 --- a/app/services/feature_flags/create_service.rb +++ b/app/services/feature_flags/create_service.rb @@ -6,7 +6,7 @@ module FeatureFlags return error('Access Denied', 403) unless can_create? return error('Version is invalid', :bad_request) unless valid_version? - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do feature_flag = project.operations_feature_flags.new(params) if feature_flag.save diff --git a/app/services/feature_flags/destroy_service.rb b/app/services/feature_flags/destroy_service.rb index b131a349fc7..986fe004db6 100644 --- a/app/services/feature_flags/destroy_service.rb +++ b/app/services/feature_flags/destroy_service.rb @@ -11,7 +11,7 @@ module FeatureFlags def destroy_feature_flag(feature_flag) return error('Access Denied', 403) unless can_destroy?(feature_flag) - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do if feature_flag.destroy save_audit_event(audit_event(feature_flag)) diff --git a/app/services/feature_flags/update_service.rb b/app/services/feature_flags/update_service.rb index f5ab6f4005b..01e4f661d75 100644 --- a/app/services/feature_flags/update_service.rb +++ b/app/services/feature_flags/update_service.rb @@ -12,7 +12,7 @@ module FeatureFlags return error('Access Denied', 403) unless can_update?(feature_flag) return error('Not Found', 404) unless valid_user_list_ids?(feature_flag, user_list_ids(params)) - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do feature_flag.assign_attributes(params) feature_flag.strategies.each do |strategy| diff --git a/app/services/issuable/clone/base_service.rb b/app/services/issuable/clone/base_service.rb index 574fe85b466..1d2c5c06d1b 100644 --- a/app/services/issuable/clone/base_service.rb +++ b/app/services/issuable/clone/base_service.rb @@ -14,7 +14,7 @@ module Issuable # Using transaction because of a high resources footprint # on rewriting notes (unfolding references) # - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do @new_entity = create_new_entity update_new_entity diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb index aedd0c377c6..38050708fc5 100644 --- a/app/services/issuable/common_system_notes_service.rb +++ b/app/services/issuable/common_system_notes_service.rb @@ -9,7 +9,7 @@ module Issuable # We disable touch so that created system notes do not update # the noteable's updated_at field - ActiveRecord::Base.no_touching do + ApplicationRecord.no_touching do if is_update if issuable.previous_changes.include?('title') create_title_change_note(issuable.previous_changes['title'].first) diff --git a/app/services/issuable/destroy_label_links_service.rb b/app/services/issuable/destroy_label_links_service.rb index 6fff9b5e8d2..49000d90c1f 100644 --- a/app/services/issuable/destroy_label_links_service.rb +++ b/app/services/issuable/destroy_label_links_service.rb @@ -22,7 +22,7 @@ module Issuable SQL loop do - result = ActiveRecord::Base.connection.execute(delete_query) + result = LabelLink.connection.execute(delete_query) break if result.cmd_tuples == 0 end diff --git a/app/services/packages/create_dependency_service.rb b/app/services/packages/create_dependency_service.rb index 2999885d55d..f2b30c9d816 100644 --- a/app/services/packages/create_dependency_service.rb +++ b/app/services/packages/create_dependency_service.rb @@ -27,7 +27,7 @@ module Packages dependencies_to_insert = names_and_version_patterns.reject { |k, _| k.in?(existing_names) } end - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do inserted_ids = bulk_insert_package_dependencies(dependencies_to_insert) bulk_insert_package_dependency_links(type, (existing_ids + inserted_ids)) end diff --git a/app/services/packages/go/create_package_service.rb b/app/services/packages/go/create_package_service.rb index 4e8b8ef8d6b..2a6eeff402e 100644 --- a/app/services/packages/go/create_package_service.rb +++ b/app/services/packages/go/create_package_service.rb @@ -23,7 +23,7 @@ module Packages files[:mod] = prepare_file(version, :mod, version.gomod) files[:zip] = prepare_file(version, :zip, version.archive.string) - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do # create new package and files package = create_package files.each { |type, (file, digests)| create_file(package, type, file, digests) } diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb index 22396eb7687..1d5d9c38432 100644 --- a/app/services/packages/npm/create_package_service.rb +++ b/app/services/packages/npm/create_package_service.rb @@ -9,7 +9,7 @@ module Packages return error('Package already exists.', 403) if current_package_exists? return error('File is too large.', 400) if file_size_exceeded? - ActiveRecord::Base.transaction { create_npm_package! } + ApplicationRecord.transaction { create_npm_package! } end private diff --git a/app/services/packages/terraform_module/create_package_service.rb b/app/services/packages/terraform_module/create_package_service.rb index fc376c70b00..03f749edfa8 100644 --- a/app/services/packages/terraform_module/create_package_service.rb +++ b/app/services/packages/terraform_module/create_package_service.rb @@ -11,7 +11,7 @@ module Packages return error('Package version already exists.', 403) if current_package_version_exists? return error('File is too large.', 400) if file_size_exceeded? - ActiveRecord::Base.transaction { create_terraform_module_package! } + ApplicationRecord.transaction { create_terraform_module_package! } end private diff --git a/app/services/projects/cleanup_service.rb b/app/services/projects/cleanup_service.rb index 5eafa5f9b29..75be3425029 100644 --- a/app/services/projects/cleanup_service.rb +++ b/app/services/projects/cleanup_service.rb @@ -65,7 +65,7 @@ module Projects def cleanup_diffs(response) old_commit_shas = extract_old_commit_shas(response.entries) - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do cleanup_merge_request_diffs(old_commit_shas) cleanup_note_diff_files(old_commit_shas) end diff --git a/app/services/projects/fetch_statistics_increment_service.rb b/app/services/projects/fetch_statistics_increment_service.rb index b150fd2d9f1..3354a074d1e 100644 --- a/app/services/projects/fetch_statistics_increment_service.rb +++ b/app/services/projects/fetch_statistics_increment_service.rb @@ -15,7 +15,7 @@ module Projects ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1 SQL - ActiveRecord::Base.connection.execute(increment_fetch_count_sql) + ProjectDailyStatistic.connection.execute(increment_fetch_count_sql) end private diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb index eda4b7102c0..011cb2e481d 100644 --- a/app/services/releases/update_service.rb +++ b/app/services/releases/update_service.rb @@ -18,7 +18,7 @@ module Releases # when it does assign_attributes instead of actual saving # this leads to the validation error being raised # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43385 - ActiveRecord::Base.transaction do + ApplicationRecord.transaction do if release.update(params) execute_hooks(release, 'update') success(tag: existing_tag, release: release, milestones_updated: milestones_updated?(previous_milestones)) diff --git a/app/services/todos/destroy/destroyed_issuable_service.rb b/app/services/todos/destroy/destroyed_issuable_service.rb index db12965224b..7a85b59eeea 100644 --- a/app/services/todos/destroy/destroyed_issuable_service.rb +++ b/app/services/todos/destroy/destroyed_issuable_service.rb @@ -20,7 +20,7 @@ module Todos SQL loop do - result = ActiveRecord::Base.connection.execute(delete_query) + result = Todo.connection.execute(delete_query) break if result.cmd_tuples == 0 diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml index b22aaabe41a..1c35250644d 100644 --- a/app/views/admin/application_settings/_email.html.haml +++ b/app/views/admin/application_settings/_email.html.haml @@ -8,20 +8,20 @@ = f.label :email_author_in_body, class: 'form-check-label' do = _('Include author name in notification email body') .form-text.text-muted - = _('Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.') + = _("Include the name of the author of the issue, merge request or comment in the email body. By default, GitLab overrides the email sender's name. Some email servers don't support that option.") .form-group .form-check = f.check_box :html_emails_enabled, class: 'form-check-input' = f.label :html_emails_enabled, class: 'form-check-label' do - = _('Enable HTML emails') + = _('Enable multipart emails') .form-text.text-muted - = _('By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.') + = _('Send email in multipart format (HTML and plain text). Uncheck to send email messages in plain text only.') .form-group = f.label :commit_email_hostname, _('Custom hostname (for private commit emails)'), class: 'label-bold' = f.text_field :commit_email_hostname, class: 'form-control gl-form-input' .form-text.text-muted - commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email.md', anchor: 'custom-hostname-for-private-commit-emails'), target: '_blank' - = _("This setting will update the hostname that is used to generate private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link } + = _("Hostname used in private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link } = render_if_exists 'admin/application_settings/email_additional_text_setting', form: f @@ -31,6 +31,6 @@ = f.label :in_product_marketing_emails_enabled, class: 'form-check-label' do = _('Enable in-product marketing emails') .form-text.text-muted - = _('By default, GitLab sends emails to help guide users through the onboarding process.') + = _('Send emails to help guide new users through the onboarding process.') = f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' } diff --git a/app/views/jira_connect/branches/new.html.haml b/app/views/jira_connect/branches/new.html.haml new file mode 100644 index 00000000000..ec2b7be47ca --- /dev/null +++ b/app/views/jira_connect/branches/new.html.haml @@ -0,0 +1,5 @@ +- @hide_breadcrumbs = true +- @hide_top_links = true +- page_title _('New branch') + +.js-jira-connect-create-branch{ data: { initial_branch_name: @branch_name } } |