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 { 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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue