Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
67daaf4021
commit
0aa20f3dac
|
@ -8,6 +8,7 @@
|
|||
- knapsack/
|
||||
- rspec/
|
||||
- crystalball/
|
||||
when: always
|
||||
|
||||
retrieve-tests-metadata:
|
||||
extends:
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
blob: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: this.blob.rawPath,
|
||||
alt: this.blob.name,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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];
|
||||
};
|
||||
|
|
|
@ -13,14 +13,16 @@ export default {
|
|||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
fileName: {
|
||||
type: String,
|
||||
blob: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
filePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fileName: this.blob.name,
|
||||
filePath: this.blob.rawPath,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
blob: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: this.blob.rawPath,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MemberUserEntity < UserEntity
|
||||
unexpose :show_status
|
||||
unexpose :path
|
||||
unexpose :state
|
||||
unexpose :status_tooltip_html
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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(*)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) | |
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
|
||||
- [A ready to use version](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml)
|
||||
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 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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -31,6 +31,12 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def safe_identify
|
||||
identify
|
||||
rescue UnknownProcessError, AmbiguousProcessError
|
||||
nil
|
||||
end
|
||||
|
||||
def puma?
|
||||
!!defined?(::Puma)
|
||||
end
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
20
package.json
20
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",
|
||||
|
|
|
@ -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,6 +283,18 @@ 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
|
||||
$rspec_run_status=$(retry_failed_rspec_examples)
|
||||
fi
|
||||
else
|
||||
echosuccess "No examples to retry, congrats!"
|
||||
fi
|
||||
|
||||
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}"
|
||||
|
@ -294,18 +304,17 @@ function rspec_paralellized_job() {
|
|||
|
||||
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" "${JUNIT_RETRY_FILE}"
|
||||
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}"
|
||||
fi
|
||||
else
|
||||
echosuccess "No examples to retry, congrats!"
|
||||
fi
|
||||
|
||||
exit $rspec_run_status
|
||||
return $rspec_run_status
|
||||
}
|
||||
|
||||
function rspec_rerun_previous_failed_tests() {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,15 +225,12 @@ 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 }) => {
|
||||
viewer | loadViewerReturnValue
|
||||
${'empty'} | ${EmptyViewer}
|
||||
${'download'} | ${DownloadViewer}
|
||||
${'text'} | ${SourceViewer}
|
||||
`('renders viewer component for $viewer files', async ({ viewer, loadViewerReturnValue }) => {
|
||||
loadViewer.mockReturnValue(loadViewerReturnValue);
|
||||
viewerProps.mockReturnValue(viewerPropsReturnValue);
|
||||
|
||||
createComponent({
|
||||
blob: {
|
||||
|
@ -238,8 +247,7 @@ describe('Blob content viewer component', () => {
|
|||
|
||||
expect(loadViewer).toHaveBeenCalledWith(viewer, false);
|
||||
expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BlobHeader action slot', () => {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,17 +12,18 @@ const router = new VueRouter();
|
|||
|
||||
describe('Source Viewer component', () => {
|
||||
let wrapper;
|
||||
const content = `// Some source code`;
|
||||
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
|
||||
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>`;
|
||||
|
||||
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();
|
||||
};
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -26,22 +26,38 @@ RSpec.describe Gitlab::Runtime do
|
|||
end
|
||||
|
||||
context "when unknown" do
|
||||
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
|
||||
|
||||
context "on multiple matches" do
|
||||
before do
|
||||
stub_const('::Puma', double)
|
||||
stub_const('::Rails::Console', double)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
# Puma has no cli_config method unless `puma/cli` is required
|
||||
context "puma without cli_config" do
|
||||
let(:puma_type) { double('::Puma') }
|
||||
|
|
|
@ -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}"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
152
yarn.lock
152
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"
|
||||
|
|
Loading…
Reference in New Issue