Skip to content
Snippets Groups Projects
Verified Commit 8dcd9d61 authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt :palm_tree: Committed by GitLab
Browse files

Merge branch '497051-lock-button-ui' into 'master'

parents 1e7a8ddb db1ca10e
No related branches found
No related tags found
1 merge request!174896Create UI for LockDirectoryButton component
Pipeline #1579270930 failed
Pipeline: E2E Omnibus GitLab EE

#1579273490

    Pipeline: E2E CNG

    #1579273485

      Pipeline: E2E GDK

      #1579271815

        +30
        Showing
        with 425 additions and 73 deletions
        ......@@ -22,7 +22,7 @@ export default {
        GlButton,
        UploadBlobModal,
        CommitChangesModal,
        LockButton: () => import('ee_component/repository/components/lock_button.vue'),
        LockFileButton: () => import('ee_component/repository/components/lock_file_button.vue'),
        },
        mixins: [getRefMixin, glFeatureFlagMixin()],
        inject: {
        ......@@ -124,7 +124,7 @@ export default {
        <template>
        <div class="gl-mr-3">
        <gl-button-group>
        <lock-button
        <lock-file-button
        v-if="glFeatures.fileLocks"
        :name="name"
        :path="path"
        ......
        ......@@ -32,6 +32,8 @@ export default {
        SourceCodeDownloadDropdown,
        CloneCodeDropdown,
        WebIdeLink: () => import('ee_else_ce/vue_shared/components/web_ide_link.vue'),
        LockDirectoryButton: () =>
        import('ee_component/repository/components/lock_directory_button.vue'),
        },
        directives: {
        GlTooltip: GlTooltipDirective,
        ......@@ -98,6 +100,9 @@ export default {
        isTreeView() {
        return this.$route.name !== 'blobPathDecoded';
        },
        isRoot() {
        return !this.currentPath || this.currentPath === '/';
        },
        getRefType() {
        return this.$route.query.ref_type;
        },
        ......@@ -190,6 +195,7 @@ export default {
        <!-- Tree controls -->
        <div v-if="isTreeView" class="tree-controls gl-mb-3 gl-flex gl-flex-wrap gl-gap-3 sm:gl-mb-0">
        <!-- EE: = render_if_exists 'projects/tree/lock_link' -->
        <lock-directory-button v-if="!isRoot" :project-path="projectPath" :path="currentPath" />
        <gl-button
        v-if="comparePath"
        data-testid="tree-compare-control"
        ......
        <script>
        import { GlButton, GlTooltipDirective, GlModal, GlModalDirective } from '@gitlab/ui';
        import { sprintf, __ } from '~/locale';
        import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
        export default {
        name: 'LockDirectoryButton',
        i18n: {
        fetchError: __('An error occurred while fetching lock information, please try again.'),
        mutationError: __('An error occurred while editing lock information, please try again.'),
        },
        modal: {
        modalTitle: __('Lock directory?'),
        actionPrimary: {
        text: __('Ok'),
        },
        actionCancel: {
        text: __('Cancel'),
        },
        },
        components: {
        GlButton,
        GlModal,
        },
        directives: {
        GlTooltip: GlTooltipDirective,
        GlModalDirective,
        },
        mixins: [glFeatureFlagMixin()],
        props: {
        projectPath: {
        type: String,
        required: true,
        },
        path: {
        type: String,
        required: true,
        },
        },
        data() {
        return {
        canAdminLocks: false,
        canPushCode: false,
        allPathLocks: [],
        pathLock: {},
        user: {
        id: 'gid://gitlab/User/1',
        username: 'root',
        name: __('Administrator'),
        avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80',
        webUrl: '/root',
        webPath: '/root',
        },
        isLocked: false,
        };
        },
        computed: {
        showLockButton() {
        return Boolean(this.glFeatures.fileLocks && this.user.id);
        },
        isLoading() {
        return false;
        },
        buttonLabel() {
        return this.isLocked ? __('Unlock') : __('Lock');
        },
        buttonState() {
        return this.isLocked ? 'unlock' : 'lock';
        },
        isDisabled() {
        return false;
        },
        tooltipText() {
        return '';
        },
        modalId() {
        return `lock-directory-modal-${this.path.replaceAll('/', '-')}`;
        },
        modalContent() {
        return sprintf(__('Are you sure you want to %{action} this directory?'), {
        action: this.buttonLabel.toLowerCase(),
        });
        },
        },
        methods: {
        toggleLock() {
        this.isLocked = !this.isLocked;
        },
        },
        };
        </script>
        <template>
        <span
        v-if="showLockButton"
        class="btn-group"
        :class="{ 'has-tooltip': tooltipText }"
        gl-tooltip
        :title="tooltipText"
        >
        <gl-button
        v-gl-modal-directive="modalId"
        :loading="isLoading"
        :disabled="isDisabled"
        class="path-lock js-path-lock"
        :data-testid="isDisabled ? 'disabled-lock-button' : 'lock-button'"
        :data-state="buttonState"
        >
        {{ buttonLabel }}
        </gl-button>
        <gl-modal
        size="sm"
        :modal-id="modalId"
        :title="$options.modal.modalTitle"
        :action-primary="$options.modal.actionPrimary"
        :action-cancel="$options.modal.actionCancel"
        @primary="toggleLock"
        >
        <p>{{ modalContent }}</p>
        </gl-modal>
        </span>
        </template>
        import { shallowMount } from '@vue/test-utils';
        import LockButton from 'ee_component/repository/components/lock_button.vue';
        import LockFileButton from 'ee_component/repository/components/lock_file_button.vue';
        import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
        const DEFAULT_PROPS = {
        ......@@ -36,12 +36,12 @@ describe('EE BlobButtonGroup component', () => {
        ...DEFAULT_INJECT,
        },
        stubs: {
        LockButton,
        LockFileButton,
        },
        });
        };
        const findLockButton = () => wrapper.findComponent(LockButton);
        const findLockFileButton = () => wrapper.findComponent(LockFileButton);
        beforeEach(() => {
        createComponent();
        ......@@ -57,9 +57,9 @@ describe('EE BlobButtonGroup component', () => {
        });
        it('renders the lock button', () => {
        expect(findLockButton().exists()).toBe(true);
        expect(findLockFileButton().exists()).toBe(true);
        expect(findLockButton().props()).toMatchObject({
        expect(findLockFileButton().props()).toMatchObject({
        canLock: true,
        isLocked: false,
        name: 'some name',
        ......
        import { RouterLinkStub } from '@vue/test-utils';
        import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
        import HeaderArea from '~/repository/components/header_area.vue';
        import LockDirectoryButton from 'ee_component/repository/components/lock_directory_button.vue';
        import CodeDropdown from '~/vue_shared/components/code_dropdown/code_dropdown.vue';
        import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_dropdown.vue';
        import { headerAppInjected } from 'ee_else_ce_jest/repository/mock_data';
        const defaultMockRoute = {
        params: {
        path: '/directory',
        },
        meta: {
        refType: '',
        },
        query: {
        ref_type: '',
        },
        };
        describe('HeaderArea', () => {
        let wrapper;
        const findLockDirectoryButton = () => wrapper.findComponent(LockDirectoryButton);
        const findCodeDropdown = () => wrapper.findComponent(CodeDropdown);
        const findCloneCodeDropdown = () => wrapper.findComponent(CloneCodeDropdown);
        const createComponent = (props = {}, params = { path: '/directory' }, provided = {}) => {
        return shallowMountExtended(HeaderArea, {
        provide: {
        ...headerAppInjected,
        ...provided,
        },
        propsData: {
        projectPath: 'test/project',
        historyLink: '/history',
        refType: 'branch',
        projectId: '123',
        refSelectorValue: 'refs/heads/main',
        ...props,
        },
        stubs: {
        RouterLink: RouterLinkStub,
        },
        mocks: {
        $route: {
        ...defaultMockRoute,
        params,
        },
        },
        });
        };
        beforeEach(() => {
        wrapper = createComponent();
        });
        describe('when rendered for tree view', () => {
        describe('Lock button', () => {
        it('renders Lock directory button for directories inside the project', () => {
        expect(findLockDirectoryButton().exists()).toBe(true);
        });
        it('does not render Lock directory button for root directory', () => {
        wrapper = createComponent({}, 'treePathDecoded', { params: '/' });
        expect(findLockDirectoryButton().exists()).toBe(false);
        });
        });
        describe('CodeDropdown', () => {
        it('renders CodeDropdown component with correct props for desktop layout', () => {
        expect(findCodeDropdown().exists()).toBe(true);
        expect(findCodeDropdown().props('kerberosUrl')).toBe(headerAppInjected.kerberosUrl);
        });
        });
        describe('SourceCodeDownloadDropdown', () => {
        it('renders CloneCodeDropdown component with correct props for mobile layout', () => {
        expect(findCloneCodeDropdown().exists()).toBe(true);
        expect(findCloneCodeDropdown().props('kerberosUrl')).toBe(headerAppInjected.kerberosUrl);
        });
        });
        });
        });
        import { shallowMount } from '@vue/test-utils';
        import { GlButton, GlSprintf, GlModal } from '@gitlab/ui';
        import waitForPromises from 'helpers/wait_for_promises';
        import LockDirectoryButton from 'ee_component/repository/components/lock_directory_button.vue';
        describe('LockDirectoryButton', () => {
        let wrapper;
        const createComponent = ({ fileLocks = true, props = {} } = {}) => {
        wrapper = shallowMount(LockDirectoryButton, {
        provide: {
        glFeatures: {
        fileLocks,
        },
        },
        propsData: {
        projectPath: 'group/project',
        path: 'app/models',
        ...props,
        },
        stubs: {
        GlSprintf,
        GlButton,
        GlModal,
        },
        });
        };
        const findLockDirectoryButton = () => wrapper.findComponent(GlButton);
        const findModal = () => wrapper.findComponent(GlModal);
        beforeEach(() => {
        createComponent();
        });
        describe('component rendering', () => {
        it('does not render when fileLocks feature is not available', async () => {
        createComponent({ fileLocks: false });
        await waitForPromises();
        expect(findLockDirectoryButton().exists()).toBe(false);
        });
        it('does not render when user is not logged in', async () => {
        // temporarily setting the user this way,
        // will change to mocked query in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173576
        wrapper.vm.user = { id: null };
        await waitForPromises();
        expect(findLockDirectoryButton().exists()).toBe(false);
        });
        it('renders lock button when feature is available and user logged in', async () => {
        await waitForPromises();
        expect(findLockDirectoryButton().exists()).toBe(true);
        });
        });
        describe('modal', () => {
        it('shows a modal when clicked', async () => {
        findLockDirectoryButton().trigger('click');
        await waitForPromises();
        expect(findModal().exists()).toBe(true);
        });
        it.each`
        locked | expectedContent
        ${true} | ${'Are you sure you want to unlock this directory?'}
        ${false} | ${'Are you sure you want to lock this directory?'}
        `('displays $expectedContent when isLocked is $locked', async ({ locked, expectedContent }) => {
        await waitForPromises();
        wrapper.vm.isLocked = locked;
        findLockDirectoryButton().trigger('click');
        await waitForPromises();
        expect(findModal().text()).toContain(expectedContent);
        });
        });
        });
        ......@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
        import Vue from 'vue';
        import VueApollo from 'vue-apollo';
        import LockButton from 'ee_component/repository/components/lock_button.vue';
        import LockFileButton from 'ee_component/repository/components/lock_file_button.vue';
        import createMockApollo from 'helpers/mock_apollo_helper';
        import lockPathMutation from '~/repository/mutations/lock_path.mutation.graphql';
        ......@@ -15,7 +15,7 @@ const DEFAULT_PROPS = {
        canLock: true,
        };
        describe('LockButton component', () => {
        describe('LockFileButton component', () => {
        let wrapper;
        const createMockApolloProvider = (resolverMock) => {
        ......@@ -24,7 +24,7 @@ describe('LockButton component', () => {
        };
        const createComponent = (props = {}, lockMutation = jest.fn()) => {
        wrapper = shallowMount(LockButton, {
        wrapper = shallowMount(LockFileButton, {
        apolloProvider: createMockApolloProvider(lockMutation),
        propsData: {
        ...DEFAULT_PROPS,
        ......@@ -36,7 +36,7 @@ describe('LockButton component', () => {
        describe('lock button', () => {
        let lockMutationMock;
        const mockEvent = { preventDefault: jest.fn() };
        const findLockButton = () => wrapper.findComponent(GlButton);
        const findLockFileButton = () => wrapper.findComponent(GlButton);
        const findModal = () => wrapper.findComponent(GlModal);
        const clickSubmit = () => findModal().vm.$emit('primary', mockEvent);
        const clickHide = () => findModal().vm.$emit('hide', mockEvent);
        ......@@ -48,7 +48,7 @@ describe('LockButton component', () => {
        it('disables the lock button if canLock is set to false', () => {
        createComponent({ canLock: false });
        expect(findLockButton().props('disabled')).toBe(true);
        expect(findLockFileButton().props('disabled')).toBe(true);
        });
        it.each`
        ......@@ -58,26 +58,26 @@ describe('LockButton component', () => {
        `('renders the $label button label', ({ isLocked, label }) => {
        createComponent({ isLocked });
        expect(findLockButton().text()).toContain(label);
        expect(findLockFileButton().text()).toContain(label);
        });
        it('sets loading prop to true when LockButton was clicked', async () => {
        it('sets loading prop to true when LockFileButton was clicked', async () => {
        createComponent();
        findLockButton().vm.$emit('click');
        findLockFileButton().vm.$emit('click');
        await clickSubmit();
        expect(findLockButton().props('loading')).toBe(true);
        expect(findLockFileButton().props('loading')).toBe(true);
        });
        it('displays a confirm modal when the lock button is clicked', () => {
        createComponent();
        findLockButton().vm.$emit('click');
        findLockFileButton().vm.$emit('click');
        expect(findModal().text()).toBe('Are you sure you want to lock some_file.js?');
        });
        it('should hide the confirm modal when a hide action is triggered', async () => {
        createComponent();
        await findLockButton().vm.$emit('click');
        await findLockFileButton().vm.$emit('click');
        expect(findModal().props('visible')).toBe(true);
        await clickHide();
        ......@@ -87,7 +87,7 @@ describe('LockButton component', () => {
        it('executes a lock mutation once lock is confirmed', () => {
        lockMutationMock = jest.fn().mockRejectedValue('Test');
        createComponent({}, lockMutationMock);
        findLockButton().vm.$emit('click');
        findLockFileButton().vm.$emit('click');
        clickSubmit();
        expect(lockMutationMock).toHaveBeenCalledWith({
        filePath: 'some/path',
        ......@@ -98,7 +98,7 @@ describe('LockButton component', () => {
        it('does not execute a lock mutation if lock not confirmed', () => {
        createComponent({}, lockMutationMock);
        findLockButton().vm.$emit('click');
        findLockFileButton().vm.$emit('click');
        expect(lockMutationMock).not.toHaveBeenCalled();
        });
        ......
        ......@@ -19,3 +19,49 @@ export const codeOwnersPropsMock = {
        branchRulesPath: '/some/Project/-/settings/repository#js-branch-rules',
        canViewBranchRules: true,
        };
        export const headerAppInjected = {
        canCollaborate: true,
        canEditTree: true,
        canPushCode: true,
        originalBranch: 'main',
        selectedBranch: 'feature/new-ui',
        newBranchPath: '/project/new-branch',
        newTagPath: '/project/new-tag',
        newBlobPath: '/project/new-file',
        forkNewBlobPath: '/project/fork/new-file',
        forkNewDirectoryPath: '/project/fork/new-directory',
        forkUploadBlobPath: '/project/fork/upload',
        uploadPath: '/project/upload',
        newDirPath: '/project/new-directory',
        projectRootPath: '/project/root/path',
        comparePath: undefined,
        isReadmeView: false,
        isFork: false,
        needsToFork: true,
        gitpodEnabled: false,
        isBlob: true,
        showEditButton: true,
        showWebIdeButton: true,
        showGitpodButton: false,
        showPipelineEditorUrl: true,
        webIdeUrl: 'https://gitlab.com/project/-/ide/',
        editUrl: 'https://gitlab.com/project/-/edit/main/',
        pipelineEditorUrl: 'https://gitlab.com/project/-/ci/editor',
        gitpodUrl: 'https://gitpod.io/#https://gitlab.com/project',
        userPreferencesGitpodPath: '/profile/preferences#gitpod',
        userProfileEnableGitpodPath: '/profile/preferences?enable_gitpod=true',
        httpUrl: 'https://gitlab.com/example-group/example-project.git',
        xcodeUrl: 'xcode://clone?repo=https://gitlab.com/example-group/example-project.git',
        sshUrl: 'git@gitlab.com:example-group/example-project.git',
        kerberosUrl: 'https://kerberos@gitlab.com/example-group/example-project.git',
        downloadLinks: [
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.zip',
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.gz',
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.bz2',
        'https://gitlab.com/example-group/example-project/-/releases',
        ],
        downloadArtifacts: [
        'https://gitlab.com/example-group/example-project/-/jobs/artifacts/main/download?job=build',
        ],
        };
        ......@@ -4928,6 +4928,9 @@ msgstr ""
        msgid "AdminUsers|user cap"
        msgstr ""
         
        msgid "Administrator"
        msgstr ""
        msgid "Administrator Account Setup"
        msgstr ""
         
        ......@@ -6009,6 +6012,9 @@ msgstr ""
        msgid "An error occurred while drawing job relationship links."
        msgstr ""
         
        msgid "An error occurred while editing lock information, please try again."
        msgstr ""
        msgid "An error occurred while enabling Service Desk."
        msgstr ""
         
        ......@@ -6057,6 +6063,9 @@ msgstr ""
        msgid "An error occurred while fetching labels, please try again."
        msgstr ""
         
        msgid "An error occurred while fetching lock information, please try again."
        msgstr ""
        msgid "An error occurred while fetching participants"
        msgstr ""
         
        ......@@ -7410,6 +7419,9 @@ msgstr ""
        msgid "Are you sure you want to %{action} %{name}?"
        msgstr ""
         
        msgid "Are you sure you want to %{action} this directory?"
        msgstr ""
        msgid "Are you sure you want to approve %{user}?"
        msgstr ""
         
        ......@@ -32625,6 +32637,9 @@ msgstr ""
        msgid "Lock File?"
        msgstr ""
         
        msgid "Lock directory?"
        msgstr ""
        msgid "Lock discussion"
        msgstr ""
         
        ......@@ -37731,6 +37746,9 @@ msgstr ""
        msgid "Oh no!"
        msgstr ""
         
        msgid "Ok"
        msgstr ""
        msgid "Okay"
        msgstr ""
         
        ......@@ -9,6 +9,7 @@ import CloneCodeDropdown from '~/vue_shared/components/code_dropdown/clone_code_
        import BlobControls from '~/repository/components/header_area/blob_controls.vue';
        import Shortcuts from '~/behaviors/shortcuts/shortcuts';
        import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
        import { headerAppInjected } from 'ee_else_ce_jest/repository/mock_data';
        const defaultMockRoute = {
        params: {
        ......@@ -22,52 +23,6 @@ const defaultMockRoute = {
        },
        };
        const defaultProvided = {
        canCollaborate: true,
        canEditTree: true,
        canPushCode: true,
        originalBranch: 'main',
        selectedBranch: 'feature/new-ui',
        newBranchPath: '/project/new-branch',
        newTagPath: '/project/new-tag',
        newBlobPath: '/project/new-file',
        forkNewBlobPath: '/project/fork/new-file',
        forkNewDirectoryPath: '/project/fork/new-directory',
        forkUploadBlobPath: '/project/fork/upload',
        uploadPath: '/project/upload',
        newDirPath: '/project/new-directory',
        projectRootPath: '/project/root/path',
        comparePath: undefined,
        isReadmeView: false,
        isFork: false,
        needsToFork: true,
        gitpodEnabled: false,
        isBlob: true,
        showEditButton: true,
        showWebIdeButton: true,
        showGitpodButton: false,
        showPipelineEditorUrl: true,
        webIdeUrl: 'https://gitlab.com/project/-/ide/',
        editUrl: 'https://gitlab.com/project/-/edit/main/',
        pipelineEditorUrl: 'https://gitlab.com/project/-/ci/editor',
        gitpodUrl: 'https://gitpod.io/#https://gitlab.com/project',
        userPreferencesGitpodPath: '/profile/preferences#gitpod',
        userProfileEnableGitpodPath: '/profile/preferences?enable_gitpod=true',
        httpUrl: 'https://gitlab.com/example-group/example-project.git',
        xcodeUrl: 'xcode://clone?repo=https://gitlab.com/example-group/example-project.git',
        sshUrl: 'git@gitlab.com:example-group/example-project.git',
        kerberosUrl: 'https://kerberos@gitlab.com/example-group/example-project.git',
        downloadLinks: [
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.zip',
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.gz',
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.bz2',
        'https://gitlab.com/example-group/example-project/-/releases',
        ],
        downloadArtifacts: [
        'https://gitlab.com/example-group/example-project/-/jobs/artifacts/main/download?job=build',
        ],
        };
        describe('HeaderArea', () => {
        let wrapper;
        ......@@ -85,7 +40,7 @@ describe('HeaderArea', () => {
        const createComponent = (props = {}, routeName = 'blobPathDecoded', provided = {}) => {
        return shallowMountExtended(HeaderArea, {
        provide: {
        ...defaultProvided,
        ...headerAppInjected,
        ...provided,
        },
        propsData: {
        ......@@ -171,8 +126,8 @@ describe('HeaderArea', () => {
        describe('CodeDropdown', () => {
        it('renders CodeDropdown component with correct props for desktop layout', () => {
        expect(findCodeDropdown().exists()).toBe(true);
        expect(findCodeDropdown().props('sshUrl')).toBe(defaultProvided.sshUrl);
        expect(findCodeDropdown().props('httpUrl')).toBe(defaultProvided.httpUrl);
        expect(findCodeDropdown().props('sshUrl')).toBe(headerAppInjected.sshUrl);
        expect(findCodeDropdown().props('httpUrl')).toBe(headerAppInjected.httpUrl);
        });
        });
        ......@@ -180,14 +135,14 @@ describe('HeaderArea', () => {
        it('renders SourceCodeDownloadDropdown and CloneCodeDropdown component with correct props for mobile layout', () => {
        expect(findSourceCodeDownloadDropdown().exists()).toBe(true);
        expect(findSourceCodeDownloadDropdown().props('downloadLinks')).toEqual(
        defaultProvided.downloadLinks,
        headerAppInjected.downloadLinks,
        );
        expect(findSourceCodeDownloadDropdown().props('downloadArtifacts')).toEqual(
        defaultProvided.downloadArtifacts,
        headerAppInjected.downloadArtifacts,
        );
        expect(findCloneCodeDropdown().exists()).toBe(true);
        expect(findCloneCodeDropdown().props('sshUrl')).toBe(defaultProvided.sshUrl);
        expect(findCloneCodeDropdown().props('httpUrl')).toBe(defaultProvided.httpUrl);
        expect(findCloneCodeDropdown().props('sshUrl')).toBe(headerAppInjected.sshUrl);
        expect(findCloneCodeDropdown().props('httpUrl')).toBe(headerAppInjected.httpUrl);
        });
        });
        });
        ......
        ......@@ -188,3 +188,49 @@ export const paginatedTreeResponseFactory = ({
        });
        export const axiosMockResponse = { html: 'text', binary: true };
        export const headerAppInjected = {
        canCollaborate: true,
        canEditTree: true,
        canPushCode: true,
        originalBranch: 'main',
        selectedBranch: 'feature/new-ui',
        newBranchPath: '/project/new-branch',
        newTagPath: '/project/new-tag',
        newBlobPath: '/project/new-file',
        forkNewBlobPath: '/project/fork/new-file',
        forkNewDirectoryPath: '/project/fork/new-directory',
        forkUploadBlobPath: '/project/fork/upload',
        uploadPath: '/project/upload',
        newDirPath: '/project/new-directory',
        projectRootPath: '/project/root/path',
        comparePath: undefined,
        isReadmeView: false,
        isFork: false,
        needsToFork: true,
        gitpodEnabled: false,
        isBlob: true,
        showEditButton: true,
        showWebIdeButton: true,
        showGitpodButton: false,
        showPipelineEditorUrl: true,
        webIdeUrl: 'https://gitlab.com/project/-/ide/',
        editUrl: 'https://gitlab.com/project/-/edit/main/',
        pipelineEditorUrl: 'https://gitlab.com/project/-/ci/editor',
        gitpodUrl: 'https://gitpod.io/#https://gitlab.com/project',
        userPreferencesGitpodPath: '/profile/preferences#gitpod',
        userProfileEnableGitpodPath: '/profile/preferences?enable_gitpod=true',
        httpUrl: 'https://gitlab.com/example-group/example-project.git',
        xcodeUrl: 'xcode://clone?repo=https://gitlab.com/example-group/example-project.git',
        sshUrl: 'git@gitlab.com:example-group/example-project.git',
        kerberosUrl: '',
        downloadLinks: [
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.zip',
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.gz',
        'https://gitlab.com/example-group/example-project/-/archive/main/example-project-main.tar.bz2',
        'https://gitlab.com/example-group/example-project/-/releases',
        ],
        downloadArtifacts: [
        'https://gitlab.com/example-group/example-project/-/jobs/artifacts/main/download?job=build',
        ],
        };
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Finish editing this message first!
        Please register or to comment