Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
84ac2a3fcd
commit
8c189e1d5d
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -170,6 +170,7 @@ export default {
|
|||
this.apolloQuery(blobInfoQuery, {
|
||||
projectPath: this.projectPath,
|
||||
filePath: this.path,
|
||||
ref: this.ref,
|
||||
});
|
||||
},
|
||||
apolloQuery(query, variables) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
|
||||
project(fullPath: $projectPath) {
|
||||
id
|
||||
userPermissions {
|
||||
pushCode
|
||||
downloadCode
|
||||
}
|
||||
pathLocks {
|
||||
nodes {
|
||||
path
|
||||
}
|
||||
}
|
||||
repository {
|
||||
empty
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue