summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml1
-rw-r--r--.gitlab/issue_templates/Deprecations.md2
-rw-r--r--app/assets/javascripts/members/components/avatars/user_avatar.vue14
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue88
-rw-r--r--app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue20
-rw-r--r--app/assets/javascripts/repository/components/blob_viewers/image_viewer.vue14
-rw-r--r--app/assets/javascripts/repository/components/blob_viewers/index.js31
-rw-r--r--app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue14
-rw-r--r--app/assets/javascripts/repository/components/blob_viewers/pdf_viewer.vue14
-rw-r--r--app/assets/javascripts/repository/components/blob_viewers/video_viewer.vue9
-rw-r--r--app/assets/javascripts/repository/constants.js51
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer.vue14
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/merge_request.rb35
-rw-r--r--app/serializers/member_user_entity.rb1
-rw-r--r--app/services/issuable/common_system_notes_service.rb2
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml4
-rw-r--r--app/views/shared/web_hooks/_hook.html.haml2
-rw-r--r--config/application.rb10
-rw-r--r--config/feature_flags/development/track_application_boot_time.yml8
-rw-r--r--config/initializers/console_message.rb22
-rw-r--r--doc/administration/clusters/kas.md17
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md1
-rw-r--r--doc/user/clusters/agent/index.md189
-rw-r--r--doc/user/clusters/agent/troubleshooting.md193
-rw-r--r--doc/user/infrastructure/iac/index.md37
-rw-r--r--doc/user/infrastructure/iac/mr_integration.md2
-rw-r--r--doc/user/infrastructure/iac/terraform_state.md12
-rw-r--r--lib/gitlab/console.rb41
-rw-r--r--lib/gitlab/metrics/boot_time_tracker.rb34
-rw-r--r--lib/gitlab/metrics/system.rb69
-rw-r--r--lib/gitlab/runtime.rb6
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json20
-rw-r--r--scripts/rspec_helpers.sh49
-rw-r--r--spec/crystalball_env.rb2
-rw-r--r--spec/fixtures/api/schemas/entities/member_user.json5
-rw-r--r--spec/frontend/content_editor/test_utils.js2
-rw-r--r--spec/frontend/members/components/avatars/user_avatar_spec.js63
-rw-r--r--spec/frontend/members/mock_data.js2
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js66
-rw-r--r--spec/frontend/repository/components/blob_viewers/download_viewer_spec.js37
-rw-r--r--spec/frontend/repository/components/blob_viewers/image_viewer_spec.js12
-rw-r--r--spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js14
-rw-r--r--spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js10
-rw-r--r--spec/frontend/repository/components/blob_viewers/video_viewer_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/source_viewer_spec.js7
-rw-r--r--spec/lib/gitlab/console_spec.rb51
-rw-r--r--spec/lib/gitlab/metrics/boot_time_tracker_spec.rb98
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb34
-rw-r--r--spec/lib/gitlab/runtime_spec.rb24
-rw-r--r--spec/models/commit_spec.rb16
-rw-r--r--spec/tooling/lib/tooling/test_map_generator_spec.rb16
-rw-r--r--tooling/lib/tooling/test_map_generator.rb7
-rw-r--r--yarn.lock152
56 files changed, 1029 insertions, 633 deletions
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index 8f4aca730e7..20cbd759ac6 100644
--- a/.gitlab/ci/test-metadata.gitlab-ci.yml
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -8,6 +8,7 @@
- knapsack/
- rspec/
- crystalball/
+ when: always
retrieve-tests-metadata:
extends:
diff --git a/.gitlab/issue_templates/Deprecations.md b/.gitlab/issue_templates/Deprecations.md
index 1de64088ca6..dd9b4faacd7 100644
--- a/.gitlab/issue_templates/Deprecations.md
+++ b/.gitlab/issue_templates/Deprecations.md
@@ -40,7 +40,7 @@ Which tier is this feature available in?
- To see who the stable counterparts are for a product team visit [product categories](https://about.gitlab.com/handbook/product/categories/)
- If there is no stable counterpart listed for Sales/CS please mention `@timtams`
- If there is no stable counterpart listed for Support please @mention `@gitlab-com/support/managers`
- - If there is no stable counterpart listed for Marketing please mention `@williamchia`
+ - If there is no stable counterpart listed for Marketing please mention `@cfoster3`
- [ ] @mention your GPM so that they are aware of planned deprecations. The goal is to have reviews happen at least two releases before the final removal of the feature or introduction of a breaking change.
diff --git a/app/assets/javascripts/members/components/avatars/user_avatar.vue b/app/assets/javascripts/members/components/avatars/user_avatar.vue
index 9687eacb036..ec59f0f681c 100644
--- a/app/assets/javascripts/members/components/avatars/user_avatar.vue
+++ b/app/assets/javascripts/members/components/avatars/user_avatar.vue
@@ -8,10 +8,14 @@ import {
import { generateBadges } from 'ee_else_ce/members/utils';
import { glEmojiTag } from '~/emoji';
import { __ } from '~/locale';
+import { isUserBusy } from '~/set_status_modal/utils';
import { AVATAR_SIZE } from '../../constants';
export default {
name: 'UserAvatar',
+ i18n: {
+ busy: __('Busy'),
+ },
avatarSize: AVATAR_SIZE,
orphanedUserLabel: __('Orphaned member'),
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
@@ -46,7 +50,10 @@ export default {
}).filter((badge) => badge.show);
},
statusEmoji() {
- return this.user?.status?.emoji;
+ return this.user?.showStatus && this.user?.status?.emoji;
+ },
+ isUserBusy() {
+ return isUserBusy(this.user?.availability || '');
},
},
methods: {
@@ -73,6 +80,11 @@ export default {
:entity-id="user.id"
>
<template #meta>
+ <div v-if="isUserBusy" class="gl-p-1">
+ <span class="gl-text-gray-500 gl-font-sm gl-font-weight-normal"
+ >({{ $options.i18n.busy }})</span
+ >
+ </div>
<div v-if="statusEmoji" class="gl-p-1">
<span
v-safe-html:[$options.safeHtmlConfig]="glEmojiTag(statusEmoji)"
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index ca89e549181..a37367afa3b 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -9,12 +9,14 @@ import axios from '~/lib/utils/axios_utils';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getRefMixin from '../mixins/get_ref';
import blobInfoQuery from '../queries/blob_info.query.graphql';
+import { DEFAULT_BLOB_INFO, TEXT_FILE_TYPE, LFS_STORAGE } from '../constants';
import BlobButtonGroup from './blob_button_group.vue';
import BlobEdit from './blob_edit.vue';
import ForkSuggestion from './fork_suggestion.vue';
-import { loadViewer, viewerProps } from './blob_viewers';
+import { loadViewer } from './blob_viewers';
export default {
i18n: {
@@ -29,7 +31,7 @@ export default {
GlButton,
ForkSuggestion,
},
- mixins: [getRefMixin],
+ mixins: [getRefMixin, glFeatureFlagMixin()],
inject: {
originalBranch: {
default: '',
@@ -78,52 +80,7 @@ export default {
isBinary: false,
isLoadingLegacyViewer: false,
activeViewerType: SIMPLE_BLOB_VIEWER,
- project: {
- userPermissions: {
- pushCode: false,
- downloadCode: false,
- createMergeRequestIn: false,
- forkProject: false,
- },
- pathLocks: {
- nodes: [],
- },
- repository: {
- empty: true,
- blobs: {
- nodes: [
- {
- name: '',
- size: '',
- rawTextBlob: '',
- type: '',
- fileType: '',
- tooLarge: false,
- path: '',
- editBlobPath: '',
- ideEditPath: '',
- forkAndEditPath: '',
- ideForkAndEditPath: '',
- storedExternally: false,
- externalStorage: '',
- environmentFormattedExternalUrl: '',
- environmentExternalUrlForRouteMap: '',
- canModifyBlob: false,
- canCurrentUserPushToBranch: false,
- archived: false,
- rawPath: '',
- externalStorageUrl: '',
- replacePath: '',
- pipelineEditorPath: '',
- deletePath: '',
- simpleViewer: {},
- richViewer: null,
- webPath: '',
- },
- ],
- },
- },
- },
+ project: DEFAULT_BLOB_INFO,
};
},
computed: {
@@ -134,7 +91,7 @@ export default {
return this.$apollo.queries.project.loading;
},
isBinaryFileType() {
- return this.isBinary || this.blobInfo.simpleViewer?.fileType !== 'text';
+ return this.isBinary || this.blobInfo.simpleViewer?.fileType !== TEXT_FILE_TYPE;
},
blobInfo() {
const nodes = this.project?.repository?.blobs?.nodes || [];
@@ -153,11 +110,16 @@ export default {
},
blobViewer() {
const { fileType } = this.viewer;
- return loadViewer(fileType, this.isUsingLfs);
+ return this.shouldLoadLegacyViewer ? null : loadViewer(fileType, this.isUsingLfs);
},
- viewerProps() {
- const { fileType } = this.viewer;
- return viewerProps(fileType, this.blobInfo);
+ shouldLoadLegacyViewer() {
+ return this.viewer.fileType === TEXT_FILE_TYPE && !this.glFeatures.highlightJs;
+ },
+ legacyViewerLoaded() {
+ return (
+ (this.activeViewerType === SIMPLE_BLOB_VIEWER && this.legacySimpleViewer) ||
+ (this.activeViewerType === RICH_BLOB_VIEWER && this.legacyRichViewer)
+ );
},
canLock() {
const { pushCode, downloadCode } = this.project.userPermissions;
@@ -186,20 +148,22 @@ export default {
: this.blobInfo.forkAndEditPath;
},
isUsingLfs() {
- return this.blobInfo.storedExternally && this.blobInfo.externalStorage === 'lfs';
+ return this.blobInfo.storedExternally && this.blobInfo.externalStorage === LFS_STORAGE;
},
},
methods: {
- loadLegacyViewer(type) {
- if (this.legacyViewerLoaded(type)) {
+ loadLegacyViewer() {
+ if (this.legacyViewerLoaded) {
return;
}
+ const type = this.activeViewerType;
+
this.isLoadingLegacyViewer = true;
axios
.get(`${this.blobInfo.webPath}?format=json&viewer=${type}`)
.then(({ data: { html, binary } }) => {
- if (type === 'simple') {
+ if (type === SIMPLE_BLOB_VIEWER) {
this.legacySimpleViewer = html;
} else {
this.legacyRichViewer = html;
@@ -210,12 +174,6 @@ export default {
})
.catch(() => this.displayError());
},
- legacyViewerLoaded(type) {
- return (
- (type === SIMPLE_BLOB_VIEWER && this.legacySimpleViewer) ||
- (type === RICH_BLOB_VIEWER && this.legacyRichViewer)
- );
- },
displayError() {
createFlash({ message: __('An error occurred while loading the file. Please try again.') });
},
@@ -223,7 +181,7 @@ export default {
this.activeViewerType = newViewer || SIMPLE_BLOB_VIEWER;
if (!this.blobViewer) {
- this.loadLegacyViewer(this.activeViewerType);
+ this.loadLegacyViewer();
}
},
editBlob(target) {
@@ -309,7 +267,7 @@ export default {
:hide-line-numbers="true"
:loading="isLoadingLegacyViewer"
/>
- <component :is="blobViewer" v-else v-bind="viewerProps" class="blob-viewer" />
+ <component :is="blobViewer" v-else :blob="blobInfo" class="blob-viewer" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue
index 48fa33eb558..f7b318c64d9 100644
--- a/app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue
@@ -9,19 +9,17 @@ export default {
GlLink,
},
props: {
- fileName: {
- type: String,
+ blob: {
+ type: Object,
required: true,
},
- filePath: {
- type: String,
- required: true,
- },
- fileSize: {
- type: Number,
- required: false,
- default: 0,
- },
+ },
+ data() {
+ return {
+ fileName: this.blob.name,
+ filePath: this.blob.rawPath,
+ fileSize: this.blob.rawSize || 0,
+ };
},
computed: {
downloadFileSize() {
diff --git a/app/assets/javascripts/repository/components/blob_viewers/image_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/image_viewer.vue
index 83d36209bb3..5027f7877aa 100644
--- a/app/assets/javascripts/repository/components/blob_viewers/image_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_viewers/image_viewer.vue
@@ -1,15 +1,17 @@
<script>
export default {
props: {
- url: {
- type: String,
- required: true,
- },
- alt: {
- type: String,
+ blob: {
+ type: Object,
required: true,
},
},
+ data() {
+ return {
+ url: this.blob.rawPath,
+ alt: this.blob.name,
+ };
+ },
};
</script>
<template>
diff --git a/app/assets/javascripts/repository/components/blob_viewers/index.js b/app/assets/javascripts/repository/components/blob_viewers/index.js
index 6e4e36579d3..daa11802cc5 100644
--- a/app/assets/javascripts/repository/components/blob_viewers/index.js
+++ b/app/assets/javascripts/repository/components/blob_viewers/index.js
@@ -17,34 +17,3 @@ export const loadViewer = (type, isUsingLfs) => {
return viewer;
};
-
-export const viewerProps = (type, blob) => {
- const props = {
- text: {
- content: blob.rawTextBlob,
- autoDetect: true, // We'll eventually disable autoDetect and pass the language explicitly to reduce the footprint (https://gitlab.com/gitlab-org/gitlab/-/issues/348145)
- },
- download: {
- fileName: blob.name,
- filePath: blob.rawPath,
- fileSize: blob.rawSize,
- },
- image: {
- url: blob.rawPath,
- alt: blob.name,
- },
- video: {
- url: blob.rawPath,
- },
- pdf: {
- url: blob.rawPath,
- fileSize: blob.rawSize,
- },
- lfs: {
- fileName: blob.name,
- filePath: blob.rawPath,
- },
- };
-
- return props[type] || props[blob.externalStorage];
-};
diff --git a/app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue
index 1596c5c91b1..6dc7e10662e 100644
--- a/app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_viewers/lfs_viewer.vue
@@ -13,15 +13,17 @@ export default {
GlSprintf,
},
props: {
- fileName: {
- type: String,
- required: true,
- },
- filePath: {
- type: String,
+ blob: {
+ type: Object,
required: true,
},
},
+ data() {
+ return {
+ fileName: this.blob.name,
+ filePath: this.blob.rawPath,
+ };
+ },
};
</script>
diff --git a/app/assets/javascripts/repository/components/blob_viewers/pdf_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/pdf_viewer.vue
index 803a357df52..c3df5984426 100644
--- a/app/assets/javascripts/repository/components/blob_viewers/pdf_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_viewers/pdf_viewer.vue
@@ -11,17 +11,17 @@ export default {
tooLargeButtonText: __('Download PDF'),
},
props: {
- url: {
- type: String,
- required: true,
- },
- fileSize: {
- type: Number,
+ blob: {
+ type: Object,
required: true,
},
},
data() {
- return { totalPages: 0 };
+ return {
+ url: this.blob.rawPath,
+ fileSize: this.blob.rawSize,
+ totalPages: 0,
+ };
},
computed: {
tooLargeToDisplay() {
diff --git a/app/assets/javascripts/repository/components/blob_viewers/video_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/video_viewer.vue
index dec0c4802ca..260b831f4d1 100644
--- a/app/assets/javascripts/repository/components/blob_viewers/video_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_viewers/video_viewer.vue
@@ -1,11 +1,16 @@
<script>
export default {
props: {
- url: {
- type: String,
+ blob: {
+ type: Object,
required: true,
},
},
+ data() {
+ return {
+ url: this.blob.rawPath,
+ };
+ },
};
</script>
<template>
diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js
index d01757d6141..e206d9bfbd2 100644
--- a/app/assets/javascripts/repository/constants.js
+++ b/app/assets/javascripts/repository/constants.js
@@ -25,3 +25,54 @@ export const PDF_MAX_FILE_SIZE = 10000000; // 10 MB
export const PDF_MAX_PAGE_LIMIT = 50;
export const ROW_APPEAR_DELAY = 150;
+
+export const DEFAULT_BLOB_INFO = {
+ userPermissions: {
+ pushCode: false,
+ downloadCode: false,
+ createMergeRequestIn: false,
+ forkProject: false,
+ },
+ pathLocks: {
+ nodes: [],
+ },
+ repository: {
+ empty: true,
+ blobs: {
+ nodes: [
+ {
+ name: '',
+ size: '',
+ rawTextBlob: '',
+ type: '',
+ fileType: '',
+ tooLarge: false,
+ path: '',
+ editBlobPath: '',
+ ideEditPath: '',
+ forkAndEditPath: '',
+ ideForkAndEditPath: '',
+ storedExternally: false,
+ externalStorage: '',
+ environmentFormattedExternalUrl: '',
+ environmentExternalUrlForRouteMap: '',
+ canModifyBlob: false,
+ canCurrentUserPushToBranch: false,
+ archived: false,
+ rawPath: '',
+ externalStorageUrl: '',
+ replacePath: '',
+ pipelineEditorPath: '',
+ deletePath: '',
+ simpleViewer: {},
+ richViewer: null,
+ webPath: '',
+ },
+ ],
+ },
+ },
+};
+
+export const TEXT_FILE_TYPE = 'text';
+
+export const LFS_STORAGE = 'lfs';
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer.vue
index 0aa2589637f..f6580a716ee 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer.vue
@@ -4,6 +4,7 @@ import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import { sanitize } from '~/lib/dompurify';
const LINE_SELECT_CLASS_NAME = 'hll';
+const PLAIN_TEXT_LANGUAGE = 'plaintext';
export default {
components: {
@@ -13,24 +14,21 @@ export default {
SafeHtml: GlSafeHtmlDirective,
},
props: {
- content: {
- type: String,
+ blob: {
+ type: Object,
required: true,
},
- language: {
- type: String,
- required: false,
- default: 'plaintext',
- },
autoDetect: {
type: Boolean,
required: false,
- default: false,
+ default: true, // We'll eventually disable autoDetect and pass the language explicitly to reduce the footprint (https://gitlab.com/gitlab-org/gitlab/-/issues/348145)
},
},
data() {
return {
languageDefinition: null,
+ content: this.blob.rawTextBlob,
+ language: this.blob.language || PLAIN_TEXT_LANGUAGE,
hljs: null,
};
},
diff --git a/app/models/commit.rb b/app/models/commit.rb
index f0c5f3c2d12..5293bfcf1ab 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -513,9 +513,7 @@ class Commit
# We don't want to do anything for `Commit` model, so this is empty.
end
- # WIP is deprecated in favor of Draft. Currently both options are supported
- # https://gitlab.com/gitlab-org/gitlab/-/issues/227426
- DRAFT_REGEX = /\A\s*#{Regexp.union(Gitlab::Regex.merge_request_wip, Gitlab::Regex.merge_request_draft)}|(fixup!|squash!)\s/.freeze
+ DRAFT_REGEX = /\A\s*#{Gitlab::Regex.merge_request_draft}|(fixup!|squash!)\s/.freeze
def work_in_progress?
!!(title =~ DRAFT_REGEX)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index f4780a1cfc0..0138c0ad20f 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -580,7 +580,7 @@ module Issuable
##
# Overridden in MergeRequest
#
- def wipless_title_changed(old_title)
+ def draftless_title_changed(old_title)
old_title != title
end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index fd77e530af0..ffa010cf9fc 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -563,16 +563,22 @@ class MergeRequest < ApplicationRecord
DRAFT_REGEX = /\A*#{Gitlab::Regex.merge_request_draft}+\s*/i.freeze
- def self.work_in_progress?(title)
+ def self.draft?(title)
!!(title =~ DRAFT_REGEX)
end
- def self.wipless_title(title)
+ def self.draftless_title(title)
title.sub(DRAFT_REGEX, "")
end
- def self.wip_title(title)
- work_in_progress?(title) ? title : "Draft: #{title}"
+ def self.draft_title(title)
+ draft?(title) ? title : "Draft: #{title}"
+ end
+
+ class << self
+ alias_method :work_in_progress?, :draft?
+ alias_method :wipless_title, :draftless_title
+ alias_method :wip_title, :draft_title
end
def self.participant_includes
@@ -585,9 +591,10 @@ class MergeRequest < ApplicationRecord
# Verifies if title has changed not taking into account Draft prefix
# for merge requests.
- def wipless_title_changed(old_title)
- self.class.wipless_title(old_title) != self.wipless_title
+ def draftless_title_changed(old_title)
+ self.class.draftless_title(old_title) != self.draftless_title
end
+ alias_method :wipless_title_changed, :draftless_title_changed
def hook_attrs
Gitlab::HookData::MergeRequestBuilder.new(self).build
@@ -1086,18 +1093,20 @@ class MergeRequest < ApplicationRecord
@closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: :closed).last
end
- def work_in_progress?
- self.class.work_in_progress?(title)
+ def draft?
+ self.class.draft?(title)
end
- alias_method :draft?, :work_in_progress?
+ alias_method :work_in_progress?, :draft?
- def wipless_title
- self.class.wipless_title(self.title)
+ def draftless_title
+ self.class.draftless_title(self.title)
end
+ alias_method :wipless_title, :draftless_title
- def wip_title
- self.class.wip_title(self.title)
+ def draft_title
+ self.class.draft_title(self.title)
end
+ alias_method :wip_title, :draft_title
def mergeable?(skip_ci_check: false, skip_discussions_check: false)
return false unless mergeable_state?(skip_ci_check: skip_ci_check,
diff --git a/app/serializers/member_user_entity.rb b/app/serializers/member_user_entity.rb
index 01920fc95bb..fde3282ad25 100644
--- a/app/serializers/member_user_entity.rb
+++ b/app/serializers/member_user_entity.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class MemberUserEntity < UserEntity
- unexpose :show_status
unexpose :path
unexpose :state
unexpose :status_tooltip_html
diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb
index 38050708fc5..9ee54c7ba0f 100644
--- a/app/services/issuable/common_system_notes_service.rb
+++ b/app/services/issuable/common_system_notes_service.rb
@@ -71,7 +71,7 @@ module Issuable
def create_title_change_note(old_title)
create_draft_note(old_title)
- if issuable.wipless_title_changed(old_title)
+ if issuable.draftless_title_changed(old_title)
SystemNoteService.change_title(issuable, issuable.project, current_user, old_title)
end
end
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 20b86e0e7cd..d663034b9d8 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -156,13 +156,13 @@
= sprite_icon('slight-frown')
%span.nav-item-name
= _('Abuse Reports')
- %span.badge.badge-pill.count= number_with_delimiter(AbuseReport.count(:all))
+ = gl_badge_tag number_with_delimiter(AbuseReport.count(:all)), variant: :info, size: :sm
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :abuse_reports, html_options: { class: "fly-out-top-item" } ) do
= link_to admin_abuse_reports_path do
%strong.fly-out-top-item-name
= _('Abuse Reports')
- %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all))
+ = gl_badge_tag number_with_delimiter(AbuseReport.count(:all)), variant: :info, size: :sm
= render_if_exists 'layouts/nav/sidebar/licenses_link'
diff --git a/app/views/shared/web_hooks/_hook.html.haml b/app/views/shared/web_hooks/_hook.html.haml
index c5a03ef4dc1..529ef47a2cf 100644
--- a/app/views/shared/web_hooks/_hook.html.haml
+++ b/app/views/shared/web_hooks/_hook.html.haml
@@ -22,4 +22,4 @@
.col-md-4.col-lg-5.text-right-md.gl-mt-2
%span>= render 'shared/web_hooks/test_button', hook: hook, button_class: 'btn-sm btn-default gl-mr-3'
%span>= link_to _('Edit'), edit_hook_path(hook), class: 'btn gl-button btn-default btn-sm gl-mr-3'
- = link_to _('Delete'), destroy_hook_path(hook), data: { confirm: _('Are you sure?') }, method: :delete, class: 'btn gl-button btn-secondary btn-danger-secondary btn-sm'
+ = link_to _('Delete'), destroy_hook_path(hook), aria: { label: s_('Webhooks|Delete webhook') }, data: { confirm_btn_variant: "danger", confirm: s_('Webhooks|Are you sure you want to delete this webhook?') }, method: :delete, class: 'btn gl-button btn-secondary btn-danger-secondary btn-sm'
diff --git a/config/application.rb b/config/application.rb
index 68b68c1c886..b11b5f1bf94 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -508,5 +508,15 @@ module Gitlab
end
end
end
+
+ # DO NOT PLACE ANY INITIALIZERS AFTER THIS.
+ config.after_initialize do
+ # on_master_start yields immediately in unclustered environments and runs
+ # when the primary process is done initializing otherwise.
+ Gitlab::Cluster::LifecycleEvents.on_master_start do
+ Gitlab::Metrics::BootTimeTracker.instance.track_boot_time!
+ Gitlab::Console.welcome!
+ end
+ end
end
end
diff --git a/config/feature_flags/development/track_application_boot_time.yml b/config/feature_flags/development/track_application_boot_time.yml
new file mode 100644
index 00000000000..2a2b5eba866
--- /dev/null
+++ b/config/feature_flags/development/track_application_boot_time.yml
@@ -0,0 +1,8 @@
+---
+name: track_application_boot_time
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79139
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351769
+milestone: '14.8'
+type: development
+group: group::memory
+default_enabled: true
diff --git a/config/initializers/console_message.rb b/config/initializers/console_message.rb
index 3f98568c500..ccb5dc51f66 100644
--- a/config/initializers/console_message.rb
+++ b/config/initializers/console_message.rb
@@ -1,28 +1,6 @@
# frozen_string_literal: true
-# rubocop:disable Rails/Output
if Gitlab::Runtime.console?
- # note that this will not print out when using `spring`
- justify = 15
-
- puts '-' * 80
- puts " Ruby:".ljust(justify) + RUBY_DESCRIPTION
- puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision}) #{Gitlab.ee? ? 'EE' : 'FOSS'}"
- puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.version)}"
-
- if ApplicationRecord.database.exists?
- puts " #{ApplicationRecord.database.human_adapter_name}:".ljust(justify) + ApplicationRecord.database.version
-
- Gitlab.ee do
- if Gitlab::Geo.connected? && Gitlab::Geo.enabled?
- puts " Geo enabled:".ljust(justify) + 'yes'
- puts " Geo server:".ljust(justify) + EE::GeoHelper.current_node_human_status
- end
- end
- end
-
- puts '-' * 80
-
# Stop irb from writing a history file by default.
module IrbNoHistory
def init_config(*)
diff --git a/doc/administration/clusters/kas.md b/doc/administration/clusters/kas.md
index ba0d7280b74..192b636e246 100644
--- a/doc/administration/clusters/kas.md
+++ b/doc/administration/clusters/kas.md
@@ -4,13 +4,15 @@ group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Install the GitLab Agent Server (KAS) **(FREE SELF)**
+# Install the GitLab Agent Server for Kubernetes (KAS) **(FREE SELF)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3834) in GitLab 13.10, the GitLab Agent Server (KAS) became available on GitLab.com under `wss://kas.gitlab.com`.
-> [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) from GitLab Premium to GitLab Free in 14.5.
+> - [Moved](https://gitlab.com/groups/gitlab-org/-/epics/6290) from GitLab Premium to GitLab Free in 14.5.
-The GitLab Agent Server (KAS) is a GitLab backend service dedicated to
-managing the [GitLab Agent](../../user/clusters/agent/index.md).
+The GitLab Agent Server for Kubernetes is a GitLab backend service dedicated to
+managing the [GitLab Agent for Kubernetes](../../user/clusters/agent/index.md).
+
+The KAS acronym refers to the former name, Kubernetes Agent Server.
The KAS is already installed and available in GitLab.com under `wss://kas.gitlab.com`.
This document describes how to install a KAS for GitLab self-managed instances.
@@ -94,8 +96,8 @@ For GitLab instances installed through Omnibus packages:
## Troubleshooting
-If you face any issues with KAS, you can read the service logs
-with the following command:
+If you have issues while using the GitLab Agent Server for Kubernetes, view the
+service logs by running the following command:
```shell
kubectl logs -f -l=app=kas -n <YOUR-GITLAB-NAMESPACE>
@@ -103,8 +105,7 @@ kubectl logs -f -l=app=kas -n <YOUR-GITLAB-NAMESPACE>
In Omnibus GitLab, find the logs in `/var/log/gitlab/gitlab-kas/`.
-See also the [user documentation](../../user/clusters/agent/index.md#troubleshooting)
-for troubleshooting problems with individual agents.
+You can also [troubleshoot issues with individual Agents](../../user/clusters/agent/troubleshooting.md).
### KAS logs - GitOps: failed to get project information
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index ced0ff310d0..d94c3cdefbf 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -48,6 +48,7 @@ The following metrics are available:
| `gitlab_database_transaction_seconds` | Histogram | 12.1 | Time spent in database transactions, in seconds | |
| `gitlab_method_call_duration_seconds` | Histogram | 10.2 | Method calls real duration | `controller`, `action`, `module`, `method` |
| `gitlab_page_out_of_bounds` | Counter | 12.8 | Counter for the PageLimiter pagination limit being hit | `controller`, `action`, `bot` |
+| `gitlab_rails_boot_time_seconds` | Gauge | 14.8 | Time elapsed for Rails primary process to finish startup | |
| `gitlab_rails_queue_duration_seconds` | Histogram | 9.4 | Measures latency between GitLab Workhorse forwarding a request to Rails | |
| `gitlab_sql_duration_seconds` | Histogram | 10.2 | SQL execution time, excluding `SCHEMA` operations and `BEGIN` / `COMMIT` | |
| `gitlab_sql_<role>_duration_seconds` | Histogram | 13.10 | SQL execution time, excluding `SCHEMA` operations and `BEGIN` / `COMMIT`, grouped by database roles (primary/replica) | |
diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md
index f85ca5aac8d..eeeb7168267 100644
--- a/doc/user/clusters/agent/index.md
+++ b/doc/user/clusters/agent/index.md
@@ -205,191 +205,6 @@ For self-managed GitLab instances, go to `https://gitlab.example.com/-/graphql-e
Find out how to [migrate to the GitLab Agent for Kubernetes](../../infrastructure/clusters/migrate_to_gitlab_agent.md) from the certificate-based integration depending on the features you use.
-## Troubleshooting
+## Related topics
-If you face any issues while using the Agent, read the
-service logs with the following command:
-
-```shell
-kubectl logs -f -l=app=gitlab-kubernetes-agent -n gitlab-kubernetes-agent
-```
-
-GitLab administrators can additionally view the [GitLab Agent Server logs](../../../administration/clusters/kas.md#troubleshooting).
-
-### Agent logs
-
-#### Transport: Error while dialing failed to WebSocket dial
-
-```json
-{
- "level": "warn",
- "time": "2020-11-04T10:14:39.368Z",
- "msg": "GetConfiguration failed",
- "error": "rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing failed to WebSocket dial: failed to send handshake request: Get \\\"https://gitlab-kas:443/-/kubernetes-agent\\\": dial tcp: lookup gitlab-kas on 10.60.0.10:53: no such host\""
-}
-```
-
-This error is shown if there are some connectivity issues between the address
-specified as `kas-address`, and your Agent pod. To fix it, make sure that you
-specified the `kas-address` correctly.
-
-```json
-{
- "level": "error",
- "time": "2021-06-25T21:15:45.335Z",
- "msg": "Reverse tunnel",
- "mod_name": "reverse_tunnel",
- "error": "Connect(): rpc error: code = Unavailable desc = connection error: desc= \"transport: Error while dialing failed to WebSocket dial: expected handshake response status code 101 but got 301\""
-}
-```
-
-This error occurs if the `kas-address` doesn't include a trailing slash. To fix it, make sure that the
-`wss` or `ws` URL ends with a trailing slash, such as `wss://GitLab.host.tld:443/-/kubernetes-agent/`
-or `ws://GitLab.host.tld:80/-/kubernetes-agent/`.
-
-#### ValidationError(Deployment.metadata)
-
-```json
-{
- "level": "info",
- "time": "2020-10-30T08:56:54.329Z",
- "msg": "Synced",
- "project_id": "root/kas-manifest001",
- "resource_key": "apps/Deployment/kas-test001/nginx-deployment",
- "sync_result": "error validating data: [ValidationError(Deployment.metadata): unknown field \"replicas\" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta, ValidationError(Deployment.metadata): unknown field \"selector\" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta, ValidationError(Deployment.metadata): unknown field \"template\" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta]"
-}
-```
-
-This error is shown if a manifest file is malformed, and Kubernetes can't
-create specified objects. Make sure that your manifest files are valid. You
-may try using them to create objects in Kubernetes directly for more troubleshooting.
-
-#### Error while dialing failed to WebSocket dial: failed to send handshake request
-
-```json
-{
- "level": "warn",
- "time": "2020-10-30T09:50:51.173Z",
- "msg": "GetConfiguration failed",
- "error": "rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing failed to WebSocket dial: failed to send handshake request: Get \\\"https://GitLabhost.tld:443/-/kubernetes-agent\\\": net/http: HTTP/1.x transport connection broken: malformed HTTP response \\\"\\\\x00\\\\x00\\\\x06\\\\x04\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x05\\\\x00\\\\x00@\\\\x00\\\"\""
-}
-```
-
-This error is shown if you configured `wss` as `kas-address` on the agent side,
-but KAS on the server side is not available via `wss`. To fix it, make sure the
-same schemes are configured on both sides.
-
-It's not possible to set the `grpc` scheme due to the issue
-[It is not possible to configure KAS to work with `grpc` without directly editing GitLab KAS deployment](https://gitlab.com/gitlab-org/gitlab/-/issues/276888). To use `grpc` while the
-issue is in progress, directly edit the deployment with the
-`kubectl edit deployment gitlab-kas` command, and change `--listen-websocket=true` to `--listen-websocket=false`. After running that command, you should be able to use
-`grpc://gitlab-kas.<YOUR-NAMESPACE>:8150`.
-
-#### Decompressor is not installed for grpc-encoding
-
-```json
-{
- "level": "warn",
- "time": "2020-11-05T05:25:46.916Z",
- "msg": "GetConfiguration.Recv failed",
- "error": "rpc error: code = Unimplemented desc = grpc: Decompressor is not installed for grpc-encoding \"gzip\""
-}
-```
-
-This error is shown if the version of the agent is newer that the version of KAS.
-To fix it, make sure that both `agentk` and KAS use the same versions.
-
-#### Certificate signed by unknown authority
-
-```json
-{
- "level": "error",
- "time": "2021-02-25T07:22:37.158Z",
- "msg": "Reverse tunnel",
- "mod_name": "reverse_tunnel",
- "error": "Connect(): rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing failed to WebSocket dial: failed to send handshake request: Get \\\"https://GitLabhost.tld:443/-/kubernetes-agent/\\\": x509: certificate signed by unknown authority\""
-}
-```
-
-This error is shown if your GitLab instance is using a certificate signed by an internal CA that
-is unknown to the agent. One approach to fixing it is to present the CA certificate file to the agent
-via a Kubernetes `configmap` and mount the file in the agent `/etc/ssl/certs` directory from where it
-will be picked up automatically.
-
-For example, if your internal CA certificate is `myCA.pem`:
-
-```plaintext
-kubectl -n gitlab-kubernetes-agent create configmap ca-pemstore --from-file=myCA.pem
-```
-
-Then in `resources.yml`:
-
-```yaml
- spec:
- serviceAccountName: gitlab-kubernetes-agent
- containers:
- - name: agent
- image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:<version>"
- args:
- - --token-file=/config/token
- - --kas-address
- - wss://kas.host.tld:443 # replace this line with the line below if using Omnibus GitLab or GitLab.com.
- # - wss://gitlab.host.tld:443/-/kubernetes-agent/
- # - wss://kas.gitlab.com # for GitLab.com users, use this KAS.
- # - grpc://host.docker.internal:8150 # use this attribute when connecting from Docker.
- volumeMounts:
- - name: token-volume
- mountPath: /config
- - name: ca-pemstore-volume
- mountPath: /etc/ssl/certs/myCA.pem
- subPath: myCA.pem
- volumes:
- - name: token-volume
- secret:
- secretName: gitlab-kubernetes-agent-token
- - name: ca-pemstore-volume
- configMap:
- name: ca-pemstore
- items:
- - key: myCA.pem
- path: myCA.pem
-```
-
-Alternatively, you can mount the certificate file at a different location and include it using the
-`--ca-cert-file` agent parameter:
-
-```yaml
- containers:
- - name: agent
- image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:<version>"
- args:
- - --ca-cert-file=/tmp/myCA.pem
- - --token-file=/config/token
- - --kas-address
- - wss://kas.host.tld:443 # replace this line with the line below if using Omnibus GitLab or GitLab.com.
- # - wss://gitlab.host.tld:443/-/kubernetes-agent/
- # - wss://kas.gitlab.com # for GitLab.com users, use this KAS.
- # - grpc://host.docker.internal:8150 # use this attribute when connecting from Docker.
- volumeMounts:
- - name: token-volume
- mountPath: /config
- - name: ca-pemstore-volume
- mountPath: /tmp/myCA.pem
- subPath: myCA.pem
-```
-
-#### Project not found
-
-```json
-{
- "level ":"error ",
- "time ":"2022-01-05T15:18:11.331Z",
- "msg ":"GetObjectsToSynchronize.Recv failed ",
- "mod_name ":"gitops ",
- "error ":"rpc error: code = NotFound desc = project not found ",
-}
-```
-
-This error is shown if the manifest project is not public. To fix it,
-[make sure your manifest project is public](repository.md#synchronize-manifest-projects) or your manifest files
-are stored in the Agent's configuration repository.
+- [Troubleshooting](troubleshooting.md)
diff --git a/doc/user/clusters/agent/troubleshooting.md b/doc/user/clusters/agent/troubleshooting.md
new file mode 100644
index 00000000000..d5777267582
--- /dev/null
+++ b/doc/user/clusters/agent/troubleshooting.md
@@ -0,0 +1,193 @@
+---
+stage: Configure
+group: Configure
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Troubleshooting the GitLab Agent for Kubernetes
+
+When you are using the GitLab Agent for Kubernetes, you might experience issues you need to troubleshoot.
+
+You can start by viewing the service logs:
+
+```shell
+kubectl logs -f -l=app=gitlab-kubernetes-agent -n gitlab-kubernetes-agent
+```
+
+If you are a GitLab administrator, you can also view the [GitLab Agent Server logs](../../../administration/clusters/kas.md#troubleshooting).
+
+## Transport: Error while dialing failed to WebSocket dial
+
+```json
+{
+ "level": "warn",
+ "time": "2020-11-04T10:14:39.368Z",
+ "msg": "GetConfiguration failed",
+ "error": "rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing failed to WebSocket dial: failed to send handshake request: Get \\\"https://gitlab-kas:443/-/kubernetes-agent\\\": dial tcp: lookup gitlab-kas on 10.60.0.10:53: no such host\""
+}
+```
+
+This error is shown if there are some connectivity issues between the address
+specified as `kas-address`, and your Agent pod. To fix it, make sure that you
+specified the `kas-address` correctly.
+
+```json
+{
+ "level": "error",
+ "time": "2021-06-25T21:15:45.335Z",
+ "msg": "Reverse tunnel",
+ "mod_name": "reverse_tunnel",
+ "error": "Connect(): rpc error: code = Unavailable desc = connection error: desc= \"transport: Error while dialing failed to WebSocket dial: expected handshake response status code 101 but got 301\""
+}
+```
+
+This error occurs if the `kas-address` doesn't include a trailing slash. To fix it, make sure that the
+`wss` or `ws` URL ends with a trailing slash, such as `wss://GitLab.host.tld:443/-/kubernetes-agent/`
+or `ws://GitLab.host.tld:80/-/kubernetes-agent/`.
+
+## ValidationError(Deployment.metadata)
+
+```json
+{
+ "level": "info",
+ "time": "2020-10-30T08:56:54.329Z",
+ "msg": "Synced",
+ "project_id": "root/kas-manifest001",
+ "resource_key": "apps/Deployment/kas-test001/nginx-deployment",
+ "sync_result": "error validating data: [ValidationError(Deployment.metadata): unknown field \"replicas\" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta, ValidationError(Deployment.metadata): unknown field \"selector\" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta, ValidationError(Deployment.metadata): unknown field \"template\" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta]"
+}
+```
+
+This error is shown if a manifest file is malformed, and Kubernetes can't
+create specified objects. Make sure that your manifest files are valid. You
+may try using them to create objects in Kubernetes directly for more troubleshooting.
+
+## Error while dialing failed to WebSocket dial: failed to send handshake request
+
+```json
+{
+ "level": "warn",
+ "time": "2020-10-30T09:50:51.173Z",
+ "msg": "GetConfiguration failed",
+ "error": "rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing failed to WebSocket dial: failed to send handshake request: Get \\\"https://GitLabhost.tld:443/-/kubernetes-agent\\\": net/http: HTTP/1.x transport connection broken: malformed HTTP response \\\"\\\\x00\\\\x00\\\\x06\\\\x04\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x05\\\\x00\\\\x00@\\\\x00\\\"\""
+}
+```
+
+This error is shown if you configured `wss` as `kas-address` on the agent side,
+but KAS on the server side is not available via `wss`. To fix it, make sure the
+same schemes are configured on both sides.
+
+It's not possible to set the `grpc` scheme due to the issue
+[It is not possible to configure KAS to work with `grpc` without directly editing GitLab KAS deployment](https://gitlab.com/gitlab-org/gitlab/-/issues/276888). To use `grpc` while the
+issue is in progress, directly edit the deployment with the
+`kubectl edit deployment gitlab-kas` command, and change `--listen-websocket=true` to `--listen-websocket=false`. After running that command, you should be able to use
+`grpc://gitlab-kas.<YOUR-NAMESPACE>:8150`.
+
+## Decompressor is not installed for grpc-encoding
+
+```json
+{
+ "level": "warn",
+ "time": "2020-11-05T05:25:46.916Z",
+ "msg": "GetConfiguration.Recv failed",
+ "error": "rpc error: code = Unimplemented desc = grpc: Decompressor is not installed for grpc-encoding \"gzip\""
+}
+```
+
+This error is shown if the version of the agent is newer that the version of KAS.
+To fix it, make sure that both `agentk` and KAS use the same versions.
+
+## Certificate signed by unknown authority
+
+```json
+{
+ "level": "error",
+ "time": "2021-02-25T07:22:37.158Z",
+ "msg": "Reverse tunnel",
+ "mod_name": "reverse_tunnel",
+ "error": "Connect(): rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing failed to WebSocket dial: failed to send handshake request: Get \\\"https://GitLabhost.tld:443/-/kubernetes-agent/\\\": x509: certificate signed by unknown authority\""
+}
+```
+
+This error is shown if your GitLab instance is using a certificate signed by an internal CA that
+is unknown to the agent. One approach to fixing it is to present the CA certificate file to the agent
+via a Kubernetes `configmap` and mount the file in the agent `/etc/ssl/certs` directory from where it
+will be picked up automatically.
+
+For example, if your internal CA certificate is `myCA.pem`:
+
+```plaintext
+kubectl -n gitlab-kubernetes-agent create configmap ca-pemstore --from-file=myCA.pem
+```
+
+Then in `resources.yml`:
+
+```yaml
+ spec:
+ serviceAccountName: gitlab-kubernetes-agent
+ containers:
+ - name: agent
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:<version>"
+ args:
+ - --token-file=/config/token
+ - --kas-address
+ - wss://kas.host.tld:443 # replace this line with the line below if using Omnibus GitLab or GitLab.com.
+ # - wss://gitlab.host.tld:443/-/kubernetes-agent/
+ # - wss://kas.gitlab.com # for GitLab.com users, use this KAS.
+ # - grpc://host.docker.internal:8150 # use this attribute when connecting from Docker.
+ volumeMounts:
+ - name: token-volume
+ mountPath: /config
+ - name: ca-pemstore-volume
+ mountPath: /etc/ssl/certs/myCA.pem
+ subPath: myCA.pem
+ volumes:
+ - name: token-volume
+ secret:
+ secretName: gitlab-kubernetes-agent-token
+ - name: ca-pemstore-volume
+ configMap:
+ name: ca-pemstore
+ items:
+ - key: myCA.pem
+ path: myCA.pem
+```
+
+Alternatively, you can mount the certificate file at a different location and include it using the
+`--ca-cert-file` agent parameter:
+
+```yaml
+ containers:
+ - name: agent
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/agentk:<version>"
+ args:
+ - --ca-cert-file=/tmp/myCA.pem
+ - --token-file=/config/token
+ - --kas-address
+ - wss://kas.host.tld:443 # replace this line with the line below if using Omnibus GitLab or GitLab.com.
+ # - wss://gitlab.host.tld:443/-/kubernetes-agent/
+ # - wss://kas.gitlab.com # for GitLab.com users, use this KAS.
+ # - grpc://host.docker.internal:8150 # use this attribute when connecting from Docker.
+ volumeMounts:
+ - name: token-volume
+ mountPath: /config
+ - name: ca-pemstore-volume
+ mountPath: /tmp/myCA.pem
+ subPath: myCA.pem
+```
+
+## Project not found
+
+```json
+{
+ "level ":"error ",
+ "time ":"2022-01-05T15:18:11.331Z",
+ "msg ":"GetObjectsToSynchronize.Recv failed ",
+ "mod_name ":"gitops ",
+ "error ":"rpc error: code = NotFound desc = project not found ",
+}
+```
+
+This error is shown if the manifest project is not public. To fix it,
+[make sure your manifest project is public](repository.md#synchronize-manifest-projects) or your manifest files
+are stored in the Agent's configuration repository.
diff --git a/doc/user/infrastructure/iac/index.md b/doc/user/infrastructure/iac/index.md
index 21769bdd14c..cb6ec8bc438 100644
--- a/doc/user/infrastructure/iac/index.md
+++ b/doc/user/infrastructure/iac/index.md
@@ -6,36 +6,33 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Infrastructure as Code with Terraform and GitLab **(FREE)**
-## Motivation
+With Terraform in GitLab, you can use GitLab authentication and authorization with
+your GitOps and Infrastructure-as-Code (IaC) workflows.
+Use these features if you want to collaborate on Terraform code within GitLab or would like to use GitLab as a Terraform state storage that incorporates best practices out of the box.
-The Terraform integration features in GitLab enable your GitOps / Infrastructure-as-Code (IaC)
-workflows to tie into GitLab authentication and authorization. These features focus on
-lowering the barrier to entry for teams to adopt Terraform, collaborate effectively in
-GitLab, and support Terraform best practices.
-
-## Quick Start
+## Integrate your project with Terraform
> SAST test was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/6655) in GitLab 14.6.
-Use the following `.gitlab-ci.yml` to set up a basic Terraform project integration
-for GitLab versions 14.0 and later:
+In GitLab 14.0 and later, to integrate your project with Terraform, add the following
+to your `.gitlab-ci.yml` file:
```yaml
include:
- template: Terraform.latest.gitlab-ci.yml
variables:
- # If not using GitLab's HTTP backend, remove this line and specify TF_HTTP_* variables
+ # If you do not use the GitLab HTTP backend, remove this line and specify TF_HTTP_* variables
TF_STATE_NAME: default
TF_CACHE_KEY: default
# If your terraform files are in a subdirectory, set TF_ROOT accordingly
# TF_ROOT: terraform/production
```
-This template includes the following parameters that you can override:
+The `Terraform.latest.gitlab-ci.yml` template:
- Uses the latest [GitLab Terraform image](https://gitlab.com/gitlab-org/terraform-images).
-- Uses the [GitLab-managed Terraform State](#gitlab-managed-terraform-state) as
+- Uses the [GitLab-managed Terraform state](#gitlab-managed-terraform-state) as
the Terraform state storage backend.
- Creates [four pipeline stages](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml):
`test`, `validate`, `build`, and `deploy`. These stages
@@ -44,10 +41,12 @@ This template includes the following parameters that you can override:
- Runs the [Terraform SAST scanner](../../application_security/iac_scanning/index.md#configure-iac-scanning-manually),
that you can disable by creating a `SAST_DISABLED` environment variable and setting it to `1`.
-The latest template described above might contain breaking changes between major GitLab releases. For users requiring more stable setups, we
-recommend using the stable templates:
+You can override the values in the default template by updating your `.gitlab-ci.yml` file.
+
+The latest template might contain breaking changes between major GitLab releases.
+For a more stable template, we recommend:
-- [A ready to use version](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml)
+- [A ready-to-use version](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml)
- [A base template for customized setups](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml)
This video from January 2021 walks you through all the GitLab Terraform integration features:
@@ -59,7 +58,7 @@ This video from January 2021 walks you through all the GitLab Terraform integrat
<iframe src="https://www.youtube.com/embed/iGXjUrkkzDI" frameborder="0" allowfullscreen="true"> </iframe>
</figure>
-## GitLab Managed Terraform state
+## GitLab-managed Terraform state
[Terraform remote backends](https://www.terraform.io/docs/language/settings/backends/index.html)
enable you to store the state file in a remote, shared store. GitLab uses the
@@ -67,7 +66,7 @@ enable you to store the state file in a remote, shared store. GitLab uses the
to securely store the state files in local storage (the default) or
[the remote store of your choice](../../../administration/terraform_state.md).
-The GitLab managed Terraform state backend can store your Terraform state easily and
+The GitLab-managed Terraform state backend can store your Terraform state easily and
securely. It spares you from setting up additional remote resources like
Amazon S3 or Google Cloud Storage. Its features include:
@@ -75,7 +74,7 @@ Amazon S3 or Google Cloud Storage. Its features include:
- Locking and unlocking state.
- Remote Terraform plan and apply execution.
-Read more on setting up and [using GitLab Managed Terraform states](terraform_state.md).
+Read more about setting up and [using GitLab-managed Terraform states](terraform_state.md).
## Terraform module registry
@@ -104,7 +103,7 @@ to manage various aspects of GitLab using Terraform. The provider is an open sou
owned by GitLab, where everyone can contribute.
The [documentation of the provider](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs)
-is available as part of the official Terraform provider documentations.
+is available as part of the official Terraform provider documentation.
## Create a new cluster through IaC (DEPRECATED)
diff --git a/doc/user/infrastructure/iac/mr_integration.md b/doc/user/infrastructure/iac/mr_integration.md
index 95ca478a383..8c135f18bc1 100644
--- a/doc/user/infrastructure/iac/mr_integration.md
+++ b/doc/user/infrastructure/iac/mr_integration.md
@@ -23,7 +23,7 @@ recommend encrypting plan output or modifying the project visibility settings.
## Configure Terraform report artifacts
-GitLab ships with a [pre-built CI template](index.md#quick-start) that uses GitLab Managed Terraform state and integrates Terraform changes into merge requests. We recommend customizing the pre-built image and relying on the `gitlab-terraform` helper provided within for a quick setup.
+GitLab ships with a [pre-built CI template](index.md#integrate-your-project-with-terraform) that uses GitLab Managed Terraform state and integrates Terraform changes into merge requests. We recommend customizing the pre-built image and relying on the `gitlab-terraform` helper provided within for a quick setup.
To manually configure a GitLab Terraform Report artifact:
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md
index 0c5c09e8ace..fb60024e0ba 100644
--- a/doc/user/infrastructure/iac/terraform_state.md
+++ b/doc/user/infrastructure/iac/terraform_state.md
@@ -4,7 +4,7 @@ group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# GitLab managed Terraform State **(FREE)**
+# GitLab-managed Terraform state **(FREE)**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2673) in GitLab 13.0.
@@ -19,7 +19,7 @@ Using local storage (the default) on clustered deployments of GitLab will result
a split state across nodes, making subsequent executions of Terraform inconsistent.
You are highly advised to use a remote storage resource in that case.
-The GitLab managed Terraform state backend can store your Terraform state easily and
+The GitLab-managed Terraform state backend can store your Terraform state easily and
securely, and spares you from setting up additional remote resources like
Amazon S3 or Google Cloud Storage. Its features include:
@@ -216,7 +216,7 @@ recommends encrypting plan output or modifying the project visibility settings.
See [this reference project](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-aws) using GitLab and Terraform to deploy a basic AWS EC2 in a custom VPC.
-## Using a GitLab managed Terraform state backend as a remote data source
+## Using a GitLab-managed Terraform state backend as a remote data source
You can use a GitLab-managed Terraform state as a
[Terraform data source](https://www.terraform.io/docs/language/state/remote-state-data.html).
@@ -260,13 +260,13 @@ using `data.terraform_remote_state.example.outputs.<OUTPUT-NAME>`.
You need at least the Developer role in the target project
to read the Terraform state.
-## Migrating to GitLab Managed Terraform state
+## Migrating to GitLab-managed Terraform state
Terraform supports copying the state when the backend is changed or
reconfigured. This can be useful if you need to migrate from another backend to
-GitLab managed Terraform state. Using a local terminal is recommended to run the commands needed for migrating to GitLab Managed Terraform state.
+GitLab-managed Terraform state. Using a local terminal is recommended to run the commands needed for migrating to GitLab-managed Terraform state.
-The following example demonstrates how to change the state name, the same workflow is needed to migrate to GitLab Managed Terraform state from a different state storage backend.
+The following example demonstrates how to change the state name, the same workflow is needed to migrate to GitLab-managed Terraform state from a different state storage backend.
### Setting up the initial backend
diff --git a/lib/gitlab/console.rb b/lib/gitlab/console.rb
new file mode 100644
index 00000000000..c3c34bb0f61
--- /dev/null
+++ b/lib/gitlab/console.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+# rubocop:disable Rails/Output
+module Gitlab
+ module Console
+ class << self
+ def welcome!
+ return unless Gitlab::Runtime.console?
+
+ # note that this will not print out when using `spring`
+ justify = 15
+
+ puts '-' * 80
+ puts " Ruby:".ljust(justify) + RUBY_DESCRIPTION
+ puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision}) #{Gitlab.ee? ? 'EE' : 'FOSS'}"
+ puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.version)}"
+
+ if ApplicationRecord.database.exists?
+ puts " #{ApplicationRecord.database.human_adapter_name}:".ljust(justify) + ApplicationRecord.database.version
+
+ Gitlab.ee do
+ if Gitlab::Geo.connected? && Gitlab::Geo.enabled?
+ puts " Geo enabled:".ljust(justify) + 'yes'
+ puts " Geo server:".ljust(justify) + EE::GeoHelper.current_node_human_status
+ end
+ end
+ end
+
+ if RUBY_PLATFORM.include?('darwin')
+ # Sorry, macOS users. The current implementation requires procfs.
+ puts '-' * 80
+ else
+ boot_time_seconds = Gitlab::Metrics::BootTimeTracker.instance.startup_time
+ booted_in = "[ booted in %.2fs ]" % [boot_time_seconds]
+ puts '-' * (80 - booted_in.length) + booted_in
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Rails/Output
diff --git a/lib/gitlab/metrics/boot_time_tracker.rb b/lib/gitlab/metrics/boot_time_tracker.rb
new file mode 100644
index 00000000000..d0eb66f293b
--- /dev/null
+++ b/lib/gitlab/metrics/boot_time_tracker.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Metrics
+ class BootTimeTracker
+ include Singleton
+
+ SUPPORTED_RUNTIMES = [:puma, :sidekiq, :console].freeze
+
+ def startup_time
+ @startup_time || 0
+ end
+
+ def track_boot_time!(logger: Gitlab::AppJsonLogger)
+ return if @startup_time
+
+ runtime = Gitlab::Runtime.safe_identify
+ return unless SUPPORTED_RUNTIMES.include?(runtime) && Feature.enabled?(:track_application_boot_time, default_enabled: :yaml)
+
+ @startup_time = Gitlab::Metrics::System.process_runtime_elapsed_seconds
+
+ Gitlab::Metrics.gauge(
+ :gitlab_rails_boot_time_seconds, 'Time elapsed for Rails primary process to finish startup'
+ ).set({}, @startup_time)
+
+ logger.info(message: 'Application boot finished', runtime: runtime.to_s, duration_s: @startup_time)
+ end
+
+ def reset!
+ @startup_time = nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 9bbcd1e056c..e646846face 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -7,6 +7,9 @@ module Gitlab
# This module relies on the /proc filesystem being available. If /proc is
# not available the methods of this module will be stubbed.
module System
+ extend self
+
+ PROC_STAT_PATH = '/proc/self/stat'
PROC_STATUS_PATH = '/proc/self/status'
PROC_SMAPS_ROLLUP_PATH = '/proc/self/smaps_rollup'
PROC_LIMITS_PATH = '/proc/self/limits'
@@ -17,7 +20,7 @@ module Gitlab
RSS_PATTERN = /VmRSS:\s+(?<value>\d+)/.freeze
MAX_OPEN_FILES_PATTERN = /Max open files\s*(?<value>\d+)/.freeze
- def self.summary
+ def summary
proportional_mem = memory_usage_uss_pss
{
version: RUBY_DESCRIPTION,
@@ -32,43 +35,43 @@ module Gitlab
end
# Returns the current process' RSS (resident set size) in bytes.
- def self.memory_usage_rss
+ def memory_usage_rss
sum_matches(PROC_STATUS_PATH, rss: RSS_PATTERN)[:rss].kilobytes
end
# Returns the current process' USS/PSS (unique/proportional set size) in bytes.
- def self.memory_usage_uss_pss
+ def memory_usage_uss_pss
sum_matches(PROC_SMAPS_ROLLUP_PATH, uss: PRIVATE_PAGES_PATTERN, pss: PSS_PATTERN)
.transform_values(&:kilobytes)
end
- def self.file_descriptor_count
+ def file_descriptor_count
Dir.glob(PROC_FD_GLOB).length
end
- def self.max_open_file_descriptors
+ def max_open_file_descriptors
sum_matches(PROC_LIMITS_PATH, max_fds: MAX_OPEN_FILES_PATTERN)[:max_fds]
end
- def self.cpu_time
+ def cpu_time
Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
end
# Returns the current real time in a given precision.
#
# Returns the time as a Float for precision = :float_second.
- def self.real_time(precision = :float_second)
+ def real_time(precision = :float_second)
Process.clock_gettime(Process::CLOCK_REALTIME, precision)
end
# Returns the current monotonic clock time as seconds with microseconds precision.
#
# Returns the time as a Float.
- def self.monotonic_time
+ def monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
end
- def self.thread_cpu_time
+ def thread_cpu_time
# Not all OS kernels are supporting `Process::CLOCK_THREAD_CPUTIME_ID`
# Refer: https://gitlab.com/gitlab-org/gitlab/issues/30567#note_221765627
return unless defined?(Process::CLOCK_THREAD_CPUTIME_ID)
@@ -76,33 +79,67 @@ module Gitlab
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second)
end
- def self.thread_cpu_duration(start_time)
+ def thread_cpu_duration(start_time)
end_time = thread_cpu_time
return unless start_time && end_time
end_time - start_time
end
+ # Returns the total time the current process has been running in seconds.
+ def process_runtime_elapsed_seconds
+ # Entry 22 (1-indexed) contains the process `starttime`, see:
+ # https://man7.org/linux/man-pages/man5/proc.5.html
+ #
+ # This value is a fixed timestamp in clock ticks.
+ # To obtain an elapsed time in seconds, we divide by the number
+ # of ticks per second and subtract from the system uptime.
+ start_time_ticks = proc_stat_entries[21].to_f
+ clock_ticks_per_second = Etc.sysconf(Etc::SC_CLK_TCK)
+ uptime - (start_time_ticks / clock_ticks_per_second)
+ end
+
+ private
+
# Given a path to a file in /proc and a hash of (metric, pattern) pairs,
# sums up all values found for those patterns under the respective metric.
- def self.sum_matches(proc_file, **patterns)
+ def sum_matches(proc_file, **patterns)
results = patterns.transform_values { 0 }
- begin
- File.foreach(proc_file) do |line|
+ safe_yield_procfile(proc_file) do |io|
+ io.each_line do |line|
patterns.each do |metric, pattern|
match = line.match(pattern)
value = match&.named_captures&.fetch('value', 0)
results[metric] += value.to_i
end
end
- rescue Errno::ENOENT
- # This means the procfile we're reading from did not exist;
- # this is safe to ignore, since we initialize each metric to 0
end
results
end
+
+ def proc_stat_entries
+ safe_yield_procfile(PROC_STAT_PATH) do |io|
+ io.read.split(' ')
+ end || []
+ end
+
+ def safe_yield_procfile(path, &block)
+ File.open(path, &block)
+ rescue Errno::ENOENT
+ # This means the procfile we're reading from did not exist;
+ # most likely we're on Darwin.
+ end
+
+ # Equivalent to reading /proc/uptime on Linux 2.6+.
+ #
+ # Returns 0 if not supported, e.g. on Darwin.
+ def uptime
+ Process.clock_gettime(Process::CLOCK_BOOTTIME)
+ rescue NameError
+ 0
+ end
end
end
end
diff --git a/lib/gitlab/runtime.rb b/lib/gitlab/runtime.rb
index 5fbbfd90be1..574e05658bc 100644
--- a/lib/gitlab/runtime.rb
+++ b/lib/gitlab/runtime.rb
@@ -31,6 +31,12 @@ module Gitlab
end
end
+ def safe_identify
+ identify
+ rescue UnknownProcessError, AmbiguousProcessError
+ nil
+ end
+
def puma?
!!defined?(::Puma)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 17b0264393a..88c0a230f7a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -40366,6 +40366,9 @@ msgstr ""
msgid "Webhooks|An issue is created, updated, closed, or reopened."
msgstr ""
+msgid "Webhooks|Are you sure you want to delete this webhook?"
+msgstr ""
+
msgid "Webhooks|Comments"
msgstr ""
@@ -40375,6 +40378,9 @@ msgstr ""
msgid "Webhooks|Confidential issues events"
msgstr ""
+msgid "Webhooks|Delete webhook"
+msgstr ""
+
msgid "Webhooks|Deployment events"
msgstr ""
diff --git a/package.json b/package.json
index 401d7cdf605..c54a0bb2ad0 100644
--- a/package.json
+++ b/package.json
@@ -62,36 +62,36 @@
"@rails/ujs": "6.1.4-1",
"@sentry/browser": "5.30.0",
"@sourcegraph/code-host-integration": "0.0.60",
- "@tiptap/core": "^2.0.0-beta.160",
+ "@tiptap/core": "^2.0.0-beta.171",
"@tiptap/extension-blockquote": "^2.0.0-beta.26",
"@tiptap/extension-bold": "^2.0.0-beta.25",
"@tiptap/extension-bullet-list": "^2.0.0-beta.26",
"@tiptap/extension-code": "^2.0.0-beta.26",
- "@tiptap/extension-code-block-lowlight": "2.0.0-beta.63",
+ "@tiptap/extension-code-block-lowlight": "2.0.0-beta.68",
"@tiptap/extension-document": "^2.0.0-beta.15",
"@tiptap/extension-dropcursor": "^2.0.0-beta.25",
"@tiptap/extension-gapcursor": "^2.0.0-beta.34",
"@tiptap/extension-hard-break": "^2.0.0-beta.30",
- "@tiptap/extension-heading": "^2.0.0-beta.24",
+ "@tiptap/extension-heading": "^2.0.0-beta.26",
"@tiptap/extension-history": "^2.0.0-beta.21",
- "@tiptap/extension-horizontal-rule": "^2.0.0-beta.30",
- "@tiptap/extension-image": "^2.0.0-beta.24",
+ "@tiptap/extension-horizontal-rule": "^2.0.0-beta.31",
+ "@tiptap/extension-image": "^2.0.0-beta.25",
"@tiptap/extension-italic": "^2.0.0-beta.25",
- "@tiptap/extension-link": "^2.0.0-beta.34",
+ "@tiptap/extension-link": "^2.0.0-beta.36",
"@tiptap/extension-list-item": "^2.0.0-beta.20",
"@tiptap/extension-ordered-list": "^2.0.0-beta.27",
"@tiptap/extension-paragraph": "^2.0.0-beta.23",
"@tiptap/extension-strike": "^2.0.0-beta.27",
"@tiptap/extension-subscript": "^2.0.0-beta.10",
"@tiptap/extension-superscript": "^2.0.0-beta.10",
- "@tiptap/extension-table": "^2.0.0-beta.46",
+ "@tiptap/extension-table": "^2.0.0-beta.48",
"@tiptap/extension-table-cell": "^2.0.0-beta.20",
"@tiptap/extension-table-header": "^2.0.0-beta.22",
"@tiptap/extension-table-row": "^2.0.0-beta.19",
- "@tiptap/extension-task-item": "^2.0.0-beta.30",
+ "@tiptap/extension-task-item": "^2.0.0-beta.31",
"@tiptap/extension-task-list": "^2.0.0-beta.26",
"@tiptap/extension-text": "^2.0.0-beta.15",
- "@tiptap/vue-2": "^2.0.0-beta.74",
+ "@tiptap/vue-2": "^2.0.0-beta.77",
"@toast-ui/editor": "^2.5.2",
"@toast-ui/vue-editor": "^2.5.2",
"apollo-cache-inmemory": "^1.6.6",
@@ -165,7 +165,7 @@
"prosemirror-model": "^1.16.1",
"prosemirror-state": "^1.3.4",
"prosemirror-tables": "^1.1.1",
- "prosemirror-view": "^1.23.5",
+ "prosemirror-view": "^1.23.6",
"raphael": "^2.2.7",
"raw-loader": "^4.0.2",
"scrollparent": "^2.0.1",
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index 3bcd87aee2e..3b1112725e3 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -209,6 +209,8 @@ function debug_rspec_variables() {
echoinfo "NEW_FLAKY_RSPEC_REPORT_PATH: ${NEW_FLAKY_RSPEC_REPORT_PATH}"
echoinfo "SKIPPED_FLAKY_TESTS_REPORT_PATH: ${SKIPPED_FLAKY_TESTS_REPORT_PATH}"
echoinfo "RETRIED_TESTS_REPORT_PATH: ${RETRIED_TESTS_REPORT_PATH}"
+
+ echoinfo "CRYSTALBALL: ${CRYSTALBALL}"
}
function rspec_paralellized_job() {
@@ -245,11 +247,7 @@ function rspec_paralellized_job() {
cp "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "${KNAPSACK_REPORT_PATH}"
- if [[ -z "${KNAPSACK_TEST_FILE_PATTERN}" ]]; then
- pattern=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(${spec_folder_prefixes}).pattern(:${test_level})")
- export KNAPSACK_TEST_FILE_PATTERN="${pattern}"
- fi
-
+ export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(${spec_folder_prefixes}).pattern(:${test_level})")
export FLAKY_RSPEC_REPORT_PATH="${rspec_flaky_folder_path}all_${report_name}_report.json"
export NEW_FLAKY_RSPEC_REPORT_PATH="${rspec_flaky_folder_path}new_${report_name}_report.json"
export SKIPPED_FLAKY_TESTS_REPORT_PATH="${rspec_flaky_folder_path}skipped_flaky_tests_${report_name}_report.txt"
@@ -285,21 +283,7 @@ function rspec_paralellized_job() {
# Experiment to retry failed examples in a new RSpec process: https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1148
if [[ $rspec_run_status -ne 0 ]]; then
if [[ "${RETRY_FAILED_TESTS_IN_NEW_PROCESS}" == "true" ]]; then
- # Keep track of the tests that are retried, later consolidated in a single file by the `rspec:flaky-tests-report` job
- local failed_examples=$(grep " failed" ${RSPEC_LAST_RUN_RESULTS_FILE})
- echo "${CI_JOB_URL}" > "${RETRIED_TESTS_REPORT_PATH}"
- echo $failed_examples >> "${RETRIED_TESTS_REPORT_PATH}"
-
- echoinfo "Retrying the failing examples in a new RSpec proces..."
-
- install_junit_merge_gem
-
- # Retry only the tests that failed on first try
- rspec_simple_job "--only-failures" "${JUNIT_RETRY_FILE}"
- rspec_run_status=$?
-
- # Merge the JUnit report from retry into the first-try report
- junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}"
+ $rspec_run_status=$(retry_failed_rspec_examples)
fi
else
echosuccess "No examples to retry, congrats!"
@@ -308,6 +292,31 @@ function rspec_paralellized_job() {
exit $rspec_run_status
}
+function retry_failed_rspec_examples() {
+ local rspec_run_status=0
+
+ # Keep track of the tests that are retried, later consolidated in a single file by the `rspec:flaky-tests-report` job
+ local failed_examples=$(grep " failed" ${RSPEC_LAST_RUN_RESULTS_FILE})
+ echo "${CI_JOB_URL}" > "${RETRIED_TESTS_REPORT_PATH}"
+ echo $failed_examples >> "${RETRIED_TESTS_REPORT_PATH}"
+
+ echoinfo "Retrying the failing examples in a new RSpec proces..."
+
+ install_junit_merge_gem
+
+ # Disable Crystalball on retry to not overwrite the existing report
+ export CRYSTALBALL="false"
+
+ # Retry only the tests that failed on first try
+ rspec_simple_job "--only-failures --pattern \"${KNAPSACK_TEST_FILE_PATTERN}\"" "${JUNIT_RETRY_FILE}"
+ rspec_run_status=$?
+
+ # Merge the JUnit report from retry into the first-try report
+ junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}"
+
+ return $rspec_run_status
+}
+
function rspec_rerun_previous_failed_tests() {
local test_file_count_threshold=${RSPEC_PREVIOUS_FAILED_TEST_FILE_COUNT_THRESHOLD:-10}
local matching_tests_file=${1}
diff --git a/spec/crystalball_env.rb b/spec/crystalball_env.rb
index d606fe69cdf..cd609dfb3f9 100644
--- a/spec/crystalball_env.rb
+++ b/spec/crystalball_env.rb
@@ -6,7 +6,7 @@ module CrystalballEnv
extend self
def start!
- return unless ENV['CRYSTALBALL']
+ return unless ENV['CRYSTALBALL'] == 'true'
require 'crystalball'
require_relative '../tooling/lib/tooling/crystalball/coverage_lines_execution_detector'
diff --git a/spec/fixtures/api/schemas/entities/member_user.json b/spec/fixtures/api/schemas/entities/member_user.json
index 41a1e510de5..d42c686bb65 100644
--- a/spec/fixtures/api/schemas/entities/member_user.json
+++ b/spec/fixtures/api/schemas/entities/member_user.json
@@ -1,6 +1,6 @@
{
"type": "object",
- "required": ["id", "name", "username", "avatar_url", "web_url", "blocked", "two_factor_enabled"],
+ "required": ["id", "name", "username", "avatar_url", "web_url", "blocked", "two_factor_enabled", "show_status"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
@@ -17,6 +17,7 @@
"emoji": { "type": "string" }
},
"additionalProperties": false
- }
+ },
+ "show_status": { "type": "boolean" }
}
}
diff --git a/spec/frontend/content_editor/test_utils.js b/spec/frontend/content_editor/test_utils.js
index b236c630e13..84eaa3c5f44 100644
--- a/spec/frontend/content_editor/test_utils.js
+++ b/spec/frontend/content_editor/test_utils.js
@@ -132,7 +132,7 @@ export const triggerNodeInputRule = ({ tiptapEditor, inputRuleText }) => {
export const triggerMarkInputRule = ({ tiptapEditor, inputRuleText }) => {
const { view } = tiptapEditor;
- tiptapEditor.chain().setContent(inputRuleText).setTextSelection(0).run();
+ tiptapEditor.chain().setContent(inputRuleText).setTextSelection(1).run();
const { state } = tiptapEditor;
const { selection } = state;
diff --git a/spec/frontend/members/components/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js
index 5cf3a4cdc13..7bcf4a11413 100644
--- a/spec/frontend/members/components/avatars/user_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/user_avatar_spec.js
@@ -1,7 +1,8 @@
import { GlAvatarLink, GlBadge } from '@gitlab/ui';
-import { within } from '@testing-library/dom';
-import { mount, createWrapper } from '@vue/test-utils';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import UserAvatar from '~/members/components/avatars/user_avatar.vue';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+
import { member as memberMock, member2faEnabled, orphanedMember } from '../../mock_data';
describe('UserAvatar', () => {
@@ -10,7 +11,7 @@ describe('UserAvatar', () => {
const { user } = memberMock;
const createComponent = (propsData = {}, provide = {}) => {
- wrapper = mount(UserAvatar, {
+ wrapper = mountExtended(UserAvatar, {
propsData: {
member: memberMock,
isCurrentUser: false,
@@ -23,9 +24,6 @@ describe('UserAvatar', () => {
});
};
- const getByText = (text, options) =>
- createWrapper(within(wrapper.element).findByText(text, options));
-
const findStatusEmoji = (emoji) => wrapper.find(`gl-emoji[data-name="${emoji}"]`);
afterEach(() => {
@@ -48,13 +46,13 @@ describe('UserAvatar', () => {
it("renders user's name", () => {
createComponent();
- expect(getByText(user.name).exists()).toBe(true);
+ expect(wrapper.findByText(user.name).exists()).toBe(true);
});
it("renders user's username", () => {
createComponent();
- expect(getByText(`@${user.username}`).exists()).toBe(true);
+ expect(wrapper.findByText(`@${user.username}`).exists()).toBe(true);
});
it("renders user's avatar", () => {
@@ -67,7 +65,7 @@ describe('UserAvatar', () => {
it('displays an orphaned user', () => {
createComponent({ member: orphanedMember });
- expect(getByText('Orphaned member').exists()).toBe(true);
+ expect(wrapper.findByText('Orphaned member').exists()).toBe(true);
});
});
@@ -85,13 +83,13 @@ describe('UserAvatar', () => {
it('renders the "It\'s you" badge when member is current user', () => {
createComponent({ isCurrentUser: true });
- expect(getByText("It's you").exists()).toBe(true);
+ expect(wrapper.findByText("It's you").exists()).toBe(true);
});
it('does not render 2FA badge when `canManageMembers` is `false`', () => {
createComponent({ member: member2faEnabled }, { canManageMembers: false });
- expect(within(wrapper.element).queryByText('2FA')).toBe(null);
+ expect(wrapper.findByText('2FA').exists()).toBe(false);
});
});
@@ -112,6 +110,23 @@ describe('UserAvatar', () => {
expect(findStatusEmoji(emoji).exists()).toBe(true);
});
+
+ describe('when `user.showStatus` is `false', () => {
+ it('does not display status emoji', () => {
+ createComponent({
+ member: {
+ ...memberMock,
+ user: {
+ ...memberMock.user,
+ showStatus: false,
+ status: { emoji, messageHtml: 'On vacation' },
+ },
+ },
+ });
+
+ expect(findStatusEmoji(emoji).exists()).toBe(false);
+ });
+ });
});
describe('when not set', () => {
@@ -122,4 +137,30 @@ describe('UserAvatar', () => {
});
});
});
+
+ describe('user availability', () => {
+ describe('when `user.availability` is `null`', () => {
+ it("does not show `(Busy)` next to user's name", () => {
+ createComponent();
+
+ expect(wrapper.findByText('(Busy)').exists()).toBe(false);
+ });
+ });
+
+ describe(`when user.availability is ${AVAILABILITY_STATUS.BUSY}`, () => {
+ it("shows `(Busy)` next to user's name", () => {
+ createComponent({
+ member: {
+ ...memberMock,
+ user: {
+ ...memberMock.user,
+ availability: AVAILABILITY_STATUS.BUSY,
+ },
+ },
+ });
+
+ expect(wrapper.findByText('(Busy)').exists()).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index 218db0b587a..83856a00a15 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -25,6 +25,8 @@ export const member = {
twoFactorEnabled: false,
oncallSchedules: [{ name: 'schedule 1' }],
escalationPolicies: [{ name: 'policy 1' }],
+ availability: null,
+ showStatus: true,
},
id: 238,
createdAt: '2020-07-17T16:22:46.923Z',
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index eb9b443c694..cbccdcece5d 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -3,7 +3,6 @@ import { mount, shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -13,7 +12,7 @@ import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import BlobEdit from '~/repository/components/blob_edit.vue';
import ForkSuggestion from '~/repository/components/fork_suggestion.vue';
-import { loadViewer, viewerProps } from '~/repository/components/blob_viewers';
+import { loadViewer } from '~/repository/components/blob_viewers';
import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
import SourceViewer from '~/vue_shared/components/source_viewer.vue';
@@ -51,6 +50,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
createMergeRequestIn = userPermissionsMock.createMergeRequestIn,
isBinary,
inject = {},
+ highlightJs = true,
} = mockData;
const project = {
@@ -78,7 +78,12 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
apolloProvider: fakeApollo,
propsData: propsMock,
mixins: [{ data: () => ({ ref: refMock }) }],
- provide: { ...inject },
+ provide: {
+ ...inject,
+ glFeatures: {
+ highlightJs,
+ },
+ },
}),
);
@@ -99,7 +104,6 @@ describe('Blob content viewer component', () => {
const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion);
beforeEach(() => {
- gon.features = { highlightJs: true };
isLoggedIn.mockReturnValue(true);
});
@@ -137,6 +141,15 @@ describe('Blob content viewer component', () => {
});
describe('legacy viewers', () => {
+ it('loads a legacy viewer when a the fileType is text and the highlightJs feature is turned off', async () => {
+ await createComponent({
+ blob: { ...simpleViewerMock, fileType: 'text', highlightJs: false },
+ });
+
+ expect(mockAxios.history.get).toHaveLength(1);
+ expect(mockAxios.history.get[0].url).toEqual('some_file.js?format=json&viewer=simple');
+ });
+
it('loads a legacy viewer when a viewer component is not available', async () => {
await createComponent({ blob: { ...simpleViewerMock, fileType: 'unknown' } });
@@ -202,7 +215,6 @@ describe('Blob content viewer component', () => {
describe('Blob viewer', () => {
afterEach(() => {
loadViewer.mockRestore();
- viewerProps.mockRestore();
});
it('does not render a BlobContent component if a Blob viewer is available', async () => {
@@ -213,33 +225,29 @@ describe('Blob content viewer component', () => {
});
it.each`
- viewer | loadViewerReturnValue | viewerPropsReturnValue
- ${'empty'} | ${EmptyViewer} | ${{}}
- ${'download'} | ${DownloadViewer} | ${{ filePath: '/some/file/path', fileName: 'test.js', fileSize: 100 }}
- ${'text'} | ${SourceViewer} | ${{ content: 'test', autoDetect: true }}
- `(
- 'renders viewer component for $viewer files',
- async ({ viewer, loadViewerReturnValue, viewerPropsReturnValue }) => {
- loadViewer.mockReturnValue(loadViewerReturnValue);
- viewerProps.mockReturnValue(viewerPropsReturnValue);
-
- createComponent({
- blob: {
- ...simpleViewerMock,
- fileType: 'null',
- simpleViewer: {
- ...simpleViewerMock.simpleViewer,
- fileType: viewer,
- },
+ viewer | loadViewerReturnValue
+ ${'empty'} | ${EmptyViewer}
+ ${'download'} | ${DownloadViewer}
+ ${'text'} | ${SourceViewer}
+ `('renders viewer component for $viewer files', async ({ viewer, loadViewerReturnValue }) => {
+ loadViewer.mockReturnValue(loadViewerReturnValue);
+
+ createComponent({
+ blob: {
+ ...simpleViewerMock,
+ fileType: 'null',
+ simpleViewer: {
+ ...simpleViewerMock.simpleViewer,
+ fileType: viewer,
},
- });
+ },
+ });
- await waitForPromises();
+ await waitForPromises();
- expect(loadViewer).toHaveBeenCalledWith(viewer, false);
- expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
- },
- );
+ expect(loadViewer).toHaveBeenCalledWith(viewer, false);
+ expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
+ });
});
describe('BlobHeader action slot', () => {
diff --git a/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js
index c71b2b3c55c..5fe25ced302 100644
--- a/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js
@@ -6,42 +6,33 @@ import DownloadViewer from '~/repository/components/blob_viewers/download_viewer
describe('Text Viewer', () => {
let wrapper;
- const DEFAULT_PROPS = {
- fileName: 'file_name.js',
- filePath: '/some/file/path',
- fileSize: 2269674,
+ const DEFAULT_BLOB_DATA = {
+ name: 'file_name.js',
+ rawPath: '/some/file/path',
+ rawSize: 2269674,
};
- const createComponent = (props = {}) => {
+ const createComponent = (blobData = {}) => {
wrapper = shallowMount(DownloadViewer, {
propsData: {
- ...DEFAULT_PROPS,
- ...props,
+ blob: {
+ ...DEFAULT_BLOB_DATA,
+ ...blobData,
+ },
},
});
};
- it('renders component', () => {
- createComponent();
-
- const { fileName, filePath, fileSize } = DEFAULT_PROPS;
- expect(wrapper.props()).toMatchObject({
- fileName,
- filePath,
- fileSize,
- });
- });
-
it('renders download human readable file size text', () => {
createComponent();
- const downloadText = `Download (${numberToHumanSize(DEFAULT_PROPS.fileSize)})`;
+ const downloadText = `Download (${numberToHumanSize(DEFAULT_BLOB_DATA.rawSize)})`;
expect(wrapper.text()).toBe(downloadText);
});
it('renders download text', () => {
createComponent({
- fileSize: 0,
+ rawSize: 0,
});
expect(wrapper.text()).toBe('Download');
@@ -49,13 +40,13 @@ describe('Text Viewer', () => {
it('renders download link', () => {
createComponent();
- const { filePath, fileName } = DEFAULT_PROPS;
+ const { rawPath, name } = DEFAULT_BLOB_DATA;
expect(wrapper.findComponent(GlLink).attributes()).toMatchObject({
rel: 'nofollow',
target: '_blank',
- href: filePath,
- download: fileName,
+ href: rawPath,
+ download: name,
});
});
diff --git a/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js
index 6735dddf51e..c23de0efdfd 100644
--- a/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js
@@ -4,13 +4,13 @@ import ImageViewer from '~/repository/components/blob_viewers/image_viewer.vue';
describe('Image Viewer', () => {
let wrapper;
- const propsData = {
- url: 'some/image.png',
- alt: 'image.png',
+ const DEFAULT_BLOB_DATA = {
+ rawPath: 'some/image.png',
+ name: 'image.png',
};
const createComponent = () => {
- wrapper = shallowMount(ImageViewer, { propsData });
+ wrapper = shallowMount(ImageViewer, { propsData: { blob: DEFAULT_BLOB_DATA } });
};
const findImage = () => wrapper.find('[data-testid="image"]');
@@ -19,7 +19,7 @@ describe('Image Viewer', () => {
createComponent();
expect(findImage().exists()).toBe(true);
- expect(findImage().attributes('src')).toBe(propsData.url);
- expect(findImage().attributes('alt')).toBe(propsData.alt);
+ expect(findImage().attributes('src')).toBe(DEFAULT_BLOB_DATA.rawPath);
+ expect(findImage().attributes('alt')).toBe(DEFAULT_BLOB_DATA.name);
});
});
diff --git a/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
index 59cca23ebfc..5caeb85834d 100644
--- a/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
@@ -5,14 +5,14 @@ import LfsViewer from '~/repository/components/blob_viewers/lfs_viewer.vue';
describe('LFS Viewer', () => {
let wrapper;
- const DEFAULT_PROPS = {
- fileName: 'file_name.js',
- filePath: '/some/file/path',
+ const DEFAULT_BLOB_DATA = {
+ name: 'file_name.js',
+ rawPath: '/some/file/path',
};
const createComponent = () => {
wrapper = shallowMount(LfsViewer, {
- propsData: { ...DEFAULT_PROPS },
+ propsData: { blob: { ...DEFAULT_BLOB_DATA } },
stubs: { GlSprintf },
});
};
@@ -30,12 +30,12 @@ describe('LFS Viewer', () => {
});
it('renders download link', () => {
- const { filePath, fileName } = DEFAULT_PROPS;
+ const { rawPath, name } = DEFAULT_BLOB_DATA;
expect(findLink().attributes()).toMatchObject({
target: '_blank',
- href: filePath,
- download: fileName,
+ href: rawPath,
+ download: name,
});
});
});
diff --git a/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js
index fd910002529..10eea691335 100644
--- a/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js
@@ -6,10 +6,12 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('PDF Viewer', () => {
let wrapper;
- const defaultPropsData = { url: 'some/pdf_blob.pdf' };
+ const DEFAULT_BLOB_DATA = { rawPath: 'some/pdf_blob.pdf' };
- const createComponent = (fileSize = 999) => {
- wrapper = shallowMountExtended(Component, { propsData: { ...defaultPropsData, fileSize } });
+ const createComponent = (rawSize = 999) => {
+ wrapper = shallowMountExtended(Component, {
+ propsData: { blob: { ...DEFAULT_BLOB_DATA, rawSize } },
+ });
};
const findPDFViewer = () => wrapper.findComponent(PdfViewer);
@@ -20,7 +22,7 @@ describe('PDF Viewer', () => {
createComponent();
expect(findPDFViewer().exists()).toBe(true);
- expect(findPDFViewer().props('pdf')).toBe(defaultPropsData.url);
+ expect(findPDFViewer().props('pdf')).toBe(DEFAULT_BLOB_DATA.rawPath);
});
describe('Too large', () => {
diff --git a/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js
index 34448c03b31..2e79a1496ce 100644
--- a/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js
@@ -4,10 +4,10 @@ import VideoViewer from '~/repository/components/blob_viewers/video_viewer.vue';
describe('Video Viewer', () => {
let wrapper;
- const propsData = { url: 'some/video.mp4' };
+ const DEFAULT_BLOB_DATA = { rawPath: 'some/video.mp4' };
const createComponent = () => {
- wrapper = shallowMountExtended(VideoViewer, { propsData });
+ wrapper = shallowMountExtended(VideoViewer, { propsData: { blob: { ...DEFAULT_BLOB_DATA } } });
};
const findVideo = () => wrapper.findByTestId('video');
@@ -16,7 +16,7 @@ describe('Video Viewer', () => {
createComponent();
expect(findVideo().exists()).toBe(true);
- expect(findVideo().attributes('src')).toBe(propsData.url);
+ expect(findVideo().attributes('src')).toBe(DEFAULT_BLOB_DATA.rawPath);
expect(findVideo().attributes('controls')).not.toBeUndefined();
});
});
diff --git a/spec/frontend/vue_shared/components/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer_spec.js
index 094d8d42a47..a0d07ddc245 100644
--- a/spec/frontend/vue_shared/components/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer_spec.js
@@ -12,17 +12,18 @@ const router = new VueRouter();
describe('Source Viewer component', () => {
let wrapper;
+ const language = 'javascript';
const content = `// Some source code`;
+ const DEFAULT_BLOB_DATA = { language, rawTextBlob: content };
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
- const language = 'javascript';
hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
- const createComponent = async (props = {}) => {
+ const createComponent = async (props = { autoDetect: false }) => {
wrapper = shallowMountExtended(SourceViewer, {
router,
- propsData: { content, language, ...props },
+ propsData: { blob: { ...DEFAULT_BLOB_DATA }, ...props },
});
await waitForPromises();
};
diff --git a/spec/lib/gitlab/console_spec.rb b/spec/lib/gitlab/console_spec.rb
new file mode 100644
index 00000000000..f043433b4c5
--- /dev/null
+++ b/spec/lib/gitlab/console_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Console do
+ describe '.welcome!' do
+ context 'when running in the Rails console' do
+ before do
+ allow(Gitlab::Runtime).to receive(:console?).and_return(true)
+ allow(Gitlab::Metrics::BootTimeTracker.instance).to receive(:startup_time).and_return(42)
+ end
+
+ shared_examples 'console messages' do
+ it 'prints system info' do
+ expect($stdout).to receive(:puts).ordered.with(include("--"))
+ expect($stdout).to receive(:puts).ordered.with(include("Ruby:"))
+ expect($stdout).to receive(:puts).ordered.with(include("GitLab:"))
+ expect($stdout).to receive(:puts).ordered.with(include("GitLab Shell:"))
+ expect($stdout).to receive(:puts).ordered.with(include("PostgreSQL:"))
+ expect($stdout).to receive(:puts).ordered.with(include("--"))
+ expect($stdout).not_to receive(:puts).ordered
+
+ described_class.welcome!
+ end
+ end
+
+ # This is to add line coverage, not to actually verify behavior on macOS.
+ context 'on darwin' do
+ before do
+ stub_const('RUBY_PLATFORM', 'x86_64-darwin-19')
+ end
+
+ it_behaves_like 'console messages'
+ end
+
+ it_behaves_like 'console messages'
+ end
+
+ context 'when not running in the Rails console' do
+ before do
+ allow(Gitlab::Runtime).to receive(:console?).and_return(false)
+ end
+
+ it 'does not print anything' do
+ expect($stdout).not_to receive(:puts)
+
+ described_class.welcome!
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/boot_time_tracker_spec.rb b/spec/lib/gitlab/metrics/boot_time_tracker_spec.rb
new file mode 100644
index 00000000000..b6497766a20
--- /dev/null
+++ b/spec/lib/gitlab/metrics/boot_time_tracker_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Metrics::BootTimeTracker do
+ let(:logger) { double('logger') }
+ let(:gauge) { double('gauge') }
+
+ subject(:tracker) { described_class.instance }
+
+ before do
+ described_class.instance.reset!
+
+ allow(logger).to receive(:info)
+ allow(gauge).to receive(:set)
+ allow(Gitlab::Metrics).to receive(:gauge).and_return(gauge)
+ end
+
+ describe '#track_boot_time!' do
+ described_class::SUPPORTED_RUNTIMES.each do |runtime|
+ context "when called on #{runtime} for the first time" do
+ before do
+ expect(Gitlab::Runtime).to receive(:safe_identify).and_return(runtime)
+ end
+
+ it 'set the startup_time' do
+ tracker.track_boot_time!(logger: logger)
+
+ expect(tracker.startup_time).to be > 0
+ end
+
+ it 'records the current process runtime' do
+ expect(Gitlab::Metrics::System).to receive(:process_runtime_elapsed_seconds).once
+
+ tracker.track_boot_time!(logger: logger)
+ end
+
+ it 'logs the application boot time' do
+ expect(Gitlab::Metrics::System).to receive(:process_runtime_elapsed_seconds).and_return(42)
+ expect(logger).to receive(:info).with(message: 'Application boot finished', runtime: runtime.to_s, duration_s: 42)
+
+ tracker.track_boot_time!(logger: logger)
+ end
+
+ it 'tracks boot time in a prometheus gauge' do
+ expect(Gitlab::Metrics::System).to receive(:process_runtime_elapsed_seconds).and_return(42)
+ expect(gauge).to receive(:set).with({}, 42)
+
+ tracker.track_boot_time!(logger: logger)
+ end
+
+ context 'on subsequent calls' do
+ it 'does nothing' do
+ tracker.track_boot_time!(logger: logger)
+
+ expect(Gitlab::Metrics::System).not_to receive(:process_runtime_elapsed_seconds)
+ expect(logger).not_to receive(:info)
+ expect(gauge).not_to receive(:set)
+
+ tracker.track_boot_time!(logger: logger)
+ end
+ end
+ end
+ end
+
+ context 'when called on other runtimes' do
+ it 'does nothing' do
+ tracker.track_boot_time!(logger: logger)
+
+ expect(Gitlab::Metrics::System).not_to receive(:process_runtime_elapsed_seconds)
+ expect(logger).not_to receive(:info)
+ expect(gauge).not_to receive(:set)
+
+ tracker.track_boot_time!(logger: logger)
+ end
+ end
+
+ # TODO: When https://gitlab.com/gitlab-org/gitlab/-/issues/351769 is closed,
+ # revert to using fast_spec_helper again.
+ context 'when feature flag is off' do
+ it 'does nothing' do
+ stub_feature_flags(track_application_boot_time: false)
+
+ expect(Gitlab::Metrics::System).not_to receive(:process_runtime_elapsed_seconds)
+ expect(logger).not_to receive(:info)
+ expect(gauge).not_to receive(:set)
+
+ tracker.track_boot_time!(logger: logger)
+ end
+ end
+ end
+
+ describe '#startup_time' do
+ it 'returns 0 when boot time not tracked' do
+ expect(tracker.startup_time).to eq(0)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 732aa553737..ce3caf8cdfe 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -4,6 +4,13 @@ require 'spec_helper'
RSpec.describe Gitlab::Metrics::System do
context 'when /proc files exist' do
+ # Modified column 22 to be 1000 (starttime ticks)
+ let(:proc_stat) do
+ <<~SNIP
+ 2095 (ruby) R 0 2095 2095 34818 2095 4194560 211267 7897 2 0 287 51 10 1 20 0 5 0 1000 566210560 80885 18446744073709551615 94736211292160 94736211292813 140720919612064 0 0 0 0 0 1107394127 0 0 0 17 3 0 0 0 0 0 94736211303768 94736211304544 94736226689024 140720919619473 140720919619513 140720919619513 140720919621604 0
+ SNIP
+ end
+
# Fixtures pulled from:
# Linux carbon 5.3.0-7648-generic #41~1586789791~19.10~9593806-Ubuntu SMP Mon Apr 13 17:50:40 UTC x86_64 x86_64 x86_64 GNU/Linux
let(:proc_status) do
@@ -97,6 +104,29 @@ RSpec.describe Gitlab::Metrics::System do
end
end
+ describe '.process_runtime_elapsed_seconds' do
+ it 'returns the seconds elapsed since the process was started' do
+ # sets process starttime ticks to 1000
+ mock_existing_proc_file('/proc/self/stat', proc_stat)
+ # system clock ticks/sec
+ expect(Etc).to receive(:sysconf).with(Etc::SC_CLK_TCK).and_return(100)
+ # system uptime in seconds
+ expect(::Process).to receive(:clock_gettime).and_return(15)
+
+ # uptime - (starttime_ticks / ticks_per_sec)
+ expect(described_class.process_runtime_elapsed_seconds).to eq(5)
+ end
+
+ context 'when inputs are not available' do
+ it 'returns 0' do
+ mock_missing_proc_file
+ expect(::Process).to receive(:clock_gettime).and_raise(NameError)
+
+ expect(described_class.process_runtime_elapsed_seconds).to eq(0)
+ end
+ end
+ end
+
describe '.summary' do
it 'contains a selection of the available fields' do
stub_const('RUBY_DESCRIPTION', 'ruby-3.0-patch1')
@@ -223,10 +253,10 @@ RSpec.describe Gitlab::Metrics::System do
end
def mock_existing_proc_file(path, content)
- allow(File).to receive(:foreach).with(path) { |_path, &block| content.each_line(&block) }
+ allow(File).to receive(:open).with(path) { |_path, &block| block.call(StringIO.new(content)) }
end
def mock_missing_proc_file
- allow(File).to receive(:foreach).and_raise(Errno::ENOENT)
+ allow(File).to receive(:open).and_raise(Errno::ENOENT)
end
end
diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb
index 4627a8db82e..402b72b9220 100644
--- a/spec/lib/gitlab/runtime_spec.rb
+++ b/spec/lib/gitlab/runtime_spec.rb
@@ -26,8 +26,16 @@ RSpec.describe Gitlab::Runtime do
end
context "when unknown" do
- it "raises an exception when trying to identify" do
- expect { subject.identify }.to raise_error(subject::UnknownProcessError)
+ describe '.identify' do
+ it "raises an exception when trying to identify" do
+ expect { subject.identify }.to raise_error(subject::UnknownProcessError)
+ end
+ end
+
+ describe '.safe_identify' do
+ it "returns nil" do
+ expect(subject.safe_identify).to be_nil
+ end
end
end
@@ -37,8 +45,16 @@ RSpec.describe Gitlab::Runtime do
stub_const('::Rails::Console', double)
end
- it "raises an exception when trying to identify" do
- expect { subject.identify }.to raise_error(subject::AmbiguousProcessError)
+ describe '.identify' do
+ it "raises an exception when trying to identify" do
+ expect { subject.identify }.to raise_error(subject::AmbiguousProcessError)
+ end
+ end
+
+ describe '.safe_identify' do
+ it "returns nil" do
+ expect(subject.safe_identify).to be_nil
+ end
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 4d3a2fac0fc..7c67b9a3d63 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -748,29 +748,23 @@ eos
describe '#work_in_progress?' do
[
- 'squash! ', 'fixup! ', 'wip: ', 'WIP: ', '[WIP] ',
+ 'squash! ', 'fixup! ',
'draft: ', '[Draft] ', '(draft) ', 'Draft: '
- ].each do |wip_prefix|
- it "detects the '#{wip_prefix}' prefix" do
- commit.message = "#{wip_prefix}#{commit.message}"
+ ].each do |draft_prefix|
+ it "detects the '#{draft_prefix}' prefix" do
+ commit.message = "#{draft_prefix}#{commit.message}"
expect(commit).to be_work_in_progress
end
end
- it "detects WIP for a commit just saying 'wip'" do
- commit.message = "wip"
-
- expect(commit).to be_work_in_progress
- end
-
it "does not detect WIP for a commit just saying 'draft'" do
commit.message = "draft"
expect(commit).not_to be_work_in_progress
end
- ["FIXUP!", "Draft - ", "Wipeout"].each do |draft_prefix|
+ ["FIXUP!", "Draft - ", "Wipeout", "WIP: ", "[WIP] ", "wip: "].each do |draft_prefix|
it "doesn't detect '#{draft_prefix}' at the start of the title as a draft" do
commit.message = "#{draft_prefix} #{commit.message}"
diff --git a/spec/tooling/lib/tooling/test_map_generator_spec.rb b/spec/tooling/lib/tooling/test_map_generator_spec.rb
index eb49b1db20e..b52d78b01a3 100644
--- a/spec/tooling/lib/tooling/test_map_generator_spec.rb
+++ b/spec/tooling/lib/tooling/test_map_generator_spec.rb
@@ -39,11 +39,23 @@ RSpec.describe Tooling::TestMapGenerator do
YAML
end
+ let(:yaml3) do
+ <<~YAML
+ ---
+ :type: Crystalball::ExecutionMap
+ :commit: 74056e8d9cf3773f43faa1cf5416f8779c8284c9
+ :timestamp: 1602671965
+ :version:
+ ---
+ YAML
+ end
+
let(:pathname) { instance_double(Pathname) }
before do
stub_file_read('yaml1.yml', content: yaml1)
stub_file_read('yaml2.yml', content: yaml2)
+ stub_file_read('yaml3.yml', content: yaml3)
end
context 'with single yaml' do
@@ -74,6 +86,10 @@ RSpec.describe Tooling::TestMapGenerator do
expect(subject.mapping[file]).to match_array(tests)
end
end
+
+ it 'displays a warning when report has no examples' do
+ expect { subject.parse('yaml3.yml') }.to output(%|No examples in yaml3.yml! Metadata: {:type=>"Crystalball::ExecutionMap", :commit=>"74056e8d9cf3773f43faa1cf5416f8779c8284c9", :timestamp=>1602671965, :version=>nil}\n|).to_stdout
+ end
end
context 'with multiple yamls' do
diff --git a/tooling/lib/tooling/test_map_generator.rb b/tooling/lib/tooling/test_map_generator.rb
index 20e4ed8e405..f96f33ff074 100644
--- a/tooling/lib/tooling/test_map_generator.rb
+++ b/tooling/lib/tooling/test_map_generator.rb
@@ -12,7 +12,12 @@ module Tooling
def parse(yaml_files)
Array(yaml_files).each do |yaml_file|
data = File.read(yaml_file)
- _metadata, example_groups = data.split("---\n").reject(&:empty?).map { |yml| YAML.safe_load(yml, [Symbol]) }
+ metadata, example_groups = data.split("---\n").reject(&:empty?).map { |yml| YAML.safe_load(yml, [Symbol]) }
+
+ if example_groups.nil?
+ puts "No examples in #{yaml_file}! Metadata: #{metadata}"
+ next
+ end
example_groups.each do |example_id, files|
files.each do |file|
diff --git a/yarn.lock b/yarn.lock
index b94a11ef175..087ad059dc9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1513,25 +1513,25 @@
dom-accessibility-api "^0.5.1"
pretty-format "^26.4.2"
-"@tiptap/core@^2.0.0-beta.160":
- version "2.0.0-beta.160"
- resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.160.tgz#98f0625dc12a3498c6bbe2ae4aed4667b62a2a11"
- integrity sha512-oKtx4vn4ew+mCH3kiBeD4UdPa5IVLLBCuSgGNnqdoIuRv397uh5SwOz7TiL5A+vTa6yke0xLSKxZ+QNR2qfVXg==
+"@tiptap/core@^2.0.0-beta.171":
+ version "2.0.0-beta.171"
+ resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.171.tgz#e681964c443383b81d2638c51fc3bbfda034a4fb"
+ integrity sha512-4CdJfcchmBOFooWPBMJ7AxJISeTstMFriQv0RyReMt0Dpef/c9UoU+NkKLwwv5VRUX0M8dL5SzEhkB8wIODqlA==
dependencies:
"@types/prosemirror-commands" "^1.0.4"
"@types/prosemirror-keymap" "^1.0.4"
- "@types/prosemirror-model" "^1.13.2"
+ "@types/prosemirror-model" "^1.16.0"
"@types/prosemirror-schema-list" "^1.0.3"
"@types/prosemirror-state" "^1.2.8"
"@types/prosemirror-transform" "^1.1.5"
- "@types/prosemirror-view" "^1.19.2"
- prosemirror-commands "^1.1.12"
+ "@types/prosemirror-view" "^1.23.1"
+ prosemirror-commands "^1.2.1"
prosemirror-keymap "^1.1.5"
prosemirror-model "^1.16.1"
prosemirror-schema-list "^1.1.6"
prosemirror-state "^1.3.4"
prosemirror-transform "^1.3.3"
- prosemirror-view "^1.23.5"
+ prosemirror-view "^1.23.6"
"@tiptap/extension-blockquote@^2.0.0-beta.26":
version "2.0.0-beta.26"
@@ -1543,13 +1543,13 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.25.tgz#ec19e7c862d25bae49609c5d6a873f372c506dee"
integrity sha512-ZNdgFYDxKo8lAp0Pqzu45I0JH3ah8/X5TCYg9zNg3QwLUFT16g2LlWDMUDGT5pH9aXxgtFaEdoVacu0EyhlPnQ==
-"@tiptap/extension-bubble-menu@^2.0.0-beta.54":
- version "2.0.0-beta.54"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.54.tgz#90ac838cb7899317207037abb74ed2f68528bd22"
- integrity sha512-jSvNy+ZVHIzEvf8BY/pLpir7CB4lWL4RNUWLT6YMmGCzdzYJK3RZ6Qp0Yoo7UMTjA5JDGn3Ax1lDj4qcsoXwzA==
+"@tiptap/extension-bubble-menu@^2.0.0-beta.55":
+ version "2.0.0-beta.55"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.55.tgz#a26ad892cea6af9eeada22235701b06d0921af48"
+ integrity sha512-v32/QnwwRbepdbrho8mTYru1/XNW/rJi3Mjrgo3rrIs67R86aEPmhmdzD3QEQUJhAJkduuwdw8zElmVWqIJQ9w==
dependencies:
prosemirror-state "^1.3.4"
- prosemirror-view "^1.23.5"
+ prosemirror-view "^1.23.6"
tippy.js "^6.3.7"
"@tiptap/extension-bullet-list@^2.0.0-beta.26":
@@ -1557,22 +1557,22 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.26.tgz#b42126d2d984c04041b14037e8d3ec1bcf16e7ec"
integrity sha512-1n5HV8gY1tLjPk4x48nva6SZlFHoPlRfF6pqSu9JcJxPO7FUSPxUokuz4swYNe0LRrtykfyNz44dUcxKVhoFow==
-"@tiptap/extension-code-block-lowlight@2.0.0-beta.63":
- version "2.0.0-beta.63"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.63.tgz#5d99e7693c2181d15f6599b4a20aa386efdc9c00"
- integrity sha512-Eam+j5YO7ulytOL+NMIhoEPZsAQag9hL19q17ctgl1k1yPwdLuUtU0lkIr0P+qxSzWXUGWX6q/nXM92Vb4hVPA==
+"@tiptap/extension-code-block-lowlight@2.0.0-beta.68":
+ version "2.0.0-beta.68"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.68.tgz#c0da8c327eb2b49d808e3ead303f6ff6b0203e05"
+ integrity sha512-927vw4DJ5pq3lYOdJbBrCjJZ/YsbsEzWkb7SwRsaztDXcgsF2z8gt6dy7onXMaqm5rDx6eNofLPdhUyLCswW8Q==
dependencies:
- "@tiptap/extension-code-block" "^2.0.0-beta.33"
+ "@tiptap/extension-code-block" "^2.0.0-beta.37"
"@types/lowlight" "^0.0.3"
lowlight "^1.20.0"
prosemirror-model "^1.16.1"
prosemirror-state "^1.3.4"
- prosemirror-view "^1.23.5"
+ prosemirror-view "^1.23.6"
-"@tiptap/extension-code-block@^2.0.0-beta.33":
- version "2.0.0-beta.33"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.33.tgz#963b06ec88f97f79e1c90b3b830eff826a4bad88"
- integrity sha512-zKiGxbbnZJrKXr0DtBd0B7yXvY3wRk5Y5BsJqe0ZsyXR8lCEY4+DcOPpr6dja2gpgQIB9+vtqZvgBfnnUXXTcw==
+"@tiptap/extension-code-block@^2.0.0-beta.37":
+ version "2.0.0-beta.37"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.37.tgz#c07c007248a21d9e0434458fd05c363b7078227f"
+ integrity sha512-mJAM+PHaNoKRYwM3D36lZ51/aoPxxvZNQn3UBnZ6G7l0ZJSgB3JvBEzqK6S8nNFeYIIxGwv4QF6vXe4MG9ie2g==
dependencies:
prosemirror-state "^1.3.4"
@@ -1594,13 +1594,13 @@
"@types/prosemirror-dropcursor" "^1.0.3"
prosemirror-dropcursor "^1.4.0"
-"@tiptap/extension-floating-menu@^2.0.0-beta.49":
- version "2.0.0-beta.49"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.49.tgz#36682849f50e217137775d7f738af952a0434113"
- integrity sha512-VY0d+fJmz8FP1CxqB9u09xCZe/9Wtwff+SxY+LGpflVf0JjS+CWcaVIDNF8zHG13SMobQ5xH6RRnSuTzlS2bRA==
+"@tiptap/extension-floating-menu@^2.0.0-beta.50":
+ version "2.0.0-beta.50"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.50.tgz#e8785d5f051a848ae053ce139581dce96b951a35"
+ integrity sha512-aQu1HtthMIYEPylr6kzioLxMiObLbcgwx9xZzF03KwNnkjQLbjZOeJX2RwSYVpiVgtfPBGOm3N/br6NSYec4yQ==
dependencies:
prosemirror-state "^1.3.4"
- prosemirror-view "^1.23.5"
+ prosemirror-view "^1.23.6"
tippy.js "^6.3.7"
"@tiptap/extension-gapcursor@^2.0.0-beta.34":
@@ -1616,10 +1616,10 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.30.tgz#165494f1194a7bad08907e6d64d349dd15851b72"
integrity sha512-X9xj/S+CikrbIE7ccUFVwit5QHEbflnKVxod+4zPwr1cxogFbE9AyLZE2MpYdx3z9LcnTYYi9leBqFrP4T/Olw==
-"@tiptap/extension-heading@^2.0.0-beta.24":
- version "2.0.0-beta.24"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.24.tgz#52ba163c8d16985739387682d5e5b28ddf242661"
- integrity sha512-5a3vgdO7Cf2+z7sulCGs/1j23gBcKiZe3pA1FrC5h6blwLu86hA1xnMAVBVNilP9b6c9f3lN9yxMzEWsp6ZEkA==
+"@tiptap/extension-heading@^2.0.0-beta.26":
+ version "2.0.0-beta.26"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.26.tgz#112b14b4d488772bda36abbf7cb2bc8aba7c42f5"
+ integrity sha512-nR6W/3rjnZH1Swo7tGBoYsmO6xMvu9MGq6jlm3WVHCB7B3CsrRvCkTwGjVIbKTaZC4bQfx5gvAUpQFvwuU+M5w==
"@tiptap/extension-history@^2.0.0-beta.21":
version "2.0.0-beta.21"
@@ -1629,27 +1629,27 @@
"@types/prosemirror-history" "^1.0.3"
prosemirror-history "^1.2.0"
-"@tiptap/extension-horizontal-rule@^2.0.0-beta.30":
- version "2.0.0-beta.30"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.30.tgz#56d497f1187384d131f3f3320f30748c1e4b766f"
- integrity sha512-h/PlkvfcMuoBGRfD7Cbeh8mxZiEc2pKveLDwOfCES9TKV5i2lqcIgctpohWyISuFcTq4K+OFgr910+Rsp8qwEg==
+"@tiptap/extension-horizontal-rule@^2.0.0-beta.31":
+ version "2.0.0-beta.31"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.31.tgz#efb383a6cedbbf4f2175d7d207eaeeba626faab0"
+ integrity sha512-MNc4retfjRgkv3qxqGya0+/BEd1Kmn+oMsCRvE+8x3sXyKIse+vdqMuG5qUcA6np0ZD/9hh1riiQ1GQdgc23Ng==
dependencies:
prosemirror-state "^1.3.4"
-"@tiptap/extension-image@^2.0.0-beta.24":
- version "2.0.0-beta.24"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.24.tgz#1010676f79925cbe11a44b6d8eee1251910fbc1d"
- integrity sha512-7oiX/Ovj9WN4xTBqWbQWd4H3SUO2eNzOiKHebVo3eqWG8NxzOGfuU0iRCENtEa7vTiRrFgyeBotneMALDpDnTQ==
+"@tiptap/extension-image@^2.0.0-beta.25":
+ version "2.0.0-beta.25"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.25.tgz#7fb001a6449a9a841ae4f42c258ad6a06022b523"
+ integrity sha512-RgW5jFVS2QNDvFhBOz7H1hY6LjYcbVAa/mE4F4c3RPg3o7GJZXNoL9s+k0QkEM2GXAvY6fX+OICMBn8TSENXKA==
"@tiptap/extension-italic@^2.0.0-beta.25":
version "2.0.0-beta.25"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.25.tgz#c2ec95cc5baf855134883c5e261da4ab0d3b9479"
integrity sha512-7PvhioTX9baVp5+AmmZU0qna+dFPZCRlSEN/GciH57N77d2uhJ/ZW5iQWTbvy5HBNddQB4Jts1UDIaC7WASrGA==
-"@tiptap/extension-link@^2.0.0-beta.34":
- version "2.0.0-beta.34"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.34.tgz#9f07b9db6a3b62192b0162afdab5480fcd93e731"
- integrity sha512-v9qqPWyKfJ9BbV2eWR4a+za1cepZywV/2Rng+1gTivzLpfBgy6Q50vgUyMe7KTZpNZw5MuZO4sBwlvangmj6Vg==
+"@tiptap/extension-link@^2.0.0-beta.36":
+ version "2.0.0-beta.36"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.36.tgz#184bac20f3226b8945e400ebfdce2feabb4f5a3c"
+ integrity sha512-jV0EBM/QPfR4e5FG5OPHZARnYS+CL8yhCzHO4J1Nb1i/+vRY9QpPVBruZABBwt+J+PMdq6t/6vvIXejCR3wyAg==
dependencies:
linkifyjs "^3.0.5"
prosemirror-model "^1.16.1"
@@ -1700,18 +1700,18 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.19.tgz#b45e82f29dfcc7405440ba237b069dbb93d1a94a"
integrity sha512-ldEVDpIUX7ZqbViTy4c/RfyNGRv++O/r3A/Ivuon1PykaDDTbPlp5JM89FunAD39cLAbo2HKtweqdmzCMlZsqA==
-"@tiptap/extension-table@^2.0.0-beta.46":
- version "2.0.0-beta.46"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.46.tgz#cce4d64fe58479031eb975626cc42e3ca9ae26bd"
- integrity sha512-JP0eYb2gS9RW1xzB1yCAgcSX4eg1MvhnYNLOxL6VCKvoHf33RW7zQEGx11W/Lrc3lh5qaMjkWcCdQYq5d8qJOA==
+"@tiptap/extension-table@^2.0.0-beta.48":
+ version "2.0.0-beta.48"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.48.tgz#4b6adfe358fd38199ef5b29dafc9425fd461d1b3"
+ integrity sha512-Hcx3kOBQyazQ3dV0Oq4zKIl1og4EqUuZ5nEWxwcb8HgxSUYIhAJQ7pujPZiRLfkoFy92oVwmh9KhBRfQqRkUpQ==
dependencies:
prosemirror-tables "^1.1.1"
- prosemirror-view "^1.23.5"
+ prosemirror-view "^1.23.6"
-"@tiptap/extension-task-item@^2.0.0-beta.30":
- version "2.0.0-beta.30"
- resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.30.tgz#54c97d2dd84e0ff40689e02f7361dbd903a40161"
- integrity sha512-56hS7vP/Hgv5R6otEPInTnd8z5n6UCBFm4sbMPA8d7yWWJDskkfuMp9YDEu2zgHZtTghPyGkw3wO6yxouGWlkg==
+"@tiptap/extension-task-item@^2.0.0-beta.31":
+ version "2.0.0-beta.31"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.31.tgz#e73f2f1e264d64ac5902ddd13ad3b63cb043b2c9"
+ integrity sha512-9MCInLAf/l/wDD1N3GgOImemloFARi1l9AJ5acfo+sDjN52yfvaLb//lvLJ6IGz4xGepeAyCME8Qns8UGqG4RQ==
"@tiptap/extension-task-list@^2.0.0-beta.26":
version "2.0.0-beta.26"
@@ -1723,14 +1723,14 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.15.tgz#f08cff1b78f1c6996464dfba1fef8ec1e107617f"
integrity sha512-S3j2+HyV2gsXZP8Wg/HA+YVXQsZ3nrXgBM9HmGAxB0ESOO50l7LWfip0f3qcw1oRlh5H3iLPkA6/f7clD2/TFA==
-"@tiptap/vue-2@^2.0.0-beta.74":
- version "2.0.0-beta.74"
- resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.74.tgz#da9e56113018525d0cb55812a2452d57b9710d26"
- integrity sha512-OY6RhR2DSvP4iIxtoWeg2qmQ1uYP4kJkdMT5ePQBFqVzs6l5+FpSjETskS8EwZVgM/IrLTMp5lHfKboP/YacOA==
+"@tiptap/vue-2@^2.0.0-beta.77":
+ version "2.0.0-beta.77"
+ resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.77.tgz#d5e7b414bba45d0892e39513dc50387d629b95ca"
+ integrity sha512-/wMi7fkqLqfWGhOpcztiGgVyqiCmOPq7TZ2LFx+cokoMJ1HpM8T7vG12u6ezOubB5Ie7/kL6WBcLJ8hONTdX0Q==
dependencies:
- "@tiptap/extension-bubble-menu" "^2.0.0-beta.54"
- "@tiptap/extension-floating-menu" "^2.0.0-beta.49"
- prosemirror-view "^1.23.5"
+ "@tiptap/extension-bubble-menu" "^2.0.0-beta.55"
+ "@tiptap/extension-floating-menu" "^2.0.0-beta.50"
+ prosemirror-view "^1.23.6"
"@toast-ui/editor@^2.5.2":
version "2.5.2"
@@ -1934,10 +1934,10 @@
"@types/prosemirror-state" "*"
"@types/prosemirror-view" "*"
-"@types/prosemirror-model@*", "@types/prosemirror-model@^1.13.2":
- version "1.13.2"
- resolved "https://registry.yarnpkg.com/@types/prosemirror-model/-/prosemirror-model-1.13.2.tgz#2adad3ec478f83204f155d7fb94c9dfde2fc3296"
- integrity sha512-a2rDB0aZ+7aIP7uBqQq1wLb4Hg4qqEvpkCqvhsgT/gG8IWC0peCAZfQ24sgTco0qSJLeDgIbtPeU6mgr869/kg==
+"@types/prosemirror-model@*", "@types/prosemirror-model@^1.16.0":
+ version "1.16.0"
+ resolved "https://registry.yarnpkg.com/@types/prosemirror-model/-/prosemirror-model-1.16.0.tgz#8b22c7431a4c93f7f550fc89c4b0e2d44d42c8b6"
+ integrity sha512-nv93YLyTEcDDl17OB90EldxZjyJQJll2WSMLDvLzTewbpvE/vtMjHT3j4mik3uSzQ6YD486AcloCO3WODY/lDg==
dependencies:
"@types/orderedmap" "*"
@@ -1966,10 +1966,10 @@
dependencies:
"@types/prosemirror-model" "*"
-"@types/prosemirror-view@*", "@types/prosemirror-view@^1.19.2":
- version "1.19.2"
- resolved "https://registry.yarnpkg.com/@types/prosemirror-view/-/prosemirror-view-1.19.2.tgz#1bab4daf0f1f14313fe0d3f6b57f0a3b4ef6c50d"
- integrity sha512-pmh2DuMJzva4D7SxspRKIzkV6FK2o52uAqGjq2dPYcQFPwu4+5RcS1TMjFVCh1R+Ia1Rx8wsCNIId/5+6DB0Bg==
+"@types/prosemirror-view@*", "@types/prosemirror-view@^1.23.1":
+ version "1.23.1"
+ resolved "https://registry.yarnpkg.com/@types/prosemirror-view/-/prosemirror-view-1.23.1.tgz#a9a926bb6b6e6873e3a9d8caa61c32f3402629eb"
+ integrity sha512-6e1B2oKUnhmZPUrsVvYjDqeVjE6jGezygjtoHsAK4ZENAxHzHqy5NT4jUvdPTWjCYeH0t2Y7pSfRPNrPIyQX4A==
dependencies:
"@types/prosemirror-model" "*"
"@types/prosemirror-state" "*"
@@ -9609,10 +9609,10 @@ prosemirror-collab@^1.2.2:
dependencies:
prosemirror-state "^1.0.0"
-prosemirror-commands@^1.1.12, prosemirror-commands@^1.1.4:
- version "1.1.12"
- resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.12.tgz#5cb0fef4e5a0039e2fa19b42a5626af03d7c2ec3"
- integrity sha512-+CrMs3w/ZVPSkR+REg8KL/clyFLv/1+SgY/OMN+CB22Z24j9TZDje72vL36lOZ/E4NeRXuiCcmENcW/vAcG67A==
+prosemirror-commands@^1.1.4, prosemirror-commands@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz#eae0cb714df695260659b78ff5d201d3a037e50d"
+ integrity sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ==
dependencies:
prosemirror-model "^1.0.0"
prosemirror-state "^1.0.0"
@@ -9727,10 +9727,10 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor
dependencies:
prosemirror-model "^1.0.0"
-prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.23.5:
- version "1.23.5"
- resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.23.5.tgz#0f4af48fa9d30aa0b945816e2b8e6cb1c82ac674"
- integrity sha512-GlcCtoFdW17KNOKjOl7OZTU3btENgVfzN/i0RBvy1SEKAZfo88PdvcFrWTraNZHH+y/uhYR3PAdMiDK9DlN5UA==
+prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.23.6:
+ version "1.23.6"
+ resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.23.6.tgz#f514b3166942cb70aac4ac24d0a28c21c3897608"
+ integrity sha512-B4DAzriNpI/AVoW0Lu6SVfX00jZZQxOVwdBQEjWlRbCdT9V0pvk4GQJ3JTFaib+b6BcPdRZ3MjWXz2xvV1rblA==
dependencies:
prosemirror-model "^1.16.0"
prosemirror-state "^1.0.0"