Commit c43c891e authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 1762728e 015c466b
...@@ -83,7 +83,7 @@ export default { ...@@ -83,7 +83,7 @@ export default {
<gl-badge variant="warning">{{ __('pending deletion') }}</gl-badge> <gl-badge variant="warning">{{ __('pending deletion') }}</gl-badge>
</div> </div>
<div v-if="isProject" class="last-updated"> <div v-if="isProject" class="last-updated">
<time-ago-tooltip :time="item.updatedAt" tooltip-placement="bottom" /> <time-ago-tooltip :time="item.lastActivityAt" tooltip-placement="bottom" />
</div> </div>
</div> </div>
</template> </template>
...@@ -98,6 +98,9 @@ export default class GroupsStore { ...@@ -98,6 +98,9 @@ export default class GroupsStore {
updatedAt: rawGroupItem.updated_at, updatedAt: rawGroupItem.updated_at,
pendingRemoval: rawGroupItem.marked_for_deletion, pendingRemoval: rawGroupItem.marked_for_deletion,
microdata: this.showSchemaMarkup ? getGroupItemMicrodata(rawGroupItem) : {}, microdata: this.showSchemaMarkup ? getGroupItemMicrodata(rawGroupItem) : {},
lastActivityAt: rawGroupItem.last_activity_at
? rawGroupItem.last_activity_at
: rawGroupItem.updated_at,
}; };
if (!isEmpty(rawGroupItem.compliance_management_framework)) { if (!isEmpty(rawGroupItem.compliance_management_framework)) {
......
...@@ -33,6 +33,8 @@ class GroupChildEntity < Grape::Entity ...@@ -33,6 +33,8 @@ class GroupChildEntity < Grape::Entity
end end
# Project only attributes # Project only attributes
expose :last_activity_at, if: lambda { |instance| project? }
expose :star_count, :archived, expose :star_count, :archived,
if: lambda { |_instance, _options| project? } if: lambda { |_instance, _options| project? }
......
# frozen_string_literal: true
class RemoveForeignKeyCiRunnerNamespacesNamespaceId < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
CONSTRAINT_NAME = 'fk_rails_f9d9ed3308'
def up
with_lock_retries do
remove_foreign_key_if_exists(:ci_runner_namespaces, :namespaces, name: CONSTRAINT_NAME)
end
end
def down
add_concurrent_foreign_key :ci_runner_namespaces, :namespaces, column: :namespace_id, on_delete: :cascade, name: CONSTRAINT_NAME
end
end
34f966723cae63e831f7fc9d965cda90f1fd7bca522fc58e78a0de4b959a47a2
\ No newline at end of file
...@@ -31438,9 +31438,6 @@ ALTER TABLE ONLY merge_requests_closing_issues ...@@ -31438,9 +31438,6 @@ ALTER TABLE ONLY merge_requests_closing_issues
ALTER TABLE ONLY merge_trains ALTER TABLE ONLY merge_trains
ADD CONSTRAINT fk_rails_f90820cb08 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL; ADD CONSTRAINT fk_rails_f90820cb08 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
ALTER TABLE ONLY ci_runner_namespaces
ADD CONSTRAINT fk_rails_f9d9ed3308 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY banned_users ALTER TABLE ONLY banned_users
ADD CONSTRAINT fk_rails_fa5bb598e5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_fa5bb598e5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
...@@ -171,6 +171,7 @@ Each feature flag is defined in a separate YAML file consisting of a number of f ...@@ -171,6 +171,7 @@ Each feature flag is defined in a separate YAML file consisting of a number of f
| `default_enabled` | yes | The default state of the feature flag that is strictly validated, with `default_enabled:` passed as an argument. | | `default_enabled` | yes | The default state of the feature flag that is strictly validated, with `default_enabled:` passed as an argument. |
| `introduced_by_url` | no | The URL to the Merge Request that introduced the feature flag. | | `introduced_by_url` | no | The URL to the Merge Request that introduced the feature flag. |
| `rollout_issue_url` | no | The URL to the Issue covering the feature flag rollout. | | `rollout_issue_url` | no | The URL to the Issue covering the feature flag rollout. |
| `milestone` | no | Milestone in which the feature was added. |
| `group` | no | The [group](https://about.gitlab.com/handbook/product/categories/#devops-stages) that owns the feature flag. | | `group` | no | The [group](https://about.gitlab.com/handbook/product/categories/#devops-stages) that owns the feature flag. |
NOTE: NOTE:
......
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton, GlModal } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import lockPathMutation from '~/repository/mutations/lock_path.mutation.graphql'; import lockPathMutation from '~/repository/mutations/lock_path.mutation.graphql';
...@@ -8,9 +8,13 @@ export default { ...@@ -8,9 +8,13 @@ export default {
i18n: { i18n: {
lock: __('Lock'), lock: __('Lock'),
unlock: __('Unlock'), unlock: __('Unlock'),
modalTitle: __('Lock File?'),
actionPrimary: __('Okay'),
actionCancel: __('Cancel'),
}, },
components: { components: {
GlButton, GlButton,
GlModal,
}, },
props: { props: {
name: { name: {
...@@ -36,6 +40,7 @@ export default { ...@@ -36,6 +40,7 @@ export default {
}, },
data() { data() {
return { return {
isModalVisible: false,
lockLoading: false, lockLoading: false,
locked: this.isLocked, locked: this.isLocked,
}; };
...@@ -52,11 +57,14 @@ export default { ...@@ -52,11 +57,14 @@ export default {
}, },
}, },
methods: { methods: {
onLockToggle() { hideModal() {
// eslint-disable-next-line no-alert this.isModalVisible = false;
if (window.confirm(this.lockConfirmText)) { },
this.toggleLock(); handleModalPrimary() {
} this.toggleLock();
},
showModal() {
this.isModalVisible = true;
}, },
toggleLock() { toggleLock() {
this.lockLoading = true; this.lockLoading = true;
...@@ -82,7 +90,23 @@ export default { ...@@ -82,7 +90,23 @@ export default {
</script> </script>
<template> <template>
<gl-button :disabled="!canLock" :loading="lockLoading" @click="onLockToggle"> <span>
{{ lockButtonTitle }} <gl-button :disabled="!canLock" :loading="lockLoading" @click="showModal">
</gl-button> {{ lockButtonTitle }}
</gl-button>
<gl-modal
modal-id="lock-file-modal"
:visible="isModalVisible"
:title="$options.i18n.modalTitle"
:action-primary="{ text: $options.i18n.actionPrimary }"
:action-cancel="{ text: $options.i18n.actionCancel }"
@primary="handleModalPrimary"
@hide="hideModal"
>
<p>
{{ lockConfirmText }}
</p>
</gl-modal>
</span>
</template> </template>
import { GlButton } from '@gitlab/ui'; import { GlButton, GlModal } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
...@@ -39,18 +39,17 @@ describe('LockButton component', () => { ...@@ -39,18 +39,17 @@ describe('LockButton component', () => {
}); });
describe('lock button', () => { describe('lock button', () => {
let confirmSpy;
let lockMutationMock; let lockMutationMock;
const mockEvent = { preventDefault: jest.fn() };
const findLockButton = () => wrapper.find(GlButton); const findLockButton = () => wrapper.find(GlButton);
const findModal = () => wrapper.findComponent(GlModal);
const clickSubmit = () => findModal().vm.$emit('primary', mockEvent);
const clickHide = () => findModal().vm.$emit('hide', mockEvent);
beforeEach(() => { beforeEach(() => {
confirmSpy = jest.spyOn(window, 'confirm');
confirmSpy.mockImplementation(jest.fn());
lockMutationMock = jest.fn(); lockMutationMock = jest.fn();
}); });
afterEach(() => confirmSpy.mockRestore());
it('disables the lock button if canLock is set to false', () => { it('disables the lock button if canLock is set to false', () => {
createComponent({ canLock: false }); createComponent({ canLock: false });
...@@ -78,18 +77,24 @@ describe('LockButton component', () => { ...@@ -78,18 +77,24 @@ describe('LockButton component', () => {
expect(findLockButton().props('loading')).toBe(true); expect(findLockButton().props('loading')).toBe(true);
}); });
it('displays a confirm dialog when the lock button is clicked', () => { it('displays a confirm modal when the lock button is clicked', () => {
createComponent(); createComponent();
findLockButton().vm.$emit('click'); findLockButton().vm.$emit('click');
expect(findModal().text()).toBe('Are you sure you want to lock some_file.js?');
});
expect(confirmSpy).toHaveBeenCalledWith('Are you sure you want to lock some_file.js?'); it('should hide the confirm modal when a hide action is triggered', () => {
createComponent();
findLockButton().vm.$emit('click');
expect(wrapper.vm.isModalVisible).toBe(true);
clickHide();
expect(wrapper.vm.isModalVisible).toBe(false);
}); });
it('executes a lock mutation once lock is confirmed', () => { it('executes a lock mutation once lock is confirmed', async () => {
confirmSpy.mockReturnValue(true);
createComponent({}, lockMutationMock); createComponent({}, lockMutationMock);
findLockButton().vm.$emit('click'); findLockButton().vm.$emit('click');
clickSubmit();
expect(lockMutationMock).toHaveBeenCalledWith({ expect(lockMutationMock).toHaveBeenCalledWith({
filePath: 'some/path', filePath: 'some/path',
lock: true, lock: true,
...@@ -98,7 +103,6 @@ describe('LockButton component', () => { ...@@ -98,7 +103,6 @@ describe('LockButton component', () => {
}); });
it('does not execute a lock mutation if lock not confirmed', () => { it('does not execute a lock mutation if lock not confirmed', () => {
confirmSpy.mockReturnValue(false);
createComponent({}, lockMutationMock); createComponent({}, lockMutationMock);
findLockButton().vm.$emit('click'); findLockButton().vm.$emit('click');
......
dast_site_profiles_pipelines:
- table: ci_pipelines
column: ci_pipeline_id
on_delete: async_delete
vulnerability_feedback:
- table: ci_pipelines
column: pipeline_id
on_delete: async_nullify
ci_pipeline_chat_data: ci_pipeline_chat_data:
- table: chat_names - table: chat_names
column: chat_name_id column: chat_name_id
......
...@@ -21426,6 +21426,9 @@ msgstr "" ...@@ -21426,6 +21426,9 @@ msgstr ""
msgid "Lock %{issuableDisplayName}" msgid "Lock %{issuableDisplayName}"
msgstr "" msgstr ""
msgid "Lock File?"
msgstr ""
msgid "Lock memberships to LDAP synchronization" msgid "Lock memberships to LDAP synchronization"
msgstr "" msgstr ""
......
...@@ -100,6 +100,7 @@ describe('GroupItemComponent', () => { ...@@ -100,6 +100,7 @@ describe('GroupItemComponent', () => {
wrapper.destroy(); wrapper.destroy();
group.type = 'project'; group.type = 'project';
group.lastActivityAt = '2017-04-09T18:40:39.101Z';
wrapper = createComponent({ group }); wrapper = createComponent({ group });
expect(wrapper.vm.isGroup).toBe(false); expect(wrapper.vm.isGroup).toBe(false);
......
...@@ -38,6 +38,7 @@ describe('ItemStats', () => { ...@@ -38,6 +38,7 @@ describe('ItemStats', () => {
...mockParentGroupItem, ...mockParentGroupItem,
type: ITEM_TYPE.PROJECT, type: ITEM_TYPE.PROJECT,
starCount: 4, starCount: 4,
lastActivityAt: '2017-04-09T18:40:39.101Z',
}; };
createComponent({ item }); createComponent({ item });
......
...@@ -6,4 +6,10 @@ RSpec.describe Ci::RunnerNamespace do ...@@ -6,4 +6,10 @@ RSpec.describe Ci::RunnerNamespace do
it_behaves_like 'includes Limitable concern' do it_behaves_like 'includes Limitable concern' do
subject { build(:ci_runner_namespace, group: create(:group, :nested), runner: create(:ci_runner, :group)) } subject { build(:ci_runner_namespace, group: create(:group, :nested), runner: create(:ci_runner, :group)) }
end end
it_behaves_like 'cleanup by a loose foreign key' do
let!(:model) { create(:ci_runner_namespace) }
let!(:parent) { model.namespace }
end
end end
...@@ -62,6 +62,10 @@ RSpec.describe GroupChildEntity do ...@@ -62,6 +62,10 @@ RSpec.describe GroupChildEntity do
expect(json[:edit_path]).to eq(edit_project_path(object)) expect(json[:edit_path]).to eq(edit_project_path(object))
end end
it 'includes the last activity at' do
expect(json[:last_activity_at]).to be_present
end
it_behaves_like 'group child json' it_behaves_like 'group child json'
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment