Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-07 15:09:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-07 15:09:49 +0300
commit8c189e1d5deaaf3f356684319e1ee101c60bc5cc (patch)
tree85e626141059c57f08f5dffec18ea864ce37c708
parent84ac2a3fcdaa8df2c35c54b1738d8e943263d4bb (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/repository/components/blob_button_group.vue29
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue20
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue1
-rw-r--r--app/assets/javascripts/repository/mutations/lock_path.mutation.graphql1
-rw-r--r--app/assets/javascripts/repository/queries/blob_info.query.graphql7
-rw-r--r--app/controllers/projects/blob_controller.rb1
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/frontend/repository/components/blob_button_group_spec.js20
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js50
9 files changed, 117 insertions, 15 deletions
diff --git a/app/assets/javascripts/repository/components/blob_button_group.vue b/app/assets/javascripts/repository/components/blob_button_group.vue
index 273825b996a..4e7ca7b17e4 100644
--- a/app/assets/javascripts/repository/components/blob_button_group.vue
+++ b/app/assets/javascripts/repository/components/blob_button_group.vue
@@ -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>
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 94cc1dfd9ea..665b0698cc0 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -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>
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 82c18d13a6a..fa358a75cc1 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -170,6 +170,7 @@ export default {
this.apolloQuery(blobInfoQuery, {
projectPath: this.projectPath,
filePath: this.path,
+ ref: this.ref,
});
},
apolloQuery(query, variables) {
diff --git a/app/assets/javascripts/repository/mutations/lock_path.mutation.graphql b/app/assets/javascripts/repository/mutations/lock_path.mutation.graphql
index 68ff22566e3..eaebc4ddf17 100644
--- a/app/assets/javascripts/repository/mutations/lock_path.mutation.graphql
+++ b/app/assets/javascripts/repository/mutations/lock_path.mutation.graphql
@@ -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
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index 4dba6869194..45f07f7dc58 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -1,7 +1,14 @@
query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
project(fullPath: $projectPath) {
+ id
userPermissions {
pushCode
+ downloadCode
+ }
+ pathLocks {
+ nodes {
+ path
+ }
}
repository {
empty
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 08066acb45c..acf6b6116b8 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2cf5e98c41e..b1c09dee78d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/frontend/repository/components/blob_button_group_spec.js b/spec/frontend/repository/components/blob_button_group_spec.js
index a449fd6f06c..f2e54653333 100644
--- a/spec/frontend/repository/components/blob_button_group_spec.js
+++ b/spec/frontend/repository/components/blob_button_group_spec.js
@@ -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);
});
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index e8d6e866248..d462995328b 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -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;