diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-05 12:08:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-05 12:08:43 +0300 |
commit | 26384c9a61da9922b8fa4b8351d4e42d51661b37 (patch) | |
tree | ef3decbed644db3c97dcdbb5b71d4ade77f3155d /app | |
parent | 79cbe31b18159ea394c6f6e3027c1dc69bdabb75 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
35 files changed, 363 insertions, 207 deletions
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 463d1427805..878b54f7d53 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -374,7 +374,7 @@ export default { <div :data-can-create-note="getNoteableData.current_user.can_create_note" - class="files d-flex prepend-top-default" + class="files d-flex" > <div v-show="showTreeList" diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue index 24542126b07..63ce43a193d 100644 --- a/app/assets/javascripts/diffs/components/compare_versions.vue +++ b/app/assets/javascripts/diffs/components/compare_versions.vue @@ -1,5 +1,4 @@ <script> -/* eslint-disable @gitlab/vue-i18n/no-bare-strings */ import { mapActions, mapGetters, mapState } from 'vuex'; import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui'; import { __ } from '~/locale'; @@ -63,9 +62,6 @@ export default { showDropdowns() { return !this.commit && this.mergeRequestDiffs.length; }, - fileTreeIcon() { - return this.showTreeList ? 'collapse-left' : 'expand-left'; - }, toggleFileBrowserTitle() { return this.showTreeList ? __('Hide file browser') : __('Show file browser'); }, @@ -91,7 +87,7 @@ export default { </script> <template> - <div class="mr-version-controls border-top border-bottom"> + <div class="mr-version-controls border-top"> <div class="mr-version-menus-container content-block" :class="{ @@ -108,17 +104,17 @@ export default { :title="toggleFileBrowserTitle" @click="toggleShowTreeList" > - <icon :name="fileTreeIcon" /> + <icon name="file-tree" /> </button> <div v-if="showDropdowns" class="d-flex align-items-center compare-versions-container"> - Changes between + {{ __('Compare') }} <compare-versions-dropdown :other-versions="mergeRequestDiffs" :merge-request-version="mergeRequestDiff" :show-commit-count="true" class="mr-version-dropdown" /> - and + {{ __('and') }} <compare-versions-dropdown :other-versions="comparableDiffs" :base-version-path="baseVersionPath" diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index ee10a1e92fc..48114f9919c 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -224,7 +224,7 @@ export default { <div v-if="!diffFile.submodule && addMergeRequestButtons" - class="file-actions d-none d-sm-block" + class="file-actions d-none d-sm-flex align-items-center flex-wrap" > <diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" /> <div class="btn-group" role="group"> diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue index 0138790cf0e..9d362ceb429 100644 --- a/app/assets/javascripts/diffs/components/diff_stats.vue +++ b/app/assets/javascripts/diffs/components/diff_stats.vue @@ -22,7 +22,7 @@ export default { }, computed: { filesText() { - return n__('File', 'Files', this.diffFilesLength); + return n__('file', 'files', this.diffFilesLength); }, isCompareVersionsHeader() { return Boolean(this.diffFilesLength); @@ -44,13 +44,21 @@ export default { > <div v-if="hasDiffFiles" class="diff-stats-group"> <icon name="doc-code" class="diff-stats-icon text-secondary" /> - <strong>{{ diffFilesLength }} {{ filesText }}</strong> + <span class="text-secondary bold">{{ diffFilesLength }} {{ filesText }}</span> </div> - <div class="diff-stats-group cgreen"> - <icon name="file-addition" class="diff-stats-icon" /> <strong>{{ addedLines }}</strong> + <div + class="diff-stats-group cgreen d-flex align-items-center" + :class="{ bold: isCompareVersionsHeader }" + > + <span>+</span> + <span class="js-file-addition-line">{{ addedLines }}</span> </div> - <div class="diff-stats-group cred"> - <icon name="file-deletion" class="diff-stats-icon" /> <strong>{{ removedLines }}</strong> + <div + class="diff-stats-group cred d-flex align-items-center" + :class="{ bold: isCompareVersionsHeader }" + > + <span>-</span> + <span class="js-file-deletion-line">{{ removedLines }}</span> </div> </div> </template> diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index 30be2e68e76..b13619a5471 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -58,8 +58,8 @@ export default { this.search = ''; }, }, - searchPlaceholder: sprintf(s__('MergeRequest|Filter files or search with %{modifier_key}+p'), { - modifier_key: /Mac/i.test(navigator.userAgent) ? 'cmd' : 'ctrl', + searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), { + modifier_key: /Mac/i.test(navigator.userAgent) ? '⌘' : 'Ctrl+', }), }; </script> diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue index 88861b7da0e..7abe3be3e99 100644 --- a/app/assets/javascripts/error_tracking/components/error_details.vue +++ b/app/assets/javascripts/error_tracking/components/error_details.vue @@ -54,10 +54,6 @@ export default { type: String, required: true, }, - issueDetailsPath: { - type: String, - required: true, - }, issueStackTracePath: { type: String, required: true, @@ -72,7 +68,7 @@ export default { }, }, apollo: { - GQLerror: { + error: { query, variables() { return { @@ -81,19 +77,19 @@ export default { }; }, pollInterval: 2000, - update: data => data.project.sentryDetailedError, + update: data => data.project.sentryErrors.detailedError, error: () => createFlash(__('Failed to load error details from Sentry.')), result(res) { - if (res.data.project?.sentryDetailedError) { - this.$apollo.queries.GQLerror.stopPolling(); - this.setStatus(this.GQLerror.status); + if (res.data.project?.sentryErrors?.detailedError) { + this.$apollo.queries.error.stopPolling(); + this.setStatus(this.error.status); } }, }, }, data() { return { - GQLerror: null, + error: null, issueCreationInProgress: false, isAlertVisible: false, closedIssueId: null, @@ -101,8 +97,6 @@ export default { }, computed: { ...mapState('details', [ - 'error', - 'loading', 'loadingStacktrace', 'stacktraceData', 'updatingResolveStatus', @@ -114,28 +108,23 @@ export default { return sprintf( __('Reported %{timeAgo} by %{reportedBy}'), { - reportedBy: `<strong>${this.GQLerror.culprit}</strong>`, + reportedBy: `<strong>${this.error.culprit}</strong>`, timeAgo: this.timeFormatted(this.stacktraceData.date_received), }, false, ); }, firstReleaseLink() { - return `${this.error.external_base_url}/releases/${this.GQLerror.firstReleaseShortVersion}`; + return `${this.error.externalBaseUrl}/releases/${this.error.firstReleaseShortVersion}`; }, lastReleaseLink() { - return `${this.error.external_base_url}releases/${this.GQLerror.lastReleaseShortVersion}`; - }, - showDetails() { - return Boolean( - !this.loading && !this.$apollo.queries.GQLerror.loading && this.error && this.GQLerror, - ); + return `${this.error.externalBaseUrl}/releases/${this.error.lastReleaseShortVersion}`; }, showStacktrace() { - return Boolean(!this.loadingStacktrace && this.stacktrace && this.stacktrace.length); + return Boolean(this.stacktrace?.length); }, issueTitle() { - return this.GQLerror.title; + return this.error.title; }, issueDescription() { return sprintf( @@ -144,13 +133,13 @@ export default { ), { description: '# Error Details:\n', - errorUrl: `${this.GQLerror.externalUrl}\n`, - firstSeen: `\n${this.GQLerror.firstSeen}\n`, - lastSeen: `${this.GQLerror.lastSeen}\n`, - countLabel: n__('- Event', '- Events', this.GQLerror.count), - count: `${this.GQLerror.count}\n`, - userCountLabel: n__('- User', '- Users', this.GQLerror.userCount), - userCount: `${this.GQLerror.userCount}\n`, + errorUrl: `${this.error.externalUrl}\n`, + firstSeen: `\n${this.error.firstSeen}\n`, + lastSeen: `${this.error.lastSeen}\n`, + countLabel: n__('- Event', '- Events', this.error.count), + count: `${this.error.count}\n`, + userCountLabel: n__('- User', '- Users', this.error.userCount), + userCount: `${this.error.userCount}\n`, }, false, ); @@ -171,12 +160,10 @@ export default { }, }, mounted() { - this.startPollingDetails(this.issueDetailsPath); this.startPollingStacktrace(this.issueStackTracePath); }, methods: { ...mapActions('details', [ - 'startPollingDetails', 'startPollingStacktrace', 'updateStatus', 'setStatus', @@ -214,10 +201,10 @@ export default { <template> <div> - <div v-if="$apollo.queries.GQLerror.loading || loading" class="py-3"> + <div v-if="$apollo.queries.error.loading" class="py-3"> <gl-loading-icon :size="3" /> </div> - <div v-else-if="showDetails" class="error-details"> + <div v-else-if="error" class="error-details"> <gl-alert v-if="isAlertVisible" @dismiss="isAlertVisible = false"> <gl-sprintf :message=" @@ -232,7 +219,7 @@ export default { <div class="top-area align-items-center justify-content-between py-3"> <span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span> - <div class="d-inline-flex"> + <div class="d-inline-flex ml-lg-auto"> <loading-button :label="ignoreBtnLabel" :loading="updatingIgnoreStatus" @@ -247,10 +234,10 @@ export default { @click="onResolveStatusUpdate" /> <gl-button - v-if="error.gitlab_issue" + v-if="error.gitlabIssuePath" class="ml-2" data-qa-selector="view_issue_button" - :href="error.gitlab_issue" + :href="error.gitlabIssuePath" variant="success" > {{ __('View issue') }} @@ -264,13 +251,13 @@ export default { <gl-form-input class="hidden" name="issue[title]" :value="issueTitle" /> <input name="issue[description]" :value="issueDescription" type="hidden" /> <gl-form-input - :value="GQLerror.sentryId" + :value="error.sentryId" class="hidden" name="issue[sentry_issue_attributes][sentry_issue_identifier]" /> <gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" /> <loading-button - v-if="!error.gitlab_issue" + v-if="!error.gitlabIssuePath" class="btn-success" :label="__('Create issue')" :loading="issueCreationInProgress" @@ -281,8 +268,8 @@ export default { </div> </div> <div> - <tooltip-on-truncate :title="GQLerror.title" truncate-target="child" placement="top"> - <h2 class="text-truncate">{{ GQLerror.title }}</h2> + <tooltip-on-truncate :title="error.title" truncate-target="child" placement="top"> + <h2 class="text-truncate">{{ error.title }}</h2> </tooltip-on-truncate> <template v-if="error.tags"> <gl-badge @@ -297,53 +284,51 @@ export default { </gl-badge> </template> <ul> - <li v-if="GQLerror.gitlabCommit"> + <li v-if="error.gitlabCommit"> <strong class="bold">{{ __('GitLab commit') }}:</strong> - <gl-link :href="GQLerror.gitlabCommitPath"> - <span>{{ GQLerror.gitlabCommit.substr(0, 10) }}</span> + <gl-link :href="error.gitlabCommitPath"> + <span>{{ error.gitlabCommit.substr(0, 10) }}</span> </gl-link> </li> - <li v-if="error.gitlab_issue"> + <li v-if="error.gitlabIssuePath"> <strong class="bold">{{ __('GitLab Issue') }}:</strong> - <gl-link :href="error.gitlab_issue"> - <span>{{ error.gitlab_issue }}</span> + <gl-link :href="error.gitlabIssuePath"> + <span>{{ error.gitlabIssuePath }}</span> </gl-link> </li> <li> <strong class="bold">{{ __('Sentry event') }}:</strong> <gl-link - v-track-event="trackClickErrorLinkToSentryOptions(GQLerror.externalUrl)" + v-track-event="trackClickErrorLinkToSentryOptions(error.externalUrl)" class="d-inline-flex align-items-center" - :href="GQLerror.externalUrl" + :href="error.externalUrl" target="_blank" > - <span class="text-truncate">{{ GQLerror.externalUrl }}</span> + <span class="text-truncate">{{ error.externalUrl }}</span> <icon name="external-link" class="ml-1 flex-shrink-0" /> </gl-link> </li> - <li v-if="GQLerror.firstReleaseShortVersion"> + <li v-if="error.firstReleaseShortVersion"> <strong class="bold">{{ __('First seen') }}:</strong> - {{ formatDate(GQLerror.firstSeen) }} + {{ formatDate(error.firstSeen) }} <gl-link :href="firstReleaseLink" target="_blank"> - <span> - {{ __('Release') }}: {{ GQLerror.firstReleaseShortVersion.substr(0, 10) }} - </span> + <span>{{ __('Release') }}: {{ error.firstReleaseShortVersion.substr(0, 10) }}</span> </gl-link> </li> - <li v-if="GQLerror.lastReleaseShortVersion"> + <li v-if="error.lastReleaseShortVersion"> <strong class="bold">{{ __('Last seen') }}:</strong> - {{ formatDate(GQLerror.lastSeen) }} + {{ formatDate(error.lastSeen) }} <gl-link :href="lastReleaseLink" target="_blank"> - <span>{{ __('Release') }}: {{ GQLerror.lastReleaseShortVersion.substr(0, 10) }}</span> + <span>{{ __('Release') }}: {{ error.lastReleaseShortVersion.substr(0, 10) }}</span> </gl-link> </li> <li> <strong class="bold">{{ __('Events') }}:</strong> - <span>{{ GQLerror.count }}</span> + <span>{{ error.count }}</span> </li> <li> <strong class="bold">{{ __('Users') }}:</strong> - <span>{{ GQLerror.userCount }}</span> + <span>{{ error.userCount }}</span> </li> </ul> @@ -351,7 +336,7 @@ export default { <gl-loading-icon :size="3" /> </div> - <template v-if="showStacktrace"> + <template v-else-if="showStacktrace"> <h3 class="my-4">{{ __('Stack trace') }}</h3> <stacktrace :entries="stacktrace" /> </template> diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index 6f3a5f683f8..1c996cbc13b 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -13,6 +13,7 @@ import { GlDropdownDivider, GlTooltipDirective, GlPagination, + GlButtonGroup, } from '@gitlab/ui'; import AccessorUtils from '~/lib/utils/accessor'; import Icon from '~/vue_shared/components/icon.vue'; @@ -20,12 +21,16 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { __ } from '~/locale'; import _ from 'underscore'; -export const tableDataClass = 'table-col d-flex d-sm-table-cell'; +export const tableDataClass = 'table-col d-flex d-sm-table-cell align-items-center'; export default { FIRST_PAGE: 1, PREV_PAGE: 1, NEXT_PAGE: 2, + statusButtons: [ + { status: 'ignored', icon: 'eye-slash', title: __('Ignore') }, + { status: 'resolved', icon: 'check-circle', title: __('Resolve') }, + ], fields: [ { key: 'error', @@ -48,20 +53,13 @@ export default { { key: 'lastSeen', label: __('Last seen'), - thClass: '', + thClass: 'w-15p', tdClass: `${tableDataClass}`, }, { - key: 'ignore', - label: '', - thClass: 'w-3rem', - tdClass: `${tableDataClass} pl-0`, - }, - { - key: 'resolved', + key: 'status', label: '', - thClass: 'w-3rem', - tdClass: `${tableDataClass} pl-0`, + tdClass: `${tableDataClass} text-right`, }, { key: 'details', @@ -88,6 +86,7 @@ export default { Icon, GlPagination, TimeAgo, + GlButtonGroup, }, directives: { GlTooltip: GlTooltipDirective, @@ -332,25 +331,19 @@ export default { <time-ago :time="errors.item.lastSeen" class="text-secondary" /> </div> </template> - <template #cell(ignore)="errors"> - <gl-button - ref="ignoreError" - v-gl-tooltip.hover - :title="__('Ignore')" - @click="updateIssueStatus(errors.item.id, 'ignored')" - > - <gl-icon name="eye-slash" :size="12" /> - </gl-button> - </template> - <template #cell(resolved)="errors"> - <gl-button - ref="resolveError" - v-gl-tooltip - :title="__('Resolve')" - @click="updateIssueStatus(errors.item.id, 'resolved')" - > - <gl-icon name="check-circle" :size="12" /> - </gl-button> + <template #cell(status)="errors"> + <gl-button-group> + <gl-button + v-for="button in $options.statusButtons" + :key="button.status" + :ref="button.title.toLowerCase() + 'Error'" + v-gl-tooltip.hover + :title="button.title" + @click="updateIssueStatus(errors.item.id, button.status)" + > + <gl-icon :name="button.icon" :size="12" /> + </gl-button> + </gl-button-group> </template> <template #cell(details)="errors"> <gl-button diff --git a/app/assets/javascripts/error_tracking/details.js b/app/assets/javascripts/error_tracking/details.js index a5a7ddc907b..1a92681374b 100644 --- a/app/assets/javascripts/error_tracking/details.js +++ b/app/assets/javascripts/error_tracking/details.js @@ -26,7 +26,6 @@ export default () => { issueId, projectPath, issueUpdatePath, - issueDetailsPath, issueStackTracePath, projectIssuesPath, } = domEl.dataset; @@ -36,7 +35,6 @@ export default () => { issueId, projectPath, issueUpdatePath, - issueDetailsPath, issueStackTracePath, projectIssuesPath, csrfToken: csrf.token, diff --git a/app/assets/javascripts/error_tracking/queries/details.query.graphql b/app/assets/javascripts/error_tracking/queries/details.query.graphql index 488a3ecc3ab..fa579c94257 100644 --- a/app/assets/javascripts/error_tracking/queries/details.query.graphql +++ b/app/assets/javascripts/error_tracking/queries/details.query.graphql @@ -1,21 +1,29 @@ query errorDetails($fullPath: ID!, $errorId: ID!) { project(fullPath: $fullPath) { - sentryDetailedError(id: $errorId) { - id - sentryId - title - userCount - count - status - firstSeen - lastSeen - message - culprit - externalUrl - firstReleaseShortVersion - lastReleaseShortVersion - gitlabCommit - gitlabCommitPath + sentryErrors { + detailedError(id: $errorId) { + id + sentryId + title + userCount + count + status + firstSeen + lastSeen + message + culprit + tags { + level + logger + } + externalUrl + externalBaseUrl + firstReleaseShortVersion + lastReleaseShortVersion + gitlabCommit + gitlabCommitPath + gitlabIssuePath + } } } } diff --git a/app/assets/javascripts/error_tracking/store/details/actions.js b/app/assets/javascripts/error_tracking/store/details/actions.js index 2b216d910ce..5914a79f092 100644 --- a/app/assets/javascripts/error_tracking/store/details/actions.js +++ b/app/assets/javascripts/error_tracking/store/details/actions.js @@ -5,36 +5,11 @@ import Poll from '~/lib/utils/poll'; import { __ } from '~/locale'; let stackTracePoll; -let detailPoll; const stopPolling = poll => { if (poll) poll.stop(); }; -export function startPollingDetails({ commit }, endpoint) { - detailPoll = new Poll({ - resource: service, - method: 'getSentryData', - data: { endpoint }, - successCallback: ({ data }) => { - if (!data) { - return; - } - - commit(types.SET_ERROR, data.error); - commit(types.SET_LOADING, false); - - stopPolling(detailPoll); - }, - errorCallback: () => { - commit(types.SET_LOADING, false); - createFlash(__('Failed to load error details from Sentry.')); - }, - }); - - detailPoll.makeRequest(); -} - export function startPollingStacktrace({ commit }, endpoint) { stackTracePoll = new Poll({ resource: service, diff --git a/app/assets/javascripts/error_tracking/store/details/mutation_types.js b/app/assets/javascripts/error_tracking/store/details/mutation_types.js index a2592253a2d..0dd49e727e6 100644 --- a/app/assets/javascripts/error_tracking/store/details/mutation_types.js +++ b/app/assets/javascripts/error_tracking/store/details/mutation_types.js @@ -1,4 +1,2 @@ -export const SET_ERROR = 'SET_ERRORS'; -export const SET_LOADING = 'SET_LOADING'; export const SET_LOADING_STACKTRACE = 'SET_LOADING_STACKTRACE'; export const SET_STACKTRACE_DATA = 'SET_STACKTRACE_DATA'; diff --git a/app/assets/javascripts/error_tracking/store/details/mutations.js b/app/assets/javascripts/error_tracking/store/details/mutations.js index 6f4720444e0..b2bde96c6a9 100644 --- a/app/assets/javascripts/error_tracking/store/details/mutations.js +++ b/app/assets/javascripts/error_tracking/store/details/mutations.js @@ -1,12 +1,6 @@ import * as types from './mutation_types'; export default { - [types.SET_ERROR](state, data) { - state.error = data; - }, - [types.SET_LOADING](state, loading) { - state.loading = loading; - }, [types.SET_LOADING_STACKTRACE](state, data) { state.loadingStacktrace = data; }, diff --git a/app/assets/javascripts/error_tracking/store/details/state.js b/app/assets/javascripts/error_tracking/store/details/state.js index f53cbe29c67..4a6bafe3114 100644 --- a/app/assets/javascripts/error_tracking/store/details/state.js +++ b/app/assets/javascripts/error_tracking/store/details/state.js @@ -1,7 +1,5 @@ export default () => ({ - error: {}, stacktraceData: {}, - loading: true, loadingStacktrace: true, updatingResolveStatus: false, updatingIgnoreStatus: false, diff --git a/app/assets/javascripts/ide/components/nav_form.vue b/app/assets/javascripts/ide/components/nav_form.vue index 23c068f329d..2ccc84ea5d5 100644 --- a/app/assets/javascripts/ide/components/nav_form.vue +++ b/app/assets/javascripts/ide/components/nav_form.vue @@ -19,15 +19,15 @@ export default { <tabs stop-propagation> <tab active> <template slot="title"> - {{ __('Merge Requests') }} + {{ __('Branches') }} </template> - <merge-request-search-list /> + <branches-search-list /> </tab> <tab> <template slot="title"> - {{ __('Branches') }} + {{ __('Merge Requests') }} </template> - <branches-search-list /> + <merge-request-search-list /> </tab> </tabs> </div> diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue index bcaaa8e09c2..b2fa020fb00 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -51,7 +51,7 @@ export default { </script> <template> - <div class="ide-new-btn d-none"> + <div class="ide-new-btn"> <div :class="{ show: isOpen, diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 5eaff7702f6..7fa48c70f41 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -1224,6 +1224,8 @@ $ide-commit-header-height: 48px; } .ide-new-btn { + display: none; + .btn { padding: 2px 5px; } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index d30f113c01f..24c6fec064a 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -14,9 +14,9 @@ cursor: pointer; @media (min-width: map-get($grid-breakpoints, md)) { - // The `-1` below is to prevent two borders from clashing up against eachother - + // The `+11` is to ensure the file header border shows when scrolled - // the bottom of the compare-versions header and the top of the file header - $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height - 1; + $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height + 11; position: -webkit-sticky; position: sticky; @@ -547,7 +547,7 @@ table.code { .diff-stats { align-items: center; - padding: 0 0.25rem; + padding: 0 1rem; .diff-stats-group { padding: 0 0.25rem; @@ -559,7 +559,7 @@ table.code { &.is-compare-versions-header { .diff-stats-group { - padding: 0 0.5rem; + padding: 0 0.25rem; } } } @@ -1054,8 +1054,8 @@ table.code { .diff-tree-list { position: -webkit-sticky; position: sticky; - $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px; - top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px; + $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 11px; + top: $top-pos; max-height: calc(100vh - #{$top-pos}); z-index: 202; @@ -1092,10 +1092,7 @@ table.code { .tree-list-scroll { max-height: 100%; - padding-top: $grid-size; padding-bottom: $grid-size; - border-top: 1px solid $border-color; - border-bottom: 1px solid $border-color; overflow-y: scroll; overflow-x: auto; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c023c9e5cbd..84daec4fb43 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -708,7 +708,7 @@ .mr-version-controls { position: relative; z-index: 203; - background: $gray-light; + background: $white-light; color: $gl-text-color; margin-top: -1px; @@ -732,7 +732,7 @@ } .content-block { - padding: $gl-padding-top $gl-padding; + padding: $gl-padding; border-bottom: 0; } diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb index 32e1a46e580..f1c0bcd491d 100644 --- a/app/controllers/concerns/confirm_email_warning.rb +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -10,7 +10,7 @@ module ConfirmEmailWarning protected def show_confirm_warning? - html_request? && request.get? && Feature.enabled?(:soft_email_confirmation) + html_request? && request.get? end def set_confirm_warning diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 21ee76d31b2..f99345fa99d 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -11,7 +11,7 @@ class ConfirmationsController < Devise::ConfirmationsController protected def after_resending_confirmation_instructions_path_for(resource) - Feature.enabled?(:soft_email_confirmation) ? stored_location_for(resource) || dashboard_projects_path : users_almost_there_path + stored_location_for(resource) || dashboard_projects_path end def after_confirmation_path_for(resource_name, resource) diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb index 6feade3df03..aa09fcdbe61 100644 --- a/app/controllers/dashboard/snippets_controller.rb +++ b/app/controllers/dashboard/snippets_controller.rb @@ -7,6 +7,10 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController skip_cross_project_access_check :index def index + @snippet_counts = Snippets::CountService + .new(current_user, author: current_user) + .execute + @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope]) .execute .page(params[:page]) diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index d7ae6d2cbb4..b9c7468890b 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -30,6 +30,10 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index + @snippet_counts = Snippets::CountService + .new(current_user, project: @project) + .execute + @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope]) .execute .page(params[:page]) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index b40264bfdf4..29b0c6b29ae 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -53,7 +53,7 @@ class RegistrationsController < Devise::RegistrationsController def welcome return redirect_to new_user_registration_path unless current_user - return redirect_to stored_location_or_dashboard_or_almost_there_path(current_user) if current_user.role.present? && !current_user.setup_for_company.nil? + return redirect_to stored_location_or_dashboard(current_user) if current_user.role.present? && !current_user.setup_for_company.nil? current_user.name = nil if current_user.name == current_user.username end @@ -65,7 +65,7 @@ class RegistrationsController < Devise::RegistrationsController if result[:status] == :success track_experiment_event(:signup_flow, 'end') # We want this event to be tracked when the user is _in_ the experimental group set_flash_message! :notice, :signed_up - redirect_to stored_location_or_dashboard_or_almost_there_path(current_user) + redirect_to stored_location_or_dashboard(current_user) else render :welcome end @@ -112,12 +112,12 @@ class RegistrationsController < Devise::RegistrationsController return users_sign_up_welcome_path if experiment_enabled?(:signup_flow) - stored_location_or_dashboard_or_almost_there_path(user) + stored_location_or_dashboard(user) end def after_inactive_sign_up_path_for(resource) Gitlab::AppLogger.info(user_created_message) - Feature.enabled?(:soft_email_confirmation) ? dashboard_projects_path : users_almost_there_path + dashboard_projects_path end private @@ -179,18 +179,10 @@ class RegistrationsController < Devise::RegistrationsController Gitlab::Utils.to_boolean(params[:terms_opt_in]) end - def confirmed_or_unconfirmed_access_allowed(user) - user.confirmed? || Feature.enabled?(:soft_email_confirmation) || experiment_enabled?(:signup_flow) - end - def stored_location_or_dashboard(user) stored_location_for(user) || dashboard_projects_path end - def stored_location_or_dashboard_or_almost_there_path(user) - confirmed_or_unconfirmed_access_allowed(user) ? stored_location_or_dashboard(user) : users_almost_there_path - end - # Part of an experiment to build a new sign up flow. Will be resolved # with https://gitlab.com/gitlab-org/growth/engineering/issues/64 def choose_layout diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb index 91fc18f4312..5be4f67bde8 100644 --- a/app/helpers/projects/error_tracking_helper.rb +++ b/app/helpers/projects/error_tracking_helper.rb @@ -22,7 +22,6 @@ module Projects::ErrorTrackingHelper { 'issue-id' => issue_id, 'project-path' => project.full_path, - 'issue-details-path' => details_project_error_tracking_index_path(*opts), 'issue-update-path' => update_project_error_tracking_index_path(*opts), 'project-issues-path' => project_issues_path(project), 'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts) diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 2602bb2cf97..eebcbcba2d3 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -74,7 +74,7 @@ module Clusters end def ingress_service - cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system') + cluster.kubeclient.get_service('istio-ingressgateway', Clusters::Kubernetes::ISTIO_SYSTEM_NAMESPACE) end def uninstall_command diff --git a/app/models/serverless/domain_cluster.rb b/app/models/serverless/domain_cluster.rb index 9a1acf7e5c3..94d90d3e305 100644 --- a/app/models/serverless/domain_cluster.rb +++ b/app/models/serverless/domain_cluster.rb @@ -10,6 +10,11 @@ module Serverless belongs_to :knative, class_name: 'Clusters::Applications::Knative', foreign_key: 'clusters_applications_knative_id' belongs_to :creator, class_name: 'User', optional: true + attr_encrypted :key, + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_truncated, + algorithm: 'aes-256-gcm' + validates :pages_domain, :knative, presence: true validates :uuid, presence: true, uniqueness: true, length: { is: Gitlab::Serverless::Domain::UUID_LENGTH }, format: { with: HEX_REGEXP, message: 'only allows hex characters' } diff --git a/app/models/user.rb b/app/models/user.rb index d44e8162abe..a5ef03215d3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1638,13 +1638,6 @@ class User < ApplicationRecord super end - # override from Devise::Confirmable - def confirmation_period_valid? - return false if Feature.disabled?(:soft_email_confirmation) - - super - end - private def default_private_profile_to_false diff --git a/app/services/clusters/kubernetes.rb b/app/services/clusters/kubernetes.rb index d29519999b2..aafea64c820 100644 --- a/app/services/clusters/kubernetes.rb +++ b/app/services/clusters/kubernetes.rb @@ -12,5 +12,7 @@ module Clusters GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding' GITLAB_CROSSPLANE_DATABASE_ROLE_NAME = 'gitlab-crossplane-database-role' GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding' + KNATIVE_SERVING_NAMESPACE = 'knative-serving' + ISTIO_SYSTEM_NAMESPACE = 'istio-system' end end diff --git a/app/services/clusters/kubernetes/configure_istio_ingress_service.rb b/app/services/clusters/kubernetes/configure_istio_ingress_service.rb new file mode 100644 index 00000000000..fe577beaa8a --- /dev/null +++ b/app/services/clusters/kubernetes/configure_istio_ingress_service.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require 'openssl' + +module Clusters + module Kubernetes + class ConfigureIstioIngressService + PASSTHROUGH_RESOURCE = Kubeclient::Resource.new( + mode: 'PASSTHROUGH' + ).freeze + + MTLS_RESOURCE = Kubeclient::Resource.new( + mode: 'MUTUAL', + privateKey: '/etc/istio/ingressgateway-certs/tls.key', + serverCertificate: '/etc/istio/ingressgateway-certs/tls.crt', + caCertificates: '/etc/istio/ingressgateway-ca-certs/cert.pem' + ).freeze + + def initialize(cluster:) + @cluster = cluster + @platform = cluster.platform + @kubeclient = platform.kubeclient + @knative = cluster.application_knative + end + + def execute + return configure_certificates if serverless_domain_cluster + + configure_passthrough + end + + private + + attr_reader :cluster, :platform, :kubeclient, :knative + + def serverless_domain_cluster + knative&.serverless_domain_cluster + end + + def configure_certificates + create_or_update_istio_cert_and_key + set_gateway_wildcard_https(MTLS_RESOURCE) + end + + def create_or_update_istio_cert_and_key + name = OpenSSL::X509::Name.parse("CN=#{knative.hostname}") + + key = OpenSSL::PKey::RSA.new(2048) + + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 0 + cert.not_before = Time.now + cert.not_after = Time.now + 1000.years + + cert.public_key = key.public_key + cert.subject = name + cert.issuer = name + cert.sign(key, OpenSSL::Digest::SHA256.new) + + serverless_domain_cluster.update!( + key: key.to_pem, + certificate: cert.to_pem + ) + + kubeclient.create_or_update_secret(istio_ca_certs_resource) + kubeclient.create_or_update_secret(istio_certs_resource) + end + + def istio_ca_certs_resource + Gitlab::Kubernetes::GenericSecret.new( + 'istio-ingressgateway-ca-certs', + { + 'cert.pem': Base64.strict_encode64(serverless_domain_cluster.certificate) + }, + Clusters::Kubernetes::ISTIO_SYSTEM_NAMESPACE + ).generate + end + + def istio_certs_resource + Gitlab::Kubernetes::TlsSecret.new( + 'istio-ingressgateway-certs', + serverless_domain_cluster.certificate, + serverless_domain_cluster.key, + Clusters::Kubernetes::ISTIO_SYSTEM_NAMESPACE + ).generate + end + + def set_gateway_wildcard_https(tls_resource) + gateway_resource = gateway + gateway_resource.spec.servers.each do |server| + next unless server.hosts == ['*'] && server.port.name == 'https' + + server.tls = tls_resource + end + kubeclient.update_gateway(gateway_resource) + end + + def configure_passthrough + set_gateway_wildcard_https(PASSTHROUGH_RESOURCE) + end + + def gateway + kubeclient.get_gateway('knative-ingress-gateway', Clusters::Kubernetes::KNATIVE_SERVING_NAMESPACE) + end + end + end +end diff --git a/app/services/snippets/count_service.rb b/app/services/snippets/count_service.rb new file mode 100644 index 00000000000..9a3d33c75cf --- /dev/null +++ b/app/services/snippets/count_service.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# Service for calculating visible Snippet counts via one query +# for the given user or project. +# +# Authorisation level checks will be included, ensuring the correct +# counts will be returned for the given user (if any). +# +# Basic usage: +# +# user = User.find(1) +# +# Snippets::CountService.new(user, author: user).execute +# #=> { +# are_public: 1, +# are_internal: 1, +# are_private: 1, +# all: 3 +# } +# +# Counts can be scoped to a project: +# +# user = User.find(1) +# project = Project.find(1) +# +# Snippets::CountService.new(user, project: project).execute +# #=> { +# are_public: 1, +# are_internal: 1, +# are_private: 0, +# all: 2 +# } +# +# Either a project or an author *must* be supplied. +module Snippets + class CountService + def initialize(current_user, author: nil, project: nil) + if !author && !project + raise( + ArgumentError, 'Must provide either an author or a project' + ) + end + + @snippets_finder = SnippetsFinder.new(current_user, author: author, project: project) + end + + def execute + counts = snippet_counts + return {} unless counts + + counts.slice( + :are_public, + :are_private, + :are_internal, + :are_public_or_internal, + :total + ) + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def snippet_counts + @snippets_finder.execute + .reorder(nil) + .select(" + count(case when snippets.visibility_level=#{Snippet::PUBLIC} and snippets.secret is FALSE then 1 else null end) as are_public, + count(case when snippets.visibility_level=#{Snippet::INTERNAL} then 1 else null end) as are_internal, + count(case when snippets.visibility_level=#{Snippet::PRIVATE} then 1 else null end) as are_private, + count(case when visibility_level=#{Snippet::PUBLIC} OR visibility_level=#{Snippet::INTERNAL} then 1 else null end) as are_public_or_internal, + count(*) as total + ") + .first + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 69155b6c04d..05214346496 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -5,7 +5,7 @@ = render 'dashboard/snippets_head' - if current_user.snippets.exists? - = render partial: 'snippets/snippets_scope_menu', locals: { include_private: true } + = render partial: 'snippets/snippets_scope_menu', locals: { include_private: true, counts: @snippet_counts } - if current_user.snippets.exists? = render partial: 'shared/snippets/list', locals: { link_project: true } diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index ed56cc8289c..a505b34f46c 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -5,7 +5,7 @@ - if current_user .top-area - include_private = @project.team.member?(current_user) || current_user.admin? - = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } + = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private, counts: @snippet_counts } - if new_project_snippet_link.present? .nav-controls diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml index cb59b11ca2b..e9c9ca6e856 100644 --- a/app/views/snippets/_snippets_scope_menu.html.haml +++ b/app/views/snippets/_snippets_scope_menu.html.haml @@ -7,25 +7,25 @@ = _("All") %span.badge.badge-pill - if include_private - = subject.snippets.count + = counts[:total] - else - = subject.snippets.public_and_internal_only.count + = counts[:are_public_or_internal] - if include_private %li{ class: active_when(params[:scope] == "are_private") } = link_to subject_snippets_path(subject, scope: 'are_private') do = _("Private") %span.badge.badge-pill - = subject.snippets.are_private.count + = counts[:are_private] %li{ class: active_when(params[:scope] == "are_internal") } = link_to subject_snippets_path(subject, scope: 'are_internal') do = _("Internal") %span.badge.badge-pill - = subject.snippets.are_internal.count + = counts[:are_internal] %li{ class: active_when(params[:scope] == "are_public") } = link_to subject_snippets_path(subject, scope: 'are_public') do = _("Public") %span.badge.badge-pill - = subject.snippets.are_public.count + = counts[:are_public] diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index c93c312438b..0426a0b8fbb 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -231,6 +231,12 @@ :latency_sensitive: :resource_boundary: :unknown :weight: 1 +- :name: gcp_cluster:cluster_configure_istio + :feature_category: :kubernetes_management + :has_external_dependencies: true + :latency_sensitive: + :resource_boundary: :unknown + :weight: 1 - :name: gcp_cluster:cluster_install_app :feature_category: :kubernetes_management :has_external_dependencies: true diff --git a/app/workers/cluster_configure_istio_worker.rb b/app/workers/cluster_configure_istio_worker.rb new file mode 100644 index 00000000000..dfdd408f286 --- /dev/null +++ b/app/workers/cluster_configure_istio_worker.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class ClusterConfigureIstioWorker + include ApplicationWorker + include ClusterQueue + + worker_has_external_dependencies! + + def perform(cluster_id) + Clusters::Cluster.find_by_id(cluster_id).try do |cluster| + Clusters::Kubernetes::ConfigureIstioIngressService.new(cluster: cluster).execute + end + end +end |