Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-07 12:09:49 +00:00
parent 84ac2a3fcd
commit 8c189e1d5d
9 changed files with 117 additions and 15 deletions

View File

@ -2,6 +2,7 @@
import { GlButtonGroup, GlButton, GlModalDirective } from '@gitlab/ui'; import { GlButtonGroup, GlButton, GlModalDirective } from '@gitlab/ui';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getRefMixin from '../mixins/get_ref'; import getRefMixin from '../mixins/get_ref';
import DeleteBlobModal from './delete_blob_modal.vue'; import DeleteBlobModal from './delete_blob_modal.vue';
import UploadBlobModal from './upload_blob_modal.vue'; import UploadBlobModal from './upload_blob_modal.vue';
@ -17,11 +18,12 @@ export default {
GlButton, GlButton,
UploadBlobModal, UploadBlobModal,
DeleteBlobModal, DeleteBlobModal,
LockButton: () => import('ee_component/repository/components/lock_button.vue'),
}, },
directives: { directives: {
GlModal: GlModalDirective, GlModal: GlModalDirective,
}, },
mixins: [getRefMixin], mixins: [getRefMixin, glFeatureFlagMixin()],
inject: { inject: {
targetBranch: { targetBranch: {
default: '', default: '',
@ -55,6 +57,18 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
projectPath: {
type: String,
required: true,
},
isLocked: {
type: Boolean,
required: true,
},
canLock: {
type: Boolean,
required: true,
},
}, },
computed: { computed: {
replaceModalId() { replaceModalId() {
@ -76,10 +90,19 @@ export default {
<template> <template>
<div class="gl-mr-3"> <div class="gl-mr-3">
<gl-button-group> <gl-button-group>
<gl-button v-gl-modal="replaceModalId"> <lock-button
v-if="glFeatures.fileLocks"
:name="name"
:path="path"
:project-path="projectPath"
:is-locked="isLocked"
:can-lock="canLock"
data-testid="lock"
/>
<gl-button v-gl-modal="replaceModalId" data-testid="replace">
{{ $options.i18n.replace }} {{ $options.i18n.replace }}
</gl-button> </gl-button>
<gl-button v-gl-modal="deleteModalId"> <gl-button v-gl-modal="deleteModalId" data-testid="delete">
{{ $options.i18n.delete }} {{ $options.i18n.delete }}
</gl-button> </gl-button>
</gl-button-group> </gl-button-group>

View File

@ -75,6 +75,10 @@ export default {
project: { project: {
userPermissions: { userPermissions: {
pushCode: false, pushCode: false,
downloadCode: false,
},
pathLocks: {
nodes: [],
}, },
repository: { repository: {
empty: true, empty: true,
@ -95,9 +99,6 @@ export default {
externalStorageUrl: '', externalStorageUrl: '',
replacePath: '', replacePath: '',
deletePath: '', deletePath: '',
canLock: false,
isLocked: false,
lockLink: '',
forkPath: '', forkPath: '',
simpleViewer: {}, simpleViewer: {},
richViewer: null, richViewer: null,
@ -120,7 +121,7 @@ export default {
return this.isBinary || this.viewer.fileType === 'download'; return this.isBinary || this.viewer.fileType === 'download';
}, },
blobInfo() { blobInfo() {
const nodes = this.project?.repository?.blobs?.nodes; const nodes = this.project?.repository?.blobs?.nodes || [];
return nodes[0] || {}; return nodes[0] || {};
}, },
@ -142,6 +143,14 @@ export default {
const { fileType } = this.viewer; const { fileType } = this.viewer;
return viewerProps(fileType, this.blobInfo); return viewerProps(fileType, this.blobInfo);
}, },
canLock() {
const { pushCode, downloadCode } = this.project.userPermissions;
return pushCode && downloadCode;
},
isLocked() {
return this.project.pathLocks.nodes.some((node) => node.path === this.path);
},
}, },
methods: { methods: {
loadLegacyViewer() { loadLegacyViewer() {
@ -191,6 +200,9 @@ export default {
:delete-path="blobInfo.webPath" :delete-path="blobInfo.webPath"
:can-push-code="project.userPermissions.pushCode" :can-push-code="project.userPermissions.pushCode"
:empty-repo="project.repository.empty" :empty-repo="project.repository.empty"
:project-path="projectPath"
:is-locked="isLocked"
:can-lock="canLock"
/> />
</template> </template>
</blob-header> </blob-header>

View File

@ -170,6 +170,7 @@ export default {
this.apolloQuery(blobInfoQuery, { this.apolloQuery(blobInfoQuery, {
projectPath: this.projectPath, projectPath: this.projectPath,
filePath: this.path, filePath: this.path,
ref: this.ref,
}); });
}, },
apolloQuery(query, variables) { apolloQuery(query, variables) {

View File

@ -1,6 +1,7 @@
mutation toggleLock($projectPath: ID!, $filePath: String!, $lock: Boolean!) { mutation toggleLock($projectPath: ID!, $filePath: String!, $lock: Boolean!) {
projectSetLocked(input: { projectPath: $projectPath, filePath: $filePath, lock: $lock }) { projectSetLocked(input: { projectPath: $projectPath, filePath: $filePath, lock: $lock }) {
project { project {
id
pathLocks { pathLocks {
nodes { nodes {
path path

View File

@ -1,7 +1,14 @@
query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) { query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
project(fullPath: $projectPath) { project(fullPath: $projectPath) {
id
userPermissions { userPermissions {
pushCode pushCode
downloadCode
}
pathLocks {
nodes {
path
}
} }
repository { repository {
empty empty

View File

@ -44,6 +44,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action do before_action do
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml) push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
push_frontend_feature_flag(:consolidated_edit_button, @project, default_enabled: :yaml) push_frontend_feature_flag(:consolidated_edit_button, @project, default_enabled: :yaml)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end end
def new def new

View File

@ -4323,6 +4323,9 @@ msgstr ""
msgid "Are you sure that you want to unarchive this project?" msgid "Are you sure that you want to unarchive this project?"
msgstr "" msgstr ""
msgid "Are you sure you want to %{action} %{name}?"
msgstr ""
msgid "Are you sure you want to cancel editing this comment?" msgid "Are you sure you want to cancel editing this comment?"
msgstr "" msgstr ""

View File

@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import LockButton from 'ee_component/repository/components/lock_button.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import BlobButtonGroup from '~/repository/components/blob_button_group.vue'; import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import DeleteBlobModal from '~/repository/components/delete_blob_modal.vue'; import DeleteBlobModal from '~/repository/components/delete_blob_modal.vue';
@ -12,9 +13,13 @@ const DEFAULT_PROPS = {
replacePath: 'some/replace/path', replacePath: 'some/replace/path',
deletePath: 'some/delete/path', deletePath: 'some/delete/path',
emptyRepo: false, emptyRepo: false,
projectPath: 'some/project/path',
isLocked: false,
canLock: true,
}; };
const DEFAULT_INJECT = { const DEFAULT_INJECT = {
glFeatures: { fileLocks: true },
targetBranch: 'master', targetBranch: 'master',
originalBranch: 'master', originalBranch: 'master',
}; };
@ -43,7 +48,8 @@ describe('BlobButtonGroup component', () => {
const findDeleteBlobModal = () => wrapper.findComponent(DeleteBlobModal); const findDeleteBlobModal = () => wrapper.findComponent(DeleteBlobModal);
const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal); const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal);
const findReplaceButton = () => wrapper.findAll(GlButton).at(0); const findReplaceButton = () => wrapper.find('[data-testid="replace"]');
const findLockButton = () => wrapper.findComponent(LockButton);
it('renders component', () => { it('renders component', () => {
createComponent(); createComponent();
@ -61,6 +67,18 @@ describe('BlobButtonGroup component', () => {
createComponent(); createComponent();
}); });
it('renders the lock button', () => {
expect(findLockButton().exists()).toBe(true);
expect(findLockButton().props()).toMatchObject({
canLock: true,
isLocked: false,
name: 'some name',
path: 'some/path',
projectPath: 'some/project/path',
});
});
it('renders both the replace and delete button', () => { it('renders both the replace and delete button', () => {
expect(wrapper.findAll(GlButton)).toHaveLength(2); expect(wrapper.findAll(GlButton)).toHaveLength(2);
}); });

View File

@ -39,9 +39,6 @@ const simpleMockData = {
externalStorageUrl: 'some_file.js', externalStorageUrl: 'some_file.js',
replacePath: 'some_file.js/replace', replacePath: 'some_file.js/replace',
deletePath: 'some_file.js/delete', deletePath: 'some_file.js/delete',
canLock: true,
isLocked: false,
lockLink: 'some_file.js/lock',
forkPath: 'some_file.js/fork', forkPath: 'some_file.js/fork',
simpleViewer: { simpleViewer: {
fileType: 'text', fileType: 'text',
@ -64,6 +61,7 @@ const richMockData = {
const projectMockData = { const projectMockData = {
userPermissions: { userPermissions: {
pushCode: true, pushCode: true,
downloadCode: true,
}, },
repository: { repository: {
empty: false, empty: false,
@ -77,13 +75,24 @@ const createComponentWithApollo = (mockData = {}, inject = {}) => {
localVue.use(VueApollo); localVue.use(VueApollo);
const defaultPushCode = projectMockData.userPermissions.pushCode; const defaultPushCode = projectMockData.userPermissions.pushCode;
const defaultDownloadCode = projectMockData.userPermissions.downloadCode;
const defaultEmptyRepo = projectMockData.repository.empty; const defaultEmptyRepo = projectMockData.repository.empty;
const { blobs, emptyRepo = defaultEmptyRepo, canPushCode = defaultPushCode } = mockData; const {
blobs,
emptyRepo = defaultEmptyRepo,
canPushCode = defaultPushCode,
canDownloadCode = defaultDownloadCode,
pathLocks = [],
} = mockData;
mockResolver = jest.fn().mockResolvedValue({ mockResolver = jest.fn().mockResolvedValue({
data: { data: {
project: { project: {
userPermissions: { pushCode: canPushCode }, id: '1234',
userPermissions: { pushCode: canPushCode, downloadCode: canDownloadCode },
pathLocks: {
nodes: pathLocks,
},
repository: { repository: {
empty: emptyRepo, empty: emptyRepo,
blobs: { blobs: {
@ -371,7 +380,7 @@ describe('Blob content viewer component', () => {
describe('BlobButtonGroup', () => { describe('BlobButtonGroup', () => {
const { name, path, replacePath, webPath } = simpleMockData; const { name, path, replacePath, webPath } = simpleMockData;
const { const {
userPermissions: { pushCode }, userPermissions: { pushCode, downloadCode },
repository: { empty }, repository: { empty },
} = projectMockData; } = projectMockData;
@ -381,7 +390,7 @@ describe('Blob content viewer component', () => {
fullFactory({ fullFactory({
mockData: { mockData: {
blobInfo: simpleMockData, blobInfo: simpleMockData,
project: { userPermissions: { pushCode }, repository: { empty } }, project: { userPermissions: { pushCode, downloadCode }, repository: { empty } },
}, },
stubs: { stubs: {
BlobContent: true, BlobContent: true,
@ -397,10 +406,37 @@ describe('Blob content viewer component', () => {
replacePath, replacePath,
deletePath: webPath, deletePath: webPath,
canPushCode: pushCode, canPushCode: pushCode,
canLock: true,
isLocked: false,
emptyRepo: empty, emptyRepo: empty,
}); });
}); });
it.each`
canPushCode | canDownloadCode | canLock
${true} | ${true} | ${true}
${false} | ${true} | ${false}
${true} | ${false} | ${false}
`('passes the correct lock states', async ({ canPushCode, canDownloadCode, canLock }) => {
fullFactory({
mockData: {
blobInfo: simpleMockData,
project: {
userPermissions: { pushCode: canPushCode, downloadCode: canDownloadCode },
repository: { empty },
},
},
stubs: {
BlobContent: true,
BlobButtonGroup: true,
},
});
await nextTick();
expect(findBlobButtonGroup().props('canLock')).toBe(canLock);
});
it('does not render if not logged in', async () => { it('does not render if not logged in', async () => {
window.gon.current_user_id = null; window.gon.current_user_id = null;