Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-03 18:17:34 +00:00
parent 67daaf4021
commit 0aa20f3dac
56 changed files with 1029 additions and 633 deletions

View File

@ -8,6 +8,7 @@
- knapsack/
- rspec/
- crystalball/
when: always
retrieve-tests-metadata:
extends:

View File

@ -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.

View File

@ -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)"

View File

@ -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>

View File

@ -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() {

View File

@ -1,15 +1,17 @@
<script>
export default {
props: {
url: {
type: String,
required: true,
},
alt: {
type: String,
blob: {
type: Object,
required: true,
},
},
data() {
return {
url: this.blob.rawPath,
alt: this.blob.name,
};
},
};
</script>
<template>

View File

@ -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];
};

View File

@ -13,15 +13,17 @@ export default {
GlSprintf,
},
props: {
fileName: {
type: String,
required: true,
},
filePath: {
type: String,
blob: {
type: Object,
required: true,
},
},
data() {
return {
fileName: this.blob.name,
filePath: this.blob.rawPath,
};
},
};
</script>

View File

@ -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() {

View File

@ -1,11 +1,16 @@
<script>
export default {
props: {
url: {
type: String,
blob: {
type: Object,
required: true,
},
},
data() {
return {
url: this.blob.rawPath,
};
},
};
</script>
<template>

View File

@ -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';

View File

@ -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,
};
},

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
class MemberUserEntity < UserEntity
unexpose :show_status
unexpose :path
unexpose :state
unexpose :status_tooltip_html

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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(*)

View File

@ -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

View File

@ -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) | |

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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:

View File

@ -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

41
lib/gitlab/console.rb Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -31,6 +31,12 @@ module Gitlab
end
end
def safe_identify
identify
rescue UnknownProcessError, AmbiguousProcessError
nil
end
def puma?
!!defined?(::Puma)
end

View File

@ -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 ""

View File

@ -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",

View File

@ -209,6 +209,8 @@ function debug_rspec_variables() {
echoinfo "NEW_FLAKY_RSPEC_REPORT_PATH: ${NEW_FLAKY_RSPEC_REPORT_PATH}"
echoinfo "SKIPPED_FLAKY_TESTS_REPORT_PATH: ${SKIPPED_FLAKY_TESTS_REPORT_PATH}"
echoinfo "RETRIED_TESTS_REPORT_PATH: ${RETRIED_TESTS_REPORT_PATH}"
echoinfo "CRYSTALBALL: ${CRYSTALBALL}"
}
function rspec_paralellized_job() {
@ -245,11 +247,7 @@ function rspec_paralellized_job() {
cp "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "${KNAPSACK_REPORT_PATH}"
if [[ -z "${KNAPSACK_TEST_FILE_PATTERN}" ]]; then
pattern=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(${spec_folder_prefixes}).pattern(:${test_level})")
export KNAPSACK_TEST_FILE_PATTERN="${pattern}"
fi
export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./tooling/quality/test_level.rb -e "puts Quality::TestLevel.new(${spec_folder_prefixes}).pattern(:${test_level})")
export FLAKY_RSPEC_REPORT_PATH="${rspec_flaky_folder_path}all_${report_name}_report.json"
export NEW_FLAKY_RSPEC_REPORT_PATH="${rspec_flaky_folder_path}new_${report_name}_report.json"
export SKIPPED_FLAKY_TESTS_REPORT_PATH="${rspec_flaky_folder_path}skipped_flaky_tests_${report_name}_report.txt"
@ -285,21 +283,7 @@ function rspec_paralellized_job() {
# Experiment to retry failed examples in a new RSpec process: https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1148
if [[ $rspec_run_status -ne 0 ]]; then
if [[ "${RETRY_FAILED_TESTS_IN_NEW_PROCESS}" == "true" ]]; then
# Keep track of the tests that are retried, later consolidated in a single file by the `rspec:flaky-tests-report` job
local failed_examples=$(grep " failed" ${RSPEC_LAST_RUN_RESULTS_FILE})
echo "${CI_JOB_URL}" > "${RETRIED_TESTS_REPORT_PATH}"
echo $failed_examples >> "${RETRIED_TESTS_REPORT_PATH}"
echoinfo "Retrying the failing examples in a new RSpec proces..."
install_junit_merge_gem
# Retry only the tests that failed on first try
rspec_simple_job "--only-failures" "${JUNIT_RETRY_FILE}"
rspec_run_status=$?
# Merge the JUnit report from retry into the first-try report
junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}"
$rspec_run_status=$(retry_failed_rspec_examples)
fi
else
echosuccess "No examples to retry, congrats!"
@ -308,6 +292,31 @@ function rspec_paralellized_job() {
exit $rspec_run_status
}
function retry_failed_rspec_examples() {
local rspec_run_status=0
# Keep track of the tests that are retried, later consolidated in a single file by the `rspec:flaky-tests-report` job
local failed_examples=$(grep " failed" ${RSPEC_LAST_RUN_RESULTS_FILE})
echo "${CI_JOB_URL}" > "${RETRIED_TESTS_REPORT_PATH}"
echo $failed_examples >> "${RETRIED_TESTS_REPORT_PATH}"
echoinfo "Retrying the failing examples in a new RSpec proces..."
install_junit_merge_gem
# Disable Crystalball on retry to not overwrite the existing report
export CRYSTALBALL="false"
# Retry only the tests that failed on first try
rspec_simple_job "--only-failures --pattern \"${KNAPSACK_TEST_FILE_PATTERN}\"" "${JUNIT_RETRY_FILE}"
rspec_run_status=$?
# Merge the JUnit report from retry into the first-try report
junit_merge "${JUNIT_RETRY_FILE}" "${JUNIT_RESULT_FILE}"
return $rspec_run_status
}
function rspec_rerun_previous_failed_tests() {
local test_file_count_threshold=${RSPEC_PREVIOUS_FAILED_TEST_FILE_COUNT_THRESHOLD:-10}
local matching_tests_file=${1}

View File

@ -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'

View File

@ -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" }
}
}

