Skip to content
Snippets Groups Projects
Commit 2dc75d4e authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Automatic merge of gitlab-org/gitlab master

parents a1984791 e9fe4ff1
No related branches found
No related tags found
1 merge request!170053Security patch upgrade alert: Only expose to admins 17-4
Showing
with 134 additions and 34 deletions
......@@ -2,7 +2,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { visitUrl } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getEnvironment from '../graphql/queries/environment.query.graphql';
import updateEnvironment from '../graphql/mutations/update_environment.mutation.graphql';
import EnvironmentForm from './environment_form.vue';
......@@ -12,11 +11,9 @@ export default {
GlLoadingIcon,
EnvironmentForm,
},
mixins: [glFeatureFlagsMixin()],
inject: ['projectEnvironmentsPath', 'projectPath', 'environmentName'],
apollo: {
// eslint-disable-next-line @gitlab/vue-no-undef-apollo-properties
environment: {
formEnvironment: {
query: getEnvironment,
variables() {
return {
......@@ -26,7 +23,7 @@ export default {
},
update(data) {
const result = data?.project?.environment || {};
this.formEnvironment = { ...result, clusterAgentId: result?.clusterAgent?.id };
return { ...result, clusterAgentId: result?.clusterAgent?.id };
},
},
},
......@@ -38,7 +35,7 @@ export default {
},
computed: {
isQueryLoading() {
return this.$apollo.queries.environment.loading;
return this.$apollo.queries.formEnvironment.loading;
},
},
methods: {
......@@ -53,6 +50,7 @@ export default {
variables: {
input: {
id: this.formEnvironment.id,
description: this.formEnvironment.description,
externalUrl: this.formEnvironment.externalUrl,
clusterAgentId: this.formEnvironment.clusterAgentId,
kubernetesNamespace: this.formEnvironment.kubernetesNamespace,
......
......@@ -16,7 +16,7 @@ import {
ENVIRONMENT_EDIT_HELP_TEXT,
} from 'ee_else_ce/environments/constants';
import csrf from '~/lib/utils/csrf';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import getUserAuthorizedAgents from '../graphql/queries/user_authorized_agents.query.graphql';
import EnvironmentFluxResourceSelector from './environment_flux_resource_selector.vue';
......@@ -33,12 +33,13 @@ export default {
GlSprintf,
EnvironmentFluxResourceSelector,
EnvironmentNamespaceSelector,
MarkdownEditor,
},
mixins: [glFeatureFlagsMixin()],
inject: {
protectedEnvironmentSettingsPath: { default: '' },
projectPath: { default: '' },
kasTunnelUrl: { default: '' },
markdownPreviewPath: { default: '' },
},
props: {
environment: {
......@@ -71,6 +72,11 @@ export default {
nameFeedback: __('This field is required'),
nameDisabledHelp: __("You cannot rename an environment after it's created."),
nameDisabledLinkText: __('How do I rename an environment?'),
descriptionLabel: __('Description'),
descriptionPlaceholder: s__('Environments|Write a description or drag your files here…'),
descriptionHelpText: s__(
'Environments|The description is displayed to anyone who can see this environment.',
),
urlLabel: __('External URL'),
urlFeedback: __('The URL should start with http:// or https://'),
agentLabel: s__('Environments|GitLab agent'),
......@@ -84,6 +90,8 @@ export default {
renamingDisabledHelpPagePath: helpPagePath('ci/environments/index.md', {
anchor: 'rename-an-environment',
}),
markdownDocsPath: helpPagePath('user/markdown'),
restrictedToolbarItems: ['full-screen'],
data() {
return {
visited: {
......@@ -158,6 +166,13 @@ export default {
credentials: 'include',
};
},
descriptionFieldProps() {
return {
'aria-label': this.$options.i18n.descriptionLabel,
placeholder: this.$options.i18n.descriptionPlaceholder,
id: 'environment_description',
};
},
},
watch: {
environment(change) {
......@@ -172,6 +187,11 @@ export default {
visit(field) {
this.visited[field] = true;
},
updateDescription($event) {
if (this.environment.description !== $event) {
this.onChange({ ...this.environment, description: $event });
}
},
getAgentsList() {
this.$apollo.addSmartQuery('userAccessAuthorizedAgents', {
variables() {
......@@ -253,6 +273,24 @@ export default {
@blur="visit('name')"
/>
</gl-form-group>
<gl-form-group
:label="$options.i18n.descriptionLabel"
:description="$options.i18n.descriptionHelpText"
label-for="environment_description"
:state="valid.description"
>
<div class="common-note-form gfm-form">
<markdown-editor
:value="environment.description"
:render-markdown-path="markdownPreviewPath"
:form-field-props="descriptionFieldProps"
:restricted-tool-bar-items="$options.restrictedToolbarItems"
:markdown-docs-path="$options.markdownDocsPath"
:disabled="loading"
@input="updateDescription"
/>
</div>
</gl-form-group>
<gl-form-group
:label="$options.i18n.urlLabel"
:state="valid.url"
......@@ -320,6 +358,7 @@ export default {
variant="confirm"
name="commit"
class="js-no-auto-disable"
data-testid="save-environment"
>{{ $options.i18n.save }}</gl-button
>
<gl-button :href="cancelPath">{{ $options.i18n.cancel }}</gl-button>
......
<script>
import { GlButton, GlModalDirective, GlTooltipDirective as GlTooltip, GlSprintf } from '@gitlab/ui';
import {
GlButton,
GlTruncateText,
GlModalDirective,
GlTooltipDirective as GlTooltip,
GlSprintf,
} from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import SafeHtml from '~/vue_shared/directives/safe_html';
import DeleteEnvironmentModal from './delete_environment_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
import DeployFreezeAlert from './deploy_freeze_alert.vue';
......@@ -14,6 +22,7 @@ export default {
components: {
GlButton,
GlSprintf,
GlTruncateText,
TimeAgo,
DeployFreezeAlert,
DeleteEnvironmentModal,
......@@ -22,6 +31,7 @@ export default {
directives: {
GlModalDirective,
GlTooltip,
SafeHtml,
},
mixins: [timeagoMixin],
props: {
......@@ -69,6 +79,7 @@ export default {
externalButtonTitle: s__('Environments|Open live environment'),
externalButtonText: __('View deployment'),
cancelAutoStopButtonTitle: __('Prevent environment from auto-stopping'),
showMoreText: __('Read more'),
},
computed: {
shouldShowCancelAutoStopButton() {
......@@ -84,6 +95,10 @@ export default {
return this.canAdminEnvironment && this.environment.hasTerminals;
},
},
mounted() {
renderGFM(this.$refs['gfm-content']);
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
</script>
<template>
......@@ -159,5 +174,18 @@ export default {
<delete-environment-modal v-if="canDestroyEnvironment" :environment="environment" />
<stop-environment-modal v-if="shouldShowStopButton" :environment="environment" />
</header>
<gl-truncate-text
v-if="environment.descriptionHtml"
:show-more-text="$options.i18n.showMoreText"
class="gl-relative gl-mb-4"
>
<div
ref="gfm-content"
v-safe-html:[$options.safeHtmlConfig]="environment.descriptionHtml"
class="md"
data-testid="environment-description-content"
></div>
</gl-truncate-text>
</div>
</template>
......@@ -13,6 +13,7 @@ export default {
return {
environment: {
name: '',
description: '',
externalUrl: '',
clusterAgentId: null,
},
......@@ -31,6 +32,7 @@ export default {
variables: {
input: {
name: this.environment.name,
description: this.environment.description,
externalUrl: this.environment.externalUrl,
projectPath: this.projectPath,
clusterAgentId: this.environment.clusterAgentId,
......
......@@ -15,6 +15,7 @@ export default (el) => {
projectEnvironmentsPath,
protectedEnvironmentSettingsPath,
projectPath,
markdownPreviewPath,
environmentName,
kasTunnelUrl,
} = el.dataset;
......@@ -26,6 +27,7 @@ export default (el) => {
projectEnvironmentsPath,
protectedEnvironmentSettingsPath,
projectPath,
markdownPreviewPath,
environmentName,
kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl),
},
......
......@@ -4,6 +4,7 @@ query getEnvironment($projectFullPath: ID!, $environmentName: String) {
environment(name: $environmentName) {
id
name
description
externalUrl
kubernetesNamespace
fluxResourcePath
......
......@@ -37,9 +37,10 @@ export const initHeader = () => {
autoStopAt: dataset.autoStopAt,
onSingleEnvironmentPage: true,
// TODO: These two props are snake_case because the environments_mixin file uses
// them and the mixin is imported in several files. It would be nice to conver them to camelCase.
// them and the mixin is imported in several files. It would be nice to convert them to camelCase.
stop_path: dataset.environmentStopPath,
delete_path: dataset.environmentDeletePath,
descriptionHtml: dataset.descriptionHtml,
};
return {
......
......@@ -11,7 +11,7 @@ export default (el) => {
return null;
}
const { projectEnvironmentsPath, projectPath, kasTunnelUrl } = el.dataset;
const { projectEnvironmentsPath, projectPath, markdownPreviewPath, kasTunnelUrl } = el.dataset;
return new Vue({
el,
......@@ -19,6 +19,7 @@ export default (el) => {
provide: {
projectEnvironmentsPath,
projectPath,
markdownPreviewPath,
kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl),
},
render(h) {
......
......@@ -179,6 +179,7 @@
"MergeRequest",
"Namespace",
"Project",
"Vulnerability",
"WorkItem"
],
"User": [
......
......@@ -157,21 +157,19 @@ const initForkInfo = () => {
initForkInfo();
const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
const legacyStatusBadge = document.querySelector('.js-ci-status-badge-legacy');
const commitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
if (legacyStatusBadge) {
legacyStatusBadge.remove();
if (commitPipelineStatusEl) {
// eslint-disable-next-line no-new
new Vue({
el: CommitPipelineStatusEl,
el: commitPipelineStatusEl,
components: {
CommitPipelineStatus,
},
render(createElement) {
return createElement('commit-pipeline-status', {
props: {
endpoint: CommitPipelineStatusEl.dataset.endpoint,
endpoint: commitPipelineStatusEl.dataset.endpoint,
},
});
},
......
......@@ -9,7 +9,7 @@ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
export default (selector = '.js-commit-pipeline-status') => {
export default (selector = '.js-commit-box-pipeline-status') => {
const el = document.querySelector(selector);
if (!el) {
......
......@@ -349,7 +349,7 @@ export const NEW_WORK_ITEM_IID = 'new-work-item-iid';
export const NEW_WORK_ITEM_GID = 'gid://gitlab/WorkItem/new';
export const NEW_EPIC_FEEDBACK_PROMPT_EXPIRY = '2024-11-01';
export const NEW_EPIC_FEEDBACK_PROMPT_EXPIRY = '2024-12-31';
export const FEATURE_NAME = 'work_item_epic_feedback';
export const CLEAR_VALUE = 'CLEAR_VALUE';
......
......@@ -77,7 +77,7 @@ def render_ci_icon(
title = "#{_('Pipeline')}: #{ci_label_for_status(status)}"
data = { toggle: 'tooltip', placement: tooltip_placement, container: container, testid: 'ci-icon' }
icon_wrapper_class = "js-ci-status-badge-legacy ci-icon-gl-icon-wrapper"
icon_wrapper_class = "ci-icon-gl-icon-wrapper"
content_tag(content_tag_variant, href: path, class: badge_classes, title: title, data: data) do
if show_status_text
......
......@@ -32,6 +32,7 @@ def environments_detail_data(user, project, environment)
environment_terminal_path: terminal_project_environment_path(project, environment),
has_terminals: environment.has_terminals?,
is_environment_available: environment.available?,
description_html: markdown_field(environment, :description),
auto_stop_at: environment.auto_stop_at,
graphql_etag_key: environment.etag_cache_key
}
......
......@@ -248,6 +248,11 @@ def search_scope
end
end
def should_show_work_items_as_epics_in_results?
::Feature.enabled?(:search_epics_uses_work_items_index, current_user) &&
::Elastic::DataMigrationService.migration_has_finished?(:backfill_work_items)
end
def should_show_zoekt_results?(_scope, _search_type)
false
end
......
# frozen_string_literal: true
module Gitlab
module Git
class TagPolicy < BasePolicy
delegate { project }
condition(:protected_tag, scope: :subject) do
ProtectedTag.protected?(project, @subject.name)
end
rule { can?(:admin_tag) & (~protected_tag | can?(:maintainer_access)) }.enable :delete_tag
def project
@subject.repository.container
end
end
end
end
......@@ -88,7 +88,7 @@ def handle_over_limit
def record_result(cleaner, result)
if cleaner.async_delete?
modification_tracker.add_deletions(result[:table], result[:affected_rows])
elsif cleaner.async_nullify? || (cleaner.update_column_to? && Feature.enabled?(:loose_foreign_keys_update_column_to, Feature.current_request))
elsif cleaner.async_nullify? || cleaner.update_column_to?
modification_tracker.add_updates(result[:table], result[:affected_rows])
else
logger.error("Invalid on_delete argument for definition: #{result[:table]}")
......
......@@ -41,7 +41,7 @@ def build_query
delete_query
elsif async_nullify?
update_query
elsif update_column_to? && Feature.enabled?(:loose_foreign_keys_update_column_to, Feature.current_request)
elsif update_column_to?
update_target_column_query
else
logger.error("Invalid on_delete argument: #{loose_foreign_key_definition.on_delete}")
......
......@@ -2,21 +2,17 @@
module Tags
class DestroyService < BaseService
def execute(tag_name)
def execute(tag_name, skip_find: false)
repository = project.repository
tag = repository.find_tag(tag_name)
unless tag
return error('No such tag', 404)
end
# If we've found the tag upstream we don't need to refind it so we can
# pass skip_find: true
return error('No such tag', 404) unless skip_find || tag_exists?(tag_name)
if repository.rm_tag(current_user, tag_name)
##
# When a tag in a repository is destroyed,
# release assets will be destroyed too.
Releases::DestroyService
.new(project, current_user, tag: tag_name)
.execute
# When a tag in a repository is destroyed, release assets will be
# destroyed too.
destroy_releases(tag_name)
unlock_artifacts(tag_name)
......@@ -38,6 +34,14 @@ def success(message)
private
def tag_exists?(tag_name)
repository.find_tag(tag_name)
end
def destroy_releases(tag_name)
Releases::DestroyService.new(project, current_user, tag: tag_name).execute
end
def unlock_artifacts(tag_name)
Ci::RefDeleteUnlockArtifactsWorker.perform_async(project.id, current_user.id, "#{::Gitlab::Git::TAG_REF_PREFIX}#{tag_name}")
end
......
......@@ -10,7 +10,7 @@
.info-well.gl-hidden.sm:gl-block
.well-segment
%ul.blob-commit-info
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref
= render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref, show_legacy_ci_icon: false
- if project.licensed_feature_available?(:code_owners)
#js-code-owners{ data: { blob_path: blob.path, project_path: @project.full_path, branch: @ref, can_view_branch_rules: can_view_branch_rules?, branch_rules_path: branch_rules_path } }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment