diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-12 00:09:14 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-12 00:09:14 +0300 |
commit | 0f079aa28d93f40ad7fda398fb2280c3e358098d (patch) | |
tree | bc67abda12fd05a9712cd0bd97f82e33eb6c927f /app | |
parent | 603ee53dbdbd3adaced752e1a119eb40d64e9979 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
9 files changed, 254 insertions, 51 deletions
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue index 4cc00eb01d9..29578c6f710 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_settings.vue @@ -1,9 +1,93 @@ <script> -export default {}; +import { ADD_VARIABLE_ACTION, EDIT_VARIABLE_ACTION, VARIABLE_ACTIONS } from '../constants'; +import { createJoinedEnvironments } from '../utils'; +import CiVariableTable from './ci_variable_table.vue'; +import CiVariableModal from './ci_variable_modal.vue'; + +export default { + components: { + CiVariableTable, + CiVariableModal, + }, + props: { + areScopedVariablesAvailable: { + type: Boolean, + required: false, + default: false, + }, + environments: { + type: Array, + required: true, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + variables: { + type: Array, + required: true, + }, + }, + data() { + return { + selectedVariable: {}, + mode: null, + }; + }, + computed: { + joinedEnvironments() { + return createJoinedEnvironments(this.variables, this.environments); + }, + showModal() { + return VARIABLE_ACTIONS.includes(this.mode); + }, + }, + methods: { + addVariable(variable) { + this.$emit('add-variable', variable); + }, + deleteVariable(variable) { + this.$emit('delete-variable', variable); + }, + updateVariable(variable) { + this.$emit('update-variable', variable); + }, + hideModal() { + this.mode = null; + }, + setSelectedVariable(variable = null) { + if (!variable) { + this.selectedVariable = {}; + this.mode = ADD_VARIABLE_ACTION; + } else { + this.selectedVariable = variable; + this.mode = EDIT_VARIABLE_ACTION; + } + }, + }, +}; </script> <template> <div class="row"> - <div class="col-lg-12"></div> + <div class="col-lg-12"> + <ci-variable-table + :is-loading="isLoading" + :variables="variables" + @set-selected-variable="setSelectedVariable" + /> + <ci-variable-modal + v-if="showModal" + :are-scoped-variables-available="areScopedVariablesAvailable" + :environments="joinedEnvironments" + :mode="mode" + :selected-variable="selectedVariable" + @add-variable="addVariable" + @delete-variable="deleteVariable" + @hideModal="hideModal" + @update-variable="updateVariable" + /> + </div> </div> </template> diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue index f078234829a..1bb94080694 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue @@ -1,10 +1,17 @@ <script> -import { GlTable, GlButton, GlModalDirective, GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { mapState, mapActions } from 'vuex'; +import { + GlButton, + GlIcon, + GlLoadingIcon, + GlModalDirective, + GlTable, + GlTooltipDirective, +} from '@gitlab/ui'; import { s__, __ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; -import { ADD_CI_VARIABLE_MODAL_ID } from '../constants'; +import { ADD_CI_VARIABLE_MODAL_ID, variableText } from '../constants'; +import { convertEnvironmentScope } from '../utils'; import CiVariablePopover from './ci_variable_popover.vue'; export default { @@ -14,7 +21,7 @@ export default { iconSize: 16, fields: [ { - key: 'variable_type', + key: 'variableType', label: s__('CiVariables|Type'), customStyle: { width: '70px' }, }, @@ -41,7 +48,7 @@ export default { customStyle: { width: '100px' }, }, { - key: 'environment_scope', + key: 'environmentScope', label: s__('CiVariables|Environments'), customStyle: { width: '20%' }, }, @@ -56,6 +63,7 @@ export default { CiVariablePopover, GlButton, GlIcon, + GlLoadingIcon, GlTable, TooltipOnTruncate, }, @@ -64,10 +72,25 @@ export default { GlTooltip: GlTooltipDirective, }, mixins: [glFeatureFlagsMixin()], + props: { + isLoading: { + type: Boolean, + required: false, + default: false, + }, + variables: { + type: Array, + required: true, + }, + }, + data() { + return { + areValuesHidden: true, + }; + }, computed: { - ...mapState(['variables', 'valuesHidden', 'isLoading', 'isDeleting']), valuesButtonText() { - return this.valuesHidden ? __('Reveal values') : __('Hide values'); + return this.areValuesHidden ? __('Reveal values') : __('Hide values'); }, isTableEmpty() { return !this.variables || this.variables.length === 0; @@ -76,18 +99,28 @@ export default { return this.$options.fields; }, }, - mounted() { - this.fetchVariables(); - }, methods: { - ...mapActions(['fetchVariables', 'toggleValues', 'editVariable']), + convertEnvironmentScopeValue(env) { + return convertEnvironmentScope(env); + }, + generateTypeText(item) { + return variableText[item.variableType]; + }, + toggleHiddenState() { + this.areValuesHidden = !this.areValuesHidden; + }, + setSelectedVariable(variable = null) { + this.$emit('set-selected-variable', variable); + }, }, }; </script> <template> <div class="ci-variable-table" data-testid="ci-variable-table"> + <gl-loading-icon v-if="isLoading" /> <gl-table + v-else :fields="fields" :items="variables" tbody-tr-class="js-ci-variable-row" @@ -104,6 +137,11 @@ export default { <template #table-colgroup="scope"> <col v-for="field in scope.fields" :key="field.key" :style="field.customStyle" /> </template> + <template #cell(variableType)="{ item }"> + <div class="gl-pt-2"> + {{ generateTypeText(item) }} + </div> + </template> <template #cell(key)="{ item }"> <div class="gl-display-flex gl-align-items-center"> <tooltip-on-truncate :title="item.key" truncate-target="child"> @@ -125,11 +163,12 @@ export default { </template> <template #cell(value)="{ item }"> <div class="gl-display-flex gl-align-items-center"> - <span v-if="valuesHidden">*********************</span> + <span v-if="areValuesHidden" data-testid="hiddenValue">*********************</span> <span v-else :id="`ci-variable-value-${item.id}`" class="gl-display-inline-block gl-max-w-full gl-text-truncate" + data-testid="revealedValue" >{{ item.value }}</span > <gl-button @@ -150,16 +189,16 @@ export default { <gl-icon v-if="item.masked" :size="$options.iconSize" :name="$options.trueIcon" /> <gl-icon v-else :size="$options.iconSize" :name="$options.falseIcon" /> </template> - <template #cell(environment_scope)="{ item }"> + <template #cell(environmentScope)="{ item }"> <div class="gl-display-flex"> <span :id="`ci-variable-env-${item.id}`" class="gl-display-inline-block gl-max-w-full gl-text-truncate" - >{{ item.environment_scope }}</span + >{{ convertEnvironmentScopeValue(item.environmentScope) }}</span > <ci-variable-popover :target="`ci-variable-env-${item.id}`" - :value="item.environment_scope" + :value="convertEnvironmentScopeValue(item.environmentScope)" :tooltip-text="__('Copy environment')" /> </div> @@ -170,7 +209,7 @@ export default { icon="pencil" :aria-label="__('Edit')" data-qa-selector="edit_ci_variable_button" - @click="editVariable(item)" + @click="setSelectedVariable(item)" /> </template> <template #empty> @@ -186,12 +225,14 @@ export default { data-qa-selector="add_ci_variable_button" variant="confirm" category="primary" + :aria-label="__('Add')" + @click="setSelectedVariable()" >{{ __('Add variable') }}</gl-button > <gl-button v-if="!isTableEmpty" data-qa-selector="reveal_ci_variable_value_button" - @click="toggleValues(!valuesHidden)" + @click="toggleHiddenState" >{{ valuesButtonText }}</gl-button > </div> diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 6c805f7b4ea..848ba7dbeef 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -245,6 +245,10 @@ "terraform": { "$ref": "#/definitions/string_file_list", "description": "Path to file or list of files with terraform plan(s)." + }, + "cyclonedx": { + "$ref": "#/definitions/string_file_list", + "markdownDescription": "Path to file or list of files with cyclonedx report(s). [Learn More](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscyclonedx)." } } } diff --git a/app/assets/javascripts/sidebar/components/severity/severity.vue b/app/assets/javascripts/sidebar/components/severity/severity.vue index 0db856543d0..776dab98f01 100644 --- a/app/assets/javascripts/sidebar/components/severity/severity.vue +++ b/app/assets/javascripts/sidebar/components/severity/severity.vue @@ -37,10 +37,10 @@ export default { <gl-icon :size="iconSize" :name="`severity-${severity.icon}`" - :class="[`icon-${severity.icon}`, { 'gl-mr-3': !iconOnly }]" + :class="[`icon-${severity.icon}`, { 'gl-mr-3 gl-flex-shrink-0': !iconOnly }]" /> - <tooltip-on-truncate v-if="!iconOnly" :title="severity.label" class="gl-text-truncate">{{ - severity.label - }}</tooltip-on-truncate> + <tooltip-on-truncate v-if="!iconOnly" :title="severity.label" class="gl-text-truncate"> + {{ severity.label }} + </tooltip-on-truncate> </div> </template> diff --git a/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue index 86e46016534..bf4ba715f85 100644 --- a/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue +++ b/app/assets/javascripts/sidebar/components/severity/sidebar_severity.vue @@ -149,23 +149,25 @@ export default { </div> <div class="hide-collapsed"> - <p - class="gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-display-flex gl-justify-content-space-between" + <div + class="gl-display-flex gl-align-items-center gl-line-height-20 gl-text-gray-900 gl-font-weight-bold" > {{ $options.i18n.SEVERITY }} <gl-button v-if="canUpdate" category="tertiary" size="small" + class="gl-ml-auto hide-collapsed gl-mr-n2" data-testid="editButton" @click="toggleFormDropdown" @keydown.esc="hideDropdown" > {{ $options.i18n.EDIT }} </gl-button> - </p> + </div> <gl-dropdown + class="gl-mt-3" :class="dropdownClass" block :header-text="__('Assign severity')" diff --git a/app/controllers/admin/topics_controller.rb b/app/controllers/admin/topics_controller.rb index b451928e591..69bcfdf4791 100644 --- a/app/controllers/admin/topics_controller.rb +++ b/app/controllers/admin/topics_controller.rb @@ -45,6 +45,22 @@ class Admin::TopicsController < Admin::ApplicationController notice: _('Topic %{topic_name} was successfully removed.') % { topic_name: @topic.title_or_name } end + def merge + source_topic = Projects::Topic.find(merge_params[:source_topic_id]) + target_topic = Projects::Topic.find(merge_params[:target_topic_id]) + + begin + ::Topics::MergeService.new(source_topic, target_topic).execute + rescue ArgumentError => e + return render status: :bad_request, json: { type: :alert, message: e.message } + end + + message = _('Topic %{source_topic} was successfully merged into topic %{target_topic}.') + redirect_to admin_topics_path, + status: :found, + notice: message % { source_topic: source_topic.name, target_topic: target_topic.name } + end + private def topic @@ -63,4 +79,8 @@ class Admin::TopicsController < Admin::ApplicationController :title ] end + + def merge_params + params.permit([:source_topic_id, :target_topic_id]) + end end diff --git a/app/models/concerns/cross_database_modification.rb b/app/models/concerns/cross_database_modification.rb index dea62f03f91..273d5f35e76 100644 --- a/app/models/concerns/cross_database_modification.rb +++ b/app/models/concerns/cross_database_modification.rb @@ -80,34 +80,22 @@ module CrossDatabaseModification end def transaction(**options, &block) - if track_gitlab_schema_in_current_transaction? - super(**options) do - # Hook into current transaction to ensure that once - # the `COMMIT` is executed the `gitlab_transactions_stack` - # will be allowing to execute `after_commit_queue` - record = TransactionStackTrackRecord.new(self, gitlab_schema) - - begin - connection.current_transaction.add_record(record) - - yield - ensure - record.done! - end + super(**options) do + # Hook into current transaction to ensure that once + # the `COMMIT` is executed the `gitlab_transactions_stack` + # will be allowing to execute `after_commit_queue` + record = TransactionStackTrackRecord.new(self, gitlab_schema) + + begin + connection.current_transaction.add_record(record) + + yield + ensure + record.done! end - else - super(**options, &block) end end - def track_gitlab_schema_in_current_transaction? - return false unless Feature::FlipperFeature.table_exists? - - Feature.enabled?(:track_gitlab_schema_in_current_transaction) - rescue ActiveRecord::NoDatabaseError, PG::ConnectionBad - false - end - def gitlab_schema case self.name when 'ActiveRecord::Base', 'ApplicationRecord' diff --git a/app/services/topics/merge_service.rb b/app/services/topics/merge_service.rb new file mode 100644 index 00000000000..0d256579fe0 --- /dev/null +++ b/app/services/topics/merge_service.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Topics + class MergeService + attr_accessor :source_topic, :target_topic + + def initialize(source_topic, target_topic) + @source_topic = source_topic + @target_topic = target_topic + end + + def execute + validate_parameters! + + ::Projects::ProjectTopic.transaction do + move_project_topics + refresh_target_topic_counters + delete_source_topic + end + end + + private + + def validate_parameters! + raise ArgumentError, 'The source topic is not a topic.' unless source_topic.is_a?(Projects::Topic) + raise ArgumentError, 'The target topic is not a topic.' unless target_topic.is_a?(Projects::Topic) + raise ArgumentError, 'The source topic and the target topic are identical.' if source_topic == target_topic + end + + # rubocop: disable CodeReuse/ActiveRecord + def move_project_topics + project_ids_for_projects_currently_using_source_and_target = ::Projects::ProjectTopic + .where(topic_id: target_topic).select(:project_id) + # Only update for projects that exclusively use the source topic + ::Projects::ProjectTopic.where(topic_id: source_topic.id) + .where.not(project_id: project_ids_for_projects_currently_using_source_and_target) + .update_all(topic_id: target_topic.id) + + # Delete source topic for projects that were using source and target + ::Projects::ProjectTopic.where(topic_id: source_topic.id).delete_all + end + + def refresh_target_topic_counters + target_topic.update!( + total_projects_count: total_projects_count(target_topic.id), + non_private_projects_count: non_private_projects_count(target_topic.id) + ) + end + + def delete_source_topic + source_topic.destroy! + end + + def total_projects_count(topic_id) + ::Projects::ProjectTopic.where(topic_id: topic_id).count + end + + def non_private_projects_count(topic_id) + ::Projects::ProjectTopic.joins(:project).where(topic_id: topic_id).where('projects.visibility_level in (10, 20)') + .count + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 1d6a6c2aed5..7d7d20ffd6a 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -58,7 +58,7 @@ - if header_link?(:merge_requests) = nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter dropdown" }) do - top_level_link = assigned_mrs_dashboard_path - = link_to top_level_link, class: 'dashboard-shortcuts-merge_requests', title: _('Merge requests'), aria: { label: _('Merge requests') }, + = link_to top_level_link, class: 'dashboard-shortcuts-merge_requests has-tooltip', title: _('Merge requests'), aria: { label: _('Merge requests') }, data: { qa_selector: 'merge_requests_shortcut_button', toggle: "dropdown", placement: 'bottom', |