View File

@ -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;

View File

@ -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);
});
});
});
});

View File

@ -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',

View File

@ -3,7 +3,6 @@ import { mount, shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@ -13,7 +12,7 @@ import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import BlobEdit from '~/repository/components/blob_edit.vue';
import ForkSuggestion from '~/repository/components/fork_suggestion.vue';
import { loadViewer, viewerProps } from '~/repository/components/blob_viewers';
import { loadViewer } from '~/repository/components/blob_viewers';
import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
import SourceViewer from '~/vue_shared/components/source_viewer.vue';
@ -51,6 +50,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
createMergeRequestIn = userPermissionsMock.createMergeRequestIn,
isBinary,
inject = {},
highlightJs = true,
} = mockData;
const project = {
@ -78,7 +78,12 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
apolloProvider: fakeApollo,
propsData: propsMock,
mixins: [{ data: () => ({ ref: refMock }) }],
provide: { ...inject },
provide: {
...inject,
glFeatures: {
highlightJs,
},
},
}),
);
@ -99,7 +104,6 @@ describe('Blob content viewer component', () => {
const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion);
beforeEach(() => {
gon.features = { highlightJs: true };
isLoggedIn.mockReturnValue(true);
});
@ -137,6 +141,15 @@ describe('Blob content viewer component', () => {
});
describe('legacy viewers', () => {
it('loads a legacy viewer when a the fileType is text and the highlightJs feature is turned off', async () => {
await createComponent({
blob: { ...simpleViewerMock, fileType: 'text', highlightJs: false },
});
expect(mockAxios.history.get).toHaveLength(1);
expect(mockAxios.history.get[0].url).toEqual('some_file.js?format=json&viewer=simple');
});
it('loads a legacy viewer when a viewer component is not available', async () => {
await createComponent({ blob: { ...simpleViewerMock, fileType: 'unknown' } });
@ -202,7 +215,6 @@ describe('Blob content viewer component', () => {
describe('Blob viewer', () => {
afterEach(() => {
loadViewer.mockRestore();
viewerProps.mockRestore();
});
it('does not render a BlobContent component if a Blob viewer is available', async () => {
@ -213,33 +225,29 @@ describe('Blob content viewer component', () => {
});
it.each`
viewer | loadViewerReturnValue | viewerPropsReturnValue
${'empty'} | ${EmptyViewer} | ${{}}
${'download'} | ${DownloadViewer} | ${{ filePath: '/some/file/path', fileName: 'test.js', fileSize: 100 }}
${'text'} | ${SourceViewer} | ${{ content: 'test', autoDetect: true }}
`(
'renders viewer component for $viewer files',
async ({ viewer, loadViewerReturnValue, viewerPropsReturnValue }) => {
loadViewer.mockReturnValue(loadViewerReturnValue);
viewerProps.mockReturnValue(viewerPropsReturnValue);
viewer | loadViewerReturnValue
${'empty'} | ${EmptyViewer}
${'download'} | ${DownloadViewer}
${'text'} | ${SourceViewer}
`('renders viewer component for $viewer files', async ({ viewer, loadViewerReturnValue }) => {
loadViewer.mockReturnValue(loadViewerReturnValue);
createComponent({
blob: {
...simpleViewerMock,
fileType: 'null',
simpleViewer: {
...simpleViewerMock.simpleViewer,
fileType: viewer,
},
createComponent({
blob: {
...simpleViewerMock,
fileType: 'null',
simpleViewer: {
...simpleViewerMock.simpleViewer,
fileType: viewer,
},
});
},
});
await waitForPromises();
await waitForPromises();
expect(loadViewer).toHaveBeenCalledWith(viewer, false);
expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
},
);
expect(loadViewer).toHaveBeenCalledWith(viewer, false);
expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
});
});
describe('BlobHeader action slot', () => {

View File

@ -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,
});
});

View File

@ -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);
});
});

View File

@ -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,
});
});
});

View File

@ -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', () => {

View File

@ -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();
});
});

View File

@ -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();
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -26,8 +26,16 @@ RSpec.describe Gitlab::Runtime do
end
context "when unknown" do
it "raises an exception when trying to identify" do
expect { subject.identify }.to raise_error(subject::UnknownProcessError)
describe '.identify' do
it "raises an exception when trying to identify" do
expect { subject.identify }.to raise_error(subject::UnknownProcessError)
end
end
describe '.safe_identify' do
it "returns nil" do
expect(subject.safe_identify).to be_nil
end
end
end
@ -37,8 +45,16 @@ RSpec.describe Gitlab::Runtime do
stub_const('::Rails::Console', double)
end
it "raises an exception when trying to identify" do
expect { subject.identify }.to raise_error(subject::AmbiguousProcessError)
describe '.identify' do
it "raises an exception when trying to identify" do
expect { subject.identify }.to raise_error(subject::AmbiguousProcessError)
end
end
describe '.safe_identify' do
it "returns nil" do
expect(subject.safe_identify).to be_nil
end
end
end

View File

@ -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}"

View File

@ -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

View File

@ -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
View File

@ -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"