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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,6 +44,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:refactor_blob_viewer, @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
def new

View File

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

View File

@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
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 BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import DeleteBlobModal from '~/repository/components/delete_blob_modal.vue';
@ -12,9 +13,13 @@ const DEFAULT_PROPS = {
replacePath: 'some/replace/path',
deletePath: 'some/delete/path',
emptyRepo: false,
projectPath: 'some/project/path',
isLocked: false,
canLock: true,
};
const DEFAULT_INJECT = {
glFeatures: { fileLocks: true },
targetBranch: 'master',
originalBranch: 'master',
};
@ -43,7 +48,8 @@ describe('BlobButtonGroup component', () => {
const findDeleteBlobModal = () => wrapper.findComponent(DeleteBlobModal);
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', () => {
createComponent();
@ -61,6 +67,18 @@ describe('BlobButtonGroup component', () => {
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', () => {
expect(wrapper.findAll(GlButton)).toHaveLength(2);
});

View File

@ -39,9 +39,6 @@ const simpleMockData = {
externalStorageUrl: 'some_file.js',
replacePath: 'some_file.js/replace',
deletePath: 'some_file.js/delete',
canLock: true,
isLocked: false,
lockLink: 'some_file.js/lock',
forkPath: 'some_file.js/fork',
simpleViewer: {
fileType: 'text',
@ -64,6 +61,7 @@ const richMockData = {
const projectMockData = {
userPermissions: {
pushCode: true,
downloadCode: true,
},
repository: {
empty: false,
@ -77,13 +75,24 @@ const createComponentWithApollo = (mockData = {}, inject = {}) => {
localVue.use(VueApollo);
const defaultPushCode = projectMockData.userPermissions.pushCode;
const defaultDownloadCode = projectMockData.userPermissions.downloadCode;
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({
data: {
project: {
userPermissions: { pushCode: canPushCode },
id: '1234',
userPermissions: { pushCode: canPushCode, downloadCode: canDownloadCode },
pathLocks: {
nodes: pathLocks,
},
repository: {
empty: emptyRepo,
blobs: {
@ -371,7 +380,7 @@ describe('Blob content viewer component', () => {
describe('BlobButtonGroup', () => {
const { name, path, replacePath, webPath } = simpleMockData;
const {
userPermissions: { pushCode },
userPermissions: { pushCode, downloadCode },
repository: { empty },
} = projectMockData;
@ -381,7 +390,7 @@ describe('Blob content viewer component', () => {
fullFactory({
mockData: {
blobInfo: simpleMockData,
project: { userPermissions: { pushCode }, repository: { empty } },
project: { userPermissions: { pushCode, downloadCode }, repository: { empty } },
},
stubs: {
BlobContent: true,
@ -397,10 +406,37 @@ describe('Blob content viewer component', () => {
replacePath,
deletePath: webPath,
canPushCode: pushCode,
canLock: true,
isLocked: false,
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 () => {
window.gon.current_user_id = null;