diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-10 12:08:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-10 12:08:10 +0300 |
commit | 82fa8a3d1e8466ef36b58604d20fcc145ea12118 (patch) | |
tree | c5c0286537405c2fa7719ecce3ed0d73d947c555 /app | |
parent | 232655bf32cd474d54de357b65ef43d77271117c (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
12 files changed, 219 insertions, 35 deletions
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 5e8b80cd959..8b44ccfd276 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -10,6 +10,11 @@ import { } from '@gitlab/ui'; import httpStatusCodes from '~/lib/utils/http_status'; + +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import projectQuery from '../queries/project_boards.query.graphql'; +import groupQuery from '../queries/group_boards.query.graphql'; + import boardsStore from '../stores/boards_store'; import BoardForm from './board_form.vue'; @@ -88,8 +93,9 @@ export default { }, data() { return { - loading: true, hasScrollFade: false, + loadingBoards: 0, + loadingRecentBoards: false, scrollFadeInitialized: false, boards: [], recentBoards: [], @@ -102,6 +108,12 @@ export default { }; }, computed: { + parentType() { + return this.groupId ? 'group' : 'project'; + }, + loading() { + return this.loadingRecentBoards && this.loadingBoards; + }, currentPage() { return this.state.currentPage; }, @@ -147,49 +159,71 @@ export default { return; } - const recentBoardsPromise = new Promise((resolve, reject) => - boardsStore - .recentBoards() - .then(resolve) - .catch(err => { - /** - * If user is unauthorized we'd still want to resolve the - * request to display all boards. - */ - if (err.response.status === httpStatusCodes.UNAUTHORIZED) { - resolve({ data: [] }); // recent boards are empty - return; - } - reject(err); - }), - ); + this.$apollo.addSmartQuery('boards', { + variables() { + return { fullPath: this.state.endpoints.fullPath }; + }, + query() { + return this.groupId ? groupQuery : projectQuery; + }, + loadingKey: 'loadingBoards', + update(data) { + if (!data?.[this.parentType]) { + return []; + } + return data[this.parentType].boards.edges.map(({ node }) => ({ + id: getIdFromGraphQLId(node.id), + name: node.name, + })); + }, + }); - Promise.all([boardsStore.allBoards(), recentBoardsPromise]) - .then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data]) - .then(([allBoardsJson, recentBoardsJson]) => { - this.loading = false; - this.boards = allBoardsJson; - this.recentBoards = recentBoardsJson; + this.loadingRecentBoards = true; + boardsStore + .recentBoards() + .then(res => { + this.recentBoards = res.data; + }) + .catch(err => { + /** + * If user is unauthorized we'd still want to resolve the + * request to display all boards. + */ + if (err?.response?.status === httpStatusCodes.UNAUTHORIZED) { + this.recentBoards = []; // recent boards are empty + return; + } + throw err; }) .then(() => this.$nextTick()) // Wait for boards list in DOM .then(() => { this.setScrollFade(); }) - .catch(() => { - this.loading = false; + .catch(() => {}) + .finally(() => { + this.loadingRecentBoards = false; }); }, isScrolledUp() { const { content } = this.$refs; + + if (!content) { + return false; + } + const currentPosition = this.contentClientHeight + content.scrollTop; - return content && currentPosition < this.maxPosition; + return currentPosition < this.maxPosition; }, initScrollFade() { - this.scrollFadeInitialized = true; - const { content } = this.$refs; + if (!content) { + return; + } + + this.scrollFadeInitialized = true; + this.contentClientHeight = content.clientHeight; this.maxPosition = content.scrollHeight; }, diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index f1b481fc386..f72fc8d54b3 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -98,6 +98,7 @@ export default () => { listsEndpoint: this.listsEndpoint, bulkUpdatePath: this.bulkUpdatePath, boardId: this.boardId, + fullPath: $boardApp.dataset.fullPath, }); boardsStore.rootPath = this.boardsEndpoint; diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js index 8d22f009784..73d37459bfe 100644 --- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js +++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js @@ -1,7 +1,15 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; import { parseBoolean } from '~/lib/utils/common_utils'; import BoardsSelector from '~/boards/components/boards_selector.vue'; +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + export default () => { const boardsSwitcherElement = document.getElementById('js-multiple-boards-switcher'); return new Vue({ @@ -9,6 +17,7 @@ export default () => { components: { BoardsSelector, }, + apolloProvider, data() { const { dataset } = boardsSwitcherElement; diff --git a/app/assets/javascripts/boards/queries/board.fragment.graphql b/app/assets/javascripts/boards/queries/board.fragment.graphql new file mode 100644 index 00000000000..48f55e899bf --- /dev/null +++ b/app/assets/javascripts/boards/queries/board.fragment.graphql @@ -0,0 +1,4 @@ +fragment BoardFragment on Board { + id, + name +} diff --git a/app/assets/javascripts/boards/queries/group_boards.query.graphql b/app/assets/javascripts/boards/queries/group_boards.query.graphql new file mode 100644 index 00000000000..74c224add7d --- /dev/null +++ b/app/assets/javascripts/boards/queries/group_boards.query.graphql @@ -0,0 +1,13 @@ +#import "ee_else_ce/boards/queries/board.fragment.graphql" + +query group_boards($fullPath: ID!) { + group(fullPath: $fullPath) { + boards { + edges { + node { + ...BoardFragment + } + } + } + } +} diff --git a/app/assets/javascripts/boards/queries/project_boards.query.graphql b/app/assets/javascripts/boards/queries/project_boards.query.graphql new file mode 100644 index 00000000000..a1326bd5eff --- /dev/null +++ b/app/assets/javascripts/boards/queries/project_boards.query.graphql @@ -0,0 +1,13 @@ +#import "ee_else_ce/boards/queries/board.fragment.graphql" + +query project_boards($fullPath: ID!) { + project(fullPath: $fullPath) { + boards { + edges { + node { + ...BoardFragment + } + } + } + } +} diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 2a5571543fb..2a2cff3d07d 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -45,7 +45,14 @@ const boardsStore = { }, multiSelect: { list: [] }, - setEndpoints({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) { + setEndpoints({ + boardsEndpoint, + listsEndpoint, + bulkUpdatePath, + boardId, + recentBoardsEndpoint, + fullPath, + }) { const listsEndpointGenerate = `${listsEndpoint}/generate.json`; this.state.endpoints = { boardsEndpoint, @@ -53,6 +60,7 @@ const boardsStore = { listsEndpoint, listsEndpointGenerate, bulkUpdatePath, + fullPath, recentBoardsEndpoint: `${recentBoardsEndpoint}.json`, }; }, @@ -542,10 +550,6 @@ const boardsStore = { return axios.post(endpoint); }, - allBoards() { - return axios.get(this.generateBoardsPath()); - }, - recentBoards() { return axios.get(this.state.endpoints.recentBoardsEndpoint); }, diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 6994f83bce0..faaa65b1a16 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -165,6 +165,16 @@ export default { showContainerRegistryPublicNote() { return this.visibilityLevel === visibilityOptions.PUBLIC; }, + + repositoryHelpText() { + if (this.visibilityLevel === visibilityOptions.PRIVATE) { + return s__('ProjectSettings|View and edit files in this project'); + } + + return s__( + 'ProjectSettings|View and edit files in this project. Non-project members will only have read access', + ); + }, }, watch: { @@ -225,6 +235,7 @@ export default { <div> <div class="project-visibility-setting"> <project-setting-row + ref="project-visibility-settings" :help-path="visibilityHelpPath" :label="s__('ProjectSettings|Project visibility')" > @@ -270,6 +281,7 @@ export default { </div> <div :class="{ 'highlight-changes': highlightChangesClass }" class="project-feature-settings"> <project-setting-row + ref="issues-settings" :label="s__('ProjectSettings|Issues')" :help-text="s__('ProjectSettings|Lightweight issue tracking system for this project')" > @@ -280,8 +292,9 @@ export default { /> </project-setting-row> <project-setting-row + ref="repository-settings" :label="s__('ProjectSettings|Repository')" - :help-text="s__('ProjectSettings|View and edit files in this project')" + :help-text="repositoryHelpText" > <project-feature-setting v-model="repositoryAccessLevel" @@ -291,6 +304,7 @@ export default { </project-setting-row> <div class="project-feature-setting-group"> <project-setting-row + ref="merge-request-settings" :label="s__('ProjectSettings|Merge requests')" :help-text="s__('ProjectSettings|Submit changes to be merged upstream')" > @@ -302,6 +316,7 @@ export default { /> </project-setting-row> <project-setting-row + ref="fork-settings" :label="s__('ProjectSettings|Forks')" :help-text=" s__('ProjectSettings|Allow users to make copies of your repository to a new project') @@ -315,6 +330,7 @@ export default { /> </project-setting-row> <project-setting-row + ref="pipeline-settings" :label="s__('ProjectSettings|Pipelines')" :help-text="s__('ProjectSettings|Build, test, and deploy your changes')" > @@ -327,6 +343,7 @@ export default { </project-setting-row> <project-setting-row v-if="registryAvailable" + ref="container-registry-settings" :help-path="registryHelpPath" :label="s__('ProjectSettings|Container registry')" :help-text=" @@ -348,6 +365,7 @@ export default { </project-setting-row> <project-setting-row v-if="lfsAvailable" + ref="git-lfs-settings" :help-path="lfsHelpPath" :label="s__('ProjectSettings|Git Large File Storage')" :help-text=" @@ -362,6 +380,7 @@ export default { </project-setting-row> <project-setting-row v-if="packagesAvailable" + ref="package-settings" :help-path="packagesHelpPath" :label="s__('ProjectSettings|Packages')" :help-text=" @@ -376,6 +395,7 @@ export default { </project-setting-row> </div> <project-setting-row + ref="wiki-settings" :label="s__('ProjectSettings|Wiki')" :help-text="s__('ProjectSettings|Pages for project documentation')" > @@ -386,6 +406,7 @@ export default { /> </project-setting-row> <project-setting-row + ref="snippet-settings" :label="s__('ProjectSettings|Snippets')" :help-text="s__('ProjectSettings|Share code pastes with others out of Git repository')" > @@ -397,6 +418,7 @@ export default { </project-setting-row> <project-setting-row v-if="pagesAvailable && pagesAccessControlEnabled" + ref="pages-settings" :help-path="pagesHelpPath" :label="s__('ProjectSettings|Pages')" :help-text=" @@ -410,7 +432,7 @@ export default { /> </project-setting-row> </div> - <project-setting-row v-if="canDisableEmails" class="mb-3"> + <project-setting-row v-if="canDisableEmails" ref="email-settings" class="mb-3"> <label class="js-emails-disabled"> <input :value="emailsDisabled" type="hidden" name="project[emails_disabled]" /> <input v-model="emailsDisabled" type="checkbox" /> diff --git a/app/assets/javascripts/snippets/components/snippet_description_edit.vue b/app/assets/javascripts/snippets/components/snippet_description_edit.vue new file mode 100644 index 00000000000..5b70ac5b715 --- /dev/null +++ b/app/assets/javascripts/snippets/components/snippet_description_edit.vue @@ -0,0 +1,72 @@ +<script> +import { GlFormInput } from '@gitlab/ui'; +import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import setupCollapsibleInputs from '~/snippet/collapsible_input'; + +export default { + components: { + GlFormInput, + MarkdownField, + }, + props: { + description: { + type: String, + default: '', + required: false, + }, + markdownPreviewPath: { + type: String, + required: true, + }, + markdownDocsPath: { + type: String, + required: true, + }, + }, + data() { + return { + text: this.description, + }; + }, + mounted() { + setupCollapsibleInputs(); + }, +}; +</script> +<template> + <div class="form-group js-description-input"> + <label>{{ s__('Snippets|Description (optional)') }}</label> + <div class="js-collapsible-input"> + <div class="js-collapsed" :class="{ 'd-none': text }"> + <gl-form-input + class="form-control" + :placeholder=" + s__( + 'Snippets|Optionally add a description about what your snippet does or how to use it…', + ) + " + data-qa-selector="description_placeholder" + /> + </div> + <markdown-field + class="js-expanded" + :class="{ 'd-none': !text }" + :markdown-preview-path="markdownPreviewPath" + :markdown-docs-path="markdownDocsPath" + > + <textarea + id="snippet-description" + slot="textarea" + v-model="text" + class="note-textarea js-gfm-input js-autosize markdown-area + qa-description-textarea" + dir="auto" + data-supports-quick-actions="false" + :aria-label="__('Description')" + :placeholder="__('Write a comment or drag your files here…')" + > + </textarea> + </markdown-field> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index c8d69143f8d..df86725c025 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -212,6 +212,8 @@ export default { return new MRWidgetService(this.getServiceEndpoints(store)); }, checkStatus(cb, isRebased) { + if (document.visibilityState !== 'visible') return Promise.resolve(); + return this.service .checkStatus() .then(({ data }) => { diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index d3950219f3f..8bb079e6447 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -13,6 +13,7 @@ module BoardsHelper disabled: (!can?(current_user, :create_non_backlog_issues, board)).to_s, issue_link_base: build_issue_link_base, root_path: root_path, + full_path: full_path, bulk_update_path: @bulk_issues_path, default_avatar: image_path(default_avatar), time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s, @@ -20,6 +21,14 @@ module BoardsHelper } end + def full_path + if board.group_board? + @group.full_path + else + @project.full_path + end + end + def build_issue_link_base if board.group_board? "#{group_path(@board.group)}/:project_path/issues" diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index ee47acc6041..15d60fe9cd8 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -313,6 +313,7 @@ class ProjectPolicy < BasePolicy enable :daily_statistics enable :admin_operations enable :read_deploy_token + enable :create_deploy_token end rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror |