diff options
11 files changed, 132 insertions, 20 deletions
diff --git a/app/assets/javascripts/projects/compare/components/app_legacy.vue b/app/assets/javascripts/projects/compare/components/app_legacy.vue index c0ff58ee074..d3f09f7d69f 100644 --- a/app/assets/javascripts/projects/compare/components/app_legacy.vue +++ b/app/assets/javascripts/projects/compare/components/app_legacy.vue @@ -37,10 +37,22 @@ export default { required: true, }, }, + data() { + return { + from: this.paramsFrom, + to: this.paramsTo, + }; + }, methods: { onSubmit() { this.$refs.form.submit(); }, + onSwapRevision() { + [this.from, this.to] = [this.to, this.from]; // swaps 'from' and 'to' + }, + onSelectRevision({ direction, revision }) { + this[direction] = revision; // direction is either 'from' or 'to' + }, }, }; </script> @@ -57,19 +69,30 @@ export default { :refs-project-path="refsProjectPath" revision-text="Source" params-name="to" - :params-branch="paramsTo" + :params-branch="to" + data-testid="sourceRevisionDropdown" + @selectRevision="onSelectRevision" /> <div class="compare-ellipsis gl-display-inline" data-testid="ellipsis">...</div> <revision-dropdown :refs-project-path="refsProjectPath" revision-text="Target" params-name="from" - :params-branch="paramsFrom" + :params-branch="from" + data-testid="targetRevisionDropdown" + @selectRevision="onSelectRevision" /> <gl-button category="primary" variant="success" class="gl-ml-3" @click="onSubmit"> {{ s__('CompareRevisions|Compare') }} </gl-button> <gl-button + data-testid="swapRevisionsButton" + class="btn btn-default gl-button gl-ml-3" + @click="onSwapRevision" + > + {{ s__('CompareRevisions|Swap revisions') }} + </gl-button> + <gl-button v-if="projectMergeRequestPath" :href="projectMergeRequestPath" data-testid="projectMrButton" diff --git a/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue b/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue index 13d80b5ae0b..f57a8942a77 100644 --- a/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue +++ b/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue @@ -55,6 +55,11 @@ export default { return this.filteredTags.length; }, }, + watch: { + paramsBranch(newBranch) { + this.setSelectedRevision(newBranch); + }, + }, mounted() { this.fetchBranchesAndTags(); }, @@ -83,10 +88,14 @@ export default { return this.paramsBranch || s__('CompareRevisions|Select branch/tag'); }, onClick(revision) { - this.selectedRevision = revision; + this.setSelectedRevision(revision); }, onSearchEnter() { - this.selectedRevision = this.searchTerm; + this.setSelectedRevision(this.searchTerm); + }, + setSelectedRevision(revision) { + this.selectedRevision = revision || s__('CompareRevisions|Select branch/tag'); + this.$emit('selectRevision', { direction: this.paramsName, revision }); }, }, }; diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml index c851601d4c3..4b3f6f8cfc7 100644 --- a/app/views/profiles/gpg_keys/_key.html.haml +++ b/app/views/profiles/gpg_keys/_key.html.haml @@ -18,7 +18,7 @@ %code= subkey.fingerprint .float-right %span.key-created-at - = s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago:time_ago_with_tooltip(key.created_at)} + = s_('Profiles|Created %{time_ago}'.html_safe) % { time_ago: time_ago_with_tooltip(key.created_at) } = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "gl-button btn btn-danger gl-ml-3" do %span.sr-only= _('Remove') = sprite_icon('remove') diff --git a/changelogs/unreleased/225345-re-add-swap-branches-feature.yml b/changelogs/unreleased/225345-re-add-swap-branches-feature.yml new file mode 100644 index 00000000000..b3960b1d744 --- /dev/null +++ b/changelogs/unreleased/225345-re-add-swap-branches-feature.yml @@ -0,0 +1,5 @@ +--- +title: Re-add swap revisions feature (legacy) +merge_request: 57802 +author: +type: added diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md new file mode 100644 index 00000000000..7d20382973a --- /dev/null +++ b/doc/development/what_requires_downtime.md @@ -0,0 +1,8 @@ +--- +redirect_to: 'avoiding_downtime_in_migrations.md' +--- + +This document was moved to [another location](avoiding_downtime_in_migrations.md). + +<!-- This redirect file can be deleted after <2021-07-01>. --> +<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page --> diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 47e4199e6fd..b5b7486bb12 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7892,6 +7892,9 @@ msgstr "" msgid "CompareRevisions|Select target project" msgstr "" +msgid "CompareRevisions|Swap revisions" +msgstr "" + msgid "CompareRevisions|Tags" msgstr "" diff --git a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb index e28e054dc46..176f1139a7a 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb @@ -45,7 +45,7 @@ module QA # Note: This test doesn't have the :orchestrated tag because it runs in the Test::Integration::Praefect # scenario with other tests that aren't considered orchestrated. # It also runs on staging using nfs-file07 as non-cluster storage and nfs-file22 as cluster/praefect storage - context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/974', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284645', type: :investigating } do + context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1755', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284645', type: :investigating } do let(:source_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } } let(:destination_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } } let(:project) do diff --git a/spec/frontend/projects/compare/components/app_legacy_spec.js b/spec/frontend/projects/compare/components/app_legacy_spec.js index 4c7f0d5cccc..93e96c8b9f7 100644 --- a/spec/frontend/projects/compare/components/app_legacy_spec.js +++ b/spec/frontend/projects/compare/components/app_legacy_spec.js @@ -8,7 +8,7 @@ jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); const projectCompareIndexPath = 'some/path'; const refsProjectPath = 'some/refs/path'; const paramsFrom = 'master'; -const paramsTo = 'master'; +const paramsTo = 'some-other-branch'; describe('CompareApp component', () => { let wrapper; @@ -36,6 +36,9 @@ describe('CompareApp component', () => { createComponent(); }); + const findSourceDropdown = () => wrapper.find('[data-testid="sourceRevisionDropdown"]'); + const findTargetDropdown = () => wrapper.find('[data-testid="targetRevisionDropdown"]'); + it('renders component with prop', () => { expect(wrapper.props()).toEqual( expect.objectContaining({ @@ -62,12 +65,31 @@ describe('CompareApp component', () => { expect(wrapper.find('[data-testid="ellipsis"]').exists()).toBe(true); }); - it('render Source and Target BranchDropdown components', () => { - const branchDropdowns = wrapper.findAll(RevisionDropdown); + describe('Source and Target BranchDropdown components', () => { + const findAllBranchDropdowns = () => wrapper.findAll(RevisionDropdown); + + it('renders the components with the correct props', () => { + expect(findAllBranchDropdowns().length).toBe(2); + expect(findSourceDropdown().props('revisionText')).toBe('Source'); + expect(findTargetDropdown().props('revisionText')).toBe('Target'); + }); + + it('sets the revision when the "selectRevision" event is emitted', async () => { + findSourceDropdown().vm.$emit('selectRevision', { + direction: 'to', + revision: 'some-source-revision', + }); + + findTargetDropdown().vm.$emit('selectRevision', { + direction: 'from', + revision: 'some-target-revision', + }); + + await wrapper.vm.$nextTick(); - expect(branchDropdowns.length).toBe(2); - expect(branchDropdowns.at(0).props('revisionText')).toBe('Source'); - expect(branchDropdowns.at(1).props('revisionText')).toBe('Target'); + expect(findTargetDropdown().props('paramsBranch')).toBe('some-target-revision'); + expect(findSourceDropdown().props('paramsBranch')).toBe('some-source-revision'); + }); }); describe('compare button', () => { @@ -87,6 +109,27 @@ describe('CompareApp component', () => { }); }); + describe('swap revisions button', () => { + const findSwapRevisionsButton = () => wrapper.find('[data-testid="swapRevisionsButton"]'); + + it('renders the swap revisions button', () => { + expect(findSwapRevisionsButton().exists()).toBe(true); + }); + + it('has the correct text', () => { + expect(findSwapRevisionsButton().text()).toBe('Swap revisions'); + }); + + it('swaps revisions when clicked', async () => { + findSwapRevisionsButton().vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + expect(findTargetDropdown().props('paramsBranch')).toBe(paramsTo); + expect(findSourceDropdown().props('paramsBranch')).toBe(paramsFrom); + }); + }); + describe('merge request buttons', () => { const findProjectMrButton = () => wrapper.find('[data-testid="projectMrButton"]'); const findCreateMrButton = () => wrapper.find('[data-testid="createMrButton"]'); diff --git a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js index 270c89e674c..ca208395e82 100644 --- a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js +++ b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js @@ -1,4 +1,4 @@ -import { GlDropdown } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import createFlash from '~/flash'; @@ -29,6 +29,7 @@ describe('RevisionDropdown component', () => { beforeEach(() => { axiosMock = new AxiosMockAdapter(axios); + createComponent(); }); afterEach(() => { @@ -39,7 +40,6 @@ describe('RevisionDropdown component', () => { const findGlDropdown = () => wrapper.find(GlDropdown); it('sets hidden input', () => { - createComponent(); expect(wrapper.find('input[type="hidden"]').attributes('value')).toBe( defaultProps.paramsBranch, ); @@ -68,8 +68,6 @@ describe('RevisionDropdown component', () => { Tags: undefined, }); - createComponent(); - await axios.waitForAll(); expect(wrapper.vm.branches).toEqual([]); @@ -79,15 +77,12 @@ describe('RevisionDropdown component', () => { it('shows flash message on error', async () => { axiosMock.onGet('some/invalid/path').replyOnce(404); - createComponent(); - await wrapper.vm.fetchBranchesAndTags(); expect(createFlash).toHaveBeenCalled(); }); describe('GlDropdown component', () => { it('renders props', () => { - createComponent(); expect(wrapper.props()).toEqual(expect.objectContaining(defaultProps)); }); @@ -99,8 +94,22 @@ describe('RevisionDropdown component', () => { }); it('display params branch text', () => { - createComponent(); expect(findGlDropdown().props('text')).toBe(defaultProps.paramsBranch); }); + + it('emits a "selectRevision" event when a revision is selected', async () => { + const findGlDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findFirstGlDropdownItem = () => findGlDropdownItems().at(0); + + wrapper.setData({ branches: ['some-branch'] }); + + await wrapper.vm.$nextTick(); + + findFirstGlDropdownItem().vm.$emit('click'); + + expect(wrapper.emitted()).toEqual({ + selectRevision: [[{ direction: 'from', revision: 'some-branch' }]], + }); + }); }); }); diff --git a/spec/tooling/lib/tooling/kubernetes_client_spec.rb b/spec/tooling/lib/tooling/kubernetes_client_spec.rb index 4a84ec09b5c..636727401af 100644 --- a/spec/tooling/lib/tooling/kubernetes_client_spec.rb +++ b/spec/tooling/lib/tooling/kubernetes_client_spec.rb @@ -123,6 +123,16 @@ RSpec.describe Tooling::KubernetesClient do it_behaves_like 'a kubectl command to delete resources by older than given creation time' end + + context 'with no resources found' do + let(:resource_names) { [] } + + it 'does not call #delete_by_exact_names' do + expect(subject).not_to receive(:delete_by_exact_names) + + subject.cleanup_by_created_at(resource_type: resource_type, created_before: two_days_ago) + end + end end describe '#raw_resource_names' do diff --git a/tooling/lib/tooling/kubernetes_client.rb b/tooling/lib/tooling/kubernetes_client.rb index 35605fd493c..9bc5626db6b 100644 --- a/tooling/lib/tooling/kubernetes_client.rb +++ b/tooling/lib/tooling/kubernetes_client.rb @@ -22,6 +22,8 @@ module Tooling def cleanup_by_created_at(resource_type:, created_before:, wait: true) resource_names = resource_names_created_before(resource_type: resource_type, created_before: created_before) + return if resource_names.empty? + delete_by_exact_names(resource_type: resource_type, resource_names: resource_names, wait: wait) end |