Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-10-04 15:12:14 +00:00
parent 0327ce54a7
commit e9ab418709
40 changed files with 876 additions and 140 deletions

View File

@ -0,0 +1,7 @@
import { buildApiUrl } from '~/api/api_utils';
import axios from '~/lib/utils/axios_utils';
const BULK_IMPORT_ENTITIES_PATH = '/api/:version/bulk_imports/entities';
export const getBulkImportsHistory = (params) =>
axios.get(buildApiUrl(BULK_IMPORT_ENTITIES_PATH), { params });

View File

@ -0,0 +1,90 @@
<script>
import { GlDropdown, GlDropdownItem, GlIcon, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
const DEFAULT_PAGE_SIZES = [20, 50, 100];
export default {
components: {
PaginationLinks,
GlDropdown,
GlDropdownItem,
GlIcon,
GlSprintf,
},
props: {
pageInfo: {
required: true,
type: Object,
},
pageSizes: {
required: false,
type: Array,
default: () => DEFAULT_PAGE_SIZES,
},
itemsCount: {
required: true,
type: Number,
},
},
computed: {
humanizedTotal() {
return this.pageInfo.total >= 1000 ? __('1000+') : this.pageInfo.total;
},
paginationInfo() {
const { page, perPage } = this.pageInfo;
const start = (page - 1) * perPage + 1;
const end = start + this.itemsCount - 1;
return { start, end };
},
},
methods: {
setPage(page) {
this.$emit('set-page', page);
},
},
};
</script>
<template>
<div class="gl-display-flex gl-align-items-center">
<pagination-links :change="setPage" :page-info="pageInfo" class="gl-m-0" />
<gl-dropdown category="tertiary" class="gl-ml-auto">
<template #button-content>
<span class="gl-font-weight-bold">
<gl-sprintf :message="__('%{count} items per page')">
<template #count>
{{ pageInfo.perPage }}
</template>
</gl-sprintf>
</span>
<gl-icon class="gl-button-icon dropdown-chevron" name="chevron-down" />
</template>
<gl-dropdown-item v-for="size in pageSizes" :key="size" @click="$emit('set-page-size', size)">
<gl-sprintf :message="__('%{count} items per page')">
<template #count>
{{ size }}
</template>
</gl-sprintf>
</gl-dropdown-item>
</gl-dropdown>
<div class="gl-ml-2" data-testid="information">
<gl-sprintf :message="s__('BulkImport|Showing %{start}-%{end} of %{total}')">
<template #start>
{{ paginationInfo.start }}
</template>
<template #end>
{{ paginationInfo.end }}
</template>
<template #total>
{{ humanizedTotal }}
</template>
</gl-sprintf>
</div>
</div>
</template>

View File

@ -1,7 +1,7 @@
import { __ } from '~/locale';
export const HELPER_TEXT_SERVICE_PING_DISABLED = __(
'To enable Registration Features, make sure "Enable service ping" is checked.',
'To enable Registration Features, first enable Service Ping.',
);
export const HELPER_TEXT_SERVICE_PING_ENABLED = __(

View File

@ -0,0 +1,176 @@
<script>
import { GlButton, GlEmptyState, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import createFlash from '~/flash';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import { joinPaths } from '~/lib/utils/url_utility';
import { getBulkImportsHistory } from '~/rest_api';
import ImportStatus from '~/import_entities/components/import_status.vue';
import PaginationBar from '~/import_entities/components/pagination_bar.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { DEFAULT_ERROR } from '../utils/error_messages';
const DEFAULT_PER_PAGE = 20;
const DEFAULT_TH_CLASSES =
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-200! gl-border-b-1! gl-p-5!';
const tableCell = (config) => ({
thClass: `${DEFAULT_TH_CLASSES}`,
tdClass: (value, key, item) => {
return {
// eslint-disable-next-line no-underscore-dangle
'gl-border-b-0!': item._showDetails,
};
},
...config,
});
export default {
components: {
GlButton,
GlEmptyState,
GlLink,
GlLoadingIcon,
GlTable,
PaginationBar,
ImportStatus,
TimeAgo,
},
data() {
return {
loading: true,
historyItems: [],
paginationConfig: {
page: 1,
perPage: DEFAULT_PER_PAGE,
},
pageInfo: {},
};
},
fields: [
tableCell({
key: 'source_full_path',
label: s__('BulkImport|Source group'),
thClass: `${DEFAULT_TH_CLASSES} gl-w-30p`,
}),
tableCell({
key: 'destination_name',
label: s__('BulkImport|New group'),
thClass: `${DEFAULT_TH_CLASSES} gl-w-40p`,
}),
tableCell({
key: 'created_at',
label: __('Date'),
}),
tableCell({
key: 'status',
label: __('Status'),
tdAttr: { 'data-qa-selector': 'import_status_indicator' },
}),
],
computed: {
hasHistoryItems() {
return this.historyItems.length > 0;
},
},
watch: {
paginationConfig: {
handler() {
this.loadHistoryItems();
},
deep: true,
immediate: true,
},
},
methods: {
async loadHistoryItems() {
try {
this.loading = true;
const { data: historyItems, headers } = await getBulkImportsHistory({
page: this.paginationConfig.page,
per_page: this.paginationConfig.perPage,
});
this.pageInfo = parseIntPagination(normalizeHeaders(headers));
this.historyItems = historyItems;
} catch (e) {
createFlash({ message: DEFAULT_ERROR, captureError: true, error: e });
} finally {
this.loading = false;
}
},
getDestinationUrl({ destination_name: name, destination_namespace: namespace }) {
return [namespace, name].filter(Boolean).join('/');
},
getFullDestinationUrl(params) {
return joinPaths(gon.relative_url_root || '', this.getDestinationUrl(params));
},
},
gitlabLogo: window.gon.gitlab_logo,
};
</script>
<template>
<div>
<div
class="gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex gl-align-items-center"
>
<h1 class="gl-my-0 gl-py-4 gl-font-size-h1">
<img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" />
{{ s__('BulkImport|Group import history') }}
</h1>
</div>
<gl-loading-icon v-if="loading" size="md" class="gl-mt-5" />
<gl-empty-state
v-else-if="!hasHistoryItems"
:title="s__('BulkImport|No history is available')"
:description="s__('BulkImport|Your imported groups will appear here.')"
/>
<template v-else>
<gl-table
:fields="$options.fields"
:items="historyItems"
data-qa-selector="import_history_table"
class="gl-w-full"
>
<template #cell(destination_name)="{ item }">
<gl-link :href="getFullDestinationUrl(item)" target="_blank">
{{ getDestinationUrl(item) }}
</gl-link>
</template>
<template #cell(created_at)="{ value }">
<time-ago :time="value" />
</template>
<template #cell(status)="{ value, item, toggleDetails, detailsShowing }">
<import-status :status="value" class="gl-display-inline-block gl-w-13" />
<gl-button
v-if="item.failures.length"
class="gl-ml-3"
:selected="detailsShowing"
@click="toggleDetails"
>{{ __('Details') }}</gl-button
>
</template>
<template #row-details="{ item }">
<pre>{{ item.failures }}</pre>
</template>
</gl-table>
<pagination-bar
:page-info="pageInfo"
:items-count="historyItems.length"
class="gl-m-0 gl-mt-3"
@set-page="paginationConfig.page = $event"
@set-page-size="paginationConfig.perPage = $event"
/>
</template>
</div>
</template>

View File

@ -0,0 +1,15 @@
import Vue from 'vue';
import BulkImportHistoryApp from './components/bulk_imports_history_app.vue';
function mountImportHistoryApp(mountElement) {
if (!mountElement) return undefined;
return new Vue({
el: mountElement,
render(createElement) {
return createElement(BulkImportHistoryApp);
},
});
}
mountImportHistoryApp(document.querySelector('#import-history-mount-element'));

View File

@ -0,0 +1,3 @@
import { __ } from '~/locale';
export const DEFAULT_ERROR = __('Something went wrong on our end.');

View File

@ -124,9 +124,6 @@ export default {
const fileName = this.requests[0].truncatedUrl;
return `${fileName}_perf_bar_${Date.now()}.json`;
},
flamegraphPath() {
return mergeUrlParams({ performance_bar: 'flamegraph' }, window.location.href);
},
},
mounted() {
this.currentRequest = this.requestId;
@ -135,6 +132,12 @@ export default {
changeCurrentRequest(newRequestId) {
this.currentRequest = newRequestId;
},
flamegraphPath(mode) {
return mergeUrlParams(
{ performance_bar: 'flamegraph', stackprof_mode: mode },
window.location.href,
);
},
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
};
@ -180,8 +183,17 @@ export default {
}}</a>
</div>
<div v-if="currentRequest.details" id="peek-flamegraph" class="view">
<a class="gl-text-blue-200" :href="flamegraphPath">{{
s__('PerformanceBar|Flamegraph')
<span class="gl-text-white-200">{{ s__('PerformanceBar|Flamegraph with mode:') }}</span>
<a class="gl-text-blue-200" :href="flamegraphPath('wall')">{{
s__('PerformanceBar|wall')
}}</a>
/
<a class="gl-text-blue-200" :href="flamegraphPath('cpu')">{{
s__('PerformanceBar|cpu')
}}</a>
/
<a class="gl-text-blue-200" :href="flamegraphPath('object')">{{
s__('PerformanceBar|object')
}}</a>
</div>
<a v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{

View File

@ -2,6 +2,7 @@ export * from './api/groups_api';
export * from './api/projects_api';
export * from './api/user_api';
export * from './api/markdown_api';
export * from './api/bulk_imports_api';
// Note: It's not possible to spy on methods imported from this file in
// Jest tests.

View File

@ -158,7 +158,7 @@ module IssuableActions
discussions = Discussion.build_collection(notes, issuable)
if issuable.is_a?(MergeRequest) && Feature.enabled?(:merge_request_discussion_cache, issuable.target_project, default_enabled: :yaml)
if issuable.is_a?(MergeRequest)
cache_context = [current_user&.cache_key, project.team.human_max_access(current_user&.id)].join(':')
render_cached(discussions, with: discussion_serializer, cache_context: -> (_) { cache_context }, context: self)

View File

@ -351,7 +351,7 @@ module ProjectsHelper
end
def show_terraform_banner?(project)
project.repository_languages.with_programming_language('HCL').exists? && project.terraform_states.empty?
Feature.enabled?(:show_terraform_banner, type: :ops, default_enabled: true) && project.repository_languages.with_programming_language('HCL').exists? && project.terraform_states.empty?
end
def project_permissions_panel_data(project)

View File

@ -37,8 +37,10 @@ module Ci
increment!(:archival_attempts, touch: :last_archival_attempt_at)
end
def track_archival!(trace_artifact_id)
update!(trace_artifact_id: trace_artifact_id, archived_at: Time.current)
def track_archival!(trace_artifact_id, checksum)
update!(trace_artifact_id: trace_artifact_id,
checksum: checksum,
archived_at: Time.current)
end
def archival_attempts_message

View File

@ -10,21 +10,21 @@
= f.label :version_check_enabled, class: 'form-check-label' do
= _("Enable version check")
.form-text.text-muted
= _("GitLab will inform you if a new version is available.")
= _("%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc.").html_safe % { link_start: "<a href='#{help_page_path("user/admin_area/settings/usage_statistics", anchor: "version-check")}'>".html_safe, link_end: '</a>'.html_safe }
= _("GitLab informs you if a new version is available.")
= _("%{link_start}What information does GitLab Inc. collect?%{link_end}").html_safe % { link_start: "<a href='#{help_page_path("user/admin_area/settings/usage_statistics", anchor: "version-check")}'>".html_safe, link_end: '</a>'.html_safe }
.form-group
- can_be_configured = @application_setting.usage_ping_can_be_configured?
.form-check
= f.check_box :usage_ping_enabled, disabled: !can_be_configured, class: 'form-check-input'
= f.label :usage_ping_enabled, class: 'form-check-label' do
= _('Enable service ping')
= _('Enable Service Ping')
.form-text.text-muted
- if can_be_configured
%p.mb-2= _('To help improve GitLab and its user experience, GitLab will periodically collect usage information.')
%p.mb-2= _('To help improve GitLab and its user experience, GitLab periodically collects usage information.')
- service_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'service-ping')
- service_ping_path = help_page_path('development/service_ping/index.md')
- service_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: service_ping_path }
%p.mb-2= s_('%{service_ping_link_start}Learn more%{service_ping_link_end} about what information is shared with GitLab Inc.').html_safe % { service_ping_link_start: service_ping_link_start, service_ping_link_end: '</a>'.html_safe }
%p.mb-2= s_('%{service_ping_link_start}What information is shared with GitLab Inc.?%{service_ping_link_end}').html_safe % { service_ping_link_start: service_ping_link_start, service_ping_link_end: '</a>'.html_safe }
%button.gl-button.btn.btn-default.js-payload-preview-trigger{ type: 'button', data: { payload_selector: ".#{payload_class}" } }
.gl-spinner.js-spinner.gl-display-none.gl-mr-2
@ -46,7 +46,7 @@
- if usage_ping_enabled
%p.gl-mb-3.text-muted{ id: 'service_ping_features_helper_text' }= _('You can enable Registration Features because Service Ping is enabled. To continue using Registration Features in the future, you will also need to register with GitLab via a new cloud licensing service.')
- else
%p.gl-mb-3.text-muted{ id: 'service_ping_features_helper_text' }= _('To enable Registration Features, make sure "Enable service ping" is checked.')
%p.gl-mb-3.text-muted{ id: 'service_ping_features_helper_text' }= _('To enable Registration Features, first enable Service Ping.')
%p.gl-mb-3.text-muted= _('Registration Features include:')
.form-text
@ -61,7 +61,7 @@
%li
= _('Email from GitLab - email users right from the Admin Area. %{link_start}Learn more%{link_end}.').html_safe % { link_start: email_from_gitlab_link, link_end: link_end }
%li
= _('Limit project size at a global, group and project level. %{link_start}Learn more%{link_end}.').html_safe % { link_start: repo_size_limit_link, link_end: link_end }
= _('Limit project size at a global, group, and project level. %{link_start}Learn more%{link_end}.').html_safe % { link_start: repo_size_limit_link, link_end: link_end }
%li
= _('Restrict group access by IP address. %{link_start}Learn more%{link_end}.').html_safe % { link_start: restrict_ip_link, link_end: link_end }

View File

@ -49,7 +49,7 @@
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Enable or disable version check and service ping.')
= _('Enable or disable version check and Service Ping.')
.settings-content
= render 'usage'

View File

@ -1,16 +1,15 @@
= form_with url: configure_import_bulk_imports_path, class: 'group-form gl-show-field-errors' do |f|
.gl-border-l-solid.gl-border-r-solid.gl-border-gray-100.gl-border-1.gl-p-5
%h4.gl-display-flex
= s_('GroupsNew|Import groups from another instance of GitLab')
%span.badge.badge-info.badge-pill.gl-badge.md.gl-ml-3
= _('Beta')
.gl-display-flex.gl-align-items-center
%h4.gl-display-flex
= s_('GroupsNew|Import groups from another instance of GitLab')
= link_to _('History'), history_import_bulk_imports_path, class: 'gl-link gl-ml-auto'
.gl-alert.gl-alert-warning{ role: 'alert' }
= sprite_icon('warning', css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index.md') }
- feedback_link_start = '<a href="https://gitlab.com/gitlab-org/gitlab/-/issues/284495" target="_blank" rel="noopener noreferrer">'.html_safe
- link_end = '</a>'.html_safe
= s_('GroupsNew|Not all related objects are migrated, as %{docs_link_start}described here%{docs_link_end}. Please %{feedback_link_start}leave feedback%{feedback_link_end} on this feature.').html_safe % { docs_link_start: docs_link_start, docs_link_end: link_end, feedback_link_start: feedback_link_start, feedback_link_end: link_end }
- docs_link_end = '</a>'.html_safe
= s_('GroupsNew|Not all related objects are migrated. %{docs_link_start}More info%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: docs_link_end }
%p.gl-mt-3
= s_('GroupsNew|Provide credentials for another instance of GitLab to import your groups directly.')
.form-group.gl-display-flex.gl-flex-direction-column

View File

@ -0,0 +1,6 @@
- add_to_breadcrumbs _('New group'), new_group_path
- add_to_breadcrumbs _('Import group'), new_group_path(anchor: 'import-group-pane')
- add_page_specific_style 'page_bundles/import'
- page_title _('Import history')
#import-history-mount-element

View File

@ -1,8 +0,0 @@
---
name: merge_request_discussion_cache
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64688
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335799
milestone: '14.1'
type: development
group: group::code review
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: sort_by_project_users_by_project_authorizations_user_id
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64528
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334167
milestone: '14.1'
type: development
group: group::optimize
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: show_terraform_banner
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71462
rollout_issue_url:
milestone: '14.4'
type: ops
group: group::configure
default_enabled: true

View File

@ -70,6 +70,7 @@ namespace :import do
post :configure
get :status
get :realtime_changes
get :history
end
resource :manifest, only: [:create, :new], controller: :manifest do

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -65,7 +65,11 @@ From left to right, the performance bar displays:
can be added by its full URL (authenticated as the current user), or by the value of
its `X-Request-Id` header.
- **Download**: a link to download the raw JSON used to generate the Performance Bar reports.
- **Flamegraph**: a link to generate a [flamegraph](../../../development/profiling.md#speedscope-flamegraphs) of the current URL.
- **Flamegraph** with mode: a link to generate a [flamegraph](../../../development/profiling.md#speedscope-flamegraphs)
of the current URL with the selected [Stackprof mode](https://github.com/tmm1/stackprof#sampling):
- The **Wall** mode samples every *interval* of the time on a clock on a wall. The interval is set to `10100` microseconds.
- The **CPU** mode samples every *interval* of CPU activity. The interval is set to `10100` microseconds.
- The **Object** mode samples every *interval*. The interval is set to `100` allocations.
- **Request Selector**: a select box displayed on the right-hand side of the
Performance Bar which enables you to view these metrics for any requests made while
the current page was open. Only the first two requests per unique URL are captured.

View File

@ -98,11 +98,13 @@ profile and log output to S3.
## Speedscope flamegraphs
You can generate a flamegraph for a particular URL by selecting the flamegraph button in the performance bar or by adding the `performance_bar=flamegraph` parameter to the request.
You can generate a flamegraph for a particular URL by selecting a flamegraph sampling mode button in the performance bar or by adding the `performance_bar=flamegraph` parameter to the request.
![Speedscope](img/speedscope_v13_12.png)
More information about the views can be found in the [Speedscope docs](https://github.com/jlfwong/speedscope#views)
Find more information about the views in the [Speedscope docs](https://github.com/jlfwong/speedscope#views).
Find more information about different sampling modes in the [Stackprof docs](https://github.com/tmm1/stackprof#sampling).
This is enabled for all users that can access the performance bar.

View File

@ -110,7 +110,7 @@ To disable Service Ping in the GitLab UI:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Metrics and profiling**.
1. Expand the **Usage statistics** section.
1. Clear the **Enable service ping** checkbox.
1. Clear the **Enable Service Ping** checkbox.
1. Select **Save changes**.
### Disable Service Ping using the configuration file
@ -554,5 +554,5 @@ To work around this bug, you have two options:
1. In GitLab, on the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Metrics and profiling**.
1. Expand **Usage Statistics**.
1. Clear the **Enable service ping** checkbox.
1. Clear the **Enable Service Ping** checkbox.
1. Select **Save Changes**.

View File

@ -136,7 +136,10 @@ Product Intelligence team as inactive and is assigned to the group owner for rev
We are working on automating this process. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/338466) for details.
Only deprecated metrics can be removed from Service Ping.
Metrics can be removed from Service Ping if they:
- Were previously [deprecated](#deprecate-a-metric).
- Are not used in any Sisense dashboard.
For an example of the metric removal process take a look at this [example issue](https://gitlab.com/gitlab-org/gitlab/-/issues/297029)

View File

@ -76,7 +76,7 @@ To enable or disable Service Ping and version check:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Metrics and profiling**.
1. Expand **Usage statistics**.
1. Select or clear the **Enable version check** and **Enable service ping** checkboxes.
1. Select or clear the **Enable version check** and **Enable Service Ping** checkboxes.
1. Select **Save changes**.
<!-- ## Troubleshooting

View File

@ -658,10 +658,7 @@ module API
users = DeclarativePolicy.subject_scope { user_project.team.users }
users = users.search(params[:search]) if params[:search].present?
users = users.where_not_in(params[:skip_users]) if params[:skip_users].present?
if Feature.enabled?(:sort_by_project_users_by_project_authorizations_user_id, user_project, default_enabled: :yaml)
users = users.order('project_authorizations.user_id' => :asc) # rubocop: disable CodeReuse/ActiveRecord
end
users = users.order('project_authorizations.user_id' => :asc) # rubocop: disable CodeReuse/ActiveRecord
present paginate(users), with: Entities::UserBasic
end

View File

@ -236,35 +236,7 @@ module Gitlab
end
def archive_stream!(stream)
clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path|
create_build_trace!(job, clone_path)
end
end
def clone_file!(src_stream, temp_dir)
FileUtils.mkdir_p(temp_dir)
Dir.mktmpdir("tmp-trace-#{job.id}", temp_dir) do |dir_path|
temp_path = File.join(dir_path, "job.log")
FileUtils.touch(temp_path)
size = IO.copy_stream(src_stream, temp_path)
raise ArchiveError, 'Failed to copy stream' unless size == src_stream.size
yield(temp_path)
end
end
def create_build_trace!(job, path)
File.open(path) do |stream|
# TODO: Set `file_format: :raw` after we've cleaned up legacy traces migration
# https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20307
trace_artifact = job.create_job_artifacts_trace!(
project: job.project,
file_type: :trace,
file: stream,
file_sha256: self.class.sha256_hexdigest(path))
trace_metadata.track_archival!(trace_artifact.id)
end
::Gitlab::Ci::Trace::Archive.new(job, trace_metadata).execute!(stream)
end
def trace_metadata

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Trace
class Archive
include ::Gitlab::Utils::StrongMemoize
include Checksummable
def initialize(job, trace_metadata)
@job = job
@trace_metadata = trace_metadata
end
def execute!(stream)
clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path|
md5_checksum = self.class.md5_hexdigest(clone_path)
sha256_checksum = self.class.sha256_hexdigest(clone_path)
job.transaction do
trace_artifact = create_build_trace!(clone_path, sha256_checksum)
trace_metadata.track_archival!(trace_artifact.id, md5_checksum)
end
end
end
private
attr_reader :job, :trace_metadata
def clone_file!(src_stream, temp_dir)
FileUtils.mkdir_p(temp_dir)
Dir.mktmpdir("tmp-trace-#{job.id}", temp_dir) do |dir_path|
temp_path = File.join(dir_path, "job.log")
FileUtils.touch(temp_path)
size = IO.copy_stream(src_stream, temp_path)
raise ::Gitlab::Ci::Trace::ArchiveError, 'Failed to copy stream' unless size == src_stream.size
yield(temp_path)
end
end
def create_build_trace!(path, file_sha256)
File.open(path) do |stream|
# TODO: Set `file_format: :raw` after we've cleaned up legacy traces migration
# https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20307
job.create_job_artifacts_trace!(
project: job.project,
file_type: :trace,
file: stream,
file_sha256: file_sha256)
end
end
end
end
end
end

View File

@ -19,11 +19,12 @@ module Gitlab
require 'stackprof'
begin
mode = stackprof_mode(request)
flamegraph = ::StackProf.run(
mode: :wall,
mode: mode,
raw: true,
aggregate: false,
interval: ::Gitlab::StackProf::DEFAULT_INTERVAL_US
interval: ::Gitlab::StackProf.interval(mode)
) do
_, _, body = @app.call(env)
end
@ -64,7 +65,7 @@ module Gitlab
var iframe = document.createElement('IFRAME');
iframe.setAttribute('id', 'speedscope-iframe');
document.body.appendChild(iframe);
var iframeUrl = '#{speedscope_path}#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
var iframeUrl = '#{speedscope_path}#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)} in #{stackprof_mode(request)} mode';
iframe.setAttribute('src', iframeUrl);
</script>
</body>
@ -73,6 +74,17 @@ module Gitlab
[200, headers, [html]]
end
def stackprof_mode(request)
case request.params['stackprof_mode']&.to_sym
when :cpu
:cpu
when :object
:object
else
:wall
end
end
end
end
end

View File

@ -75,20 +75,20 @@ module Gitlab
current_timeout_s = nil
else
mode = ENV['STACKPROF_MODE']&.to_sym || DEFAULT_MODE
interval = ENV['STACKPROF_INTERVAL']&.to_i
interval ||= (mode == :object ? DEFAULT_INTERVAL_EVENTS : DEFAULT_INTERVAL_US)
stackprof_interval = ENV['STACKPROF_INTERVAL']&.to_i
stackprof_interval ||= interval(mode)
log_event(
'starting profile',
profile_mode: mode,
profile_interval: interval,
profile_interval: stackprof_interval,
profile_timeout: timeout_s
)
::StackProf.start(
mode: mode,
raw: Gitlab::Utils.to_boolean(ENV['STACKPROF_RAW'] || 'true'),
interval: interval
interval: stackprof_interval
)
current_timeout_s = timeout_s
end
@ -131,5 +131,9 @@ module Gitlab
pid: Process.pid
}.merge(labels.compact))
end
def self.interval(mode)
mode == :object ? DEFAULT_INTERVAL_EVENTS : DEFAULT_INTERVAL_US
end
end
end

View File

@ -715,15 +715,15 @@ msgstr ""
msgid "%{link_start}Learn more%{link_end} about roles."
msgstr ""
msgid "%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc."
msgstr ""
msgid "%{link_start}Remove the %{draft_snippet} prefix%{link_end} from the title to allow this merge request to be merged when it's ready."
msgstr ""
msgid "%{link_start}Start the title with %{draft_snippet}%{link_end} to prevent a merge request that is a work in progress from being merged before it's ready."
msgstr ""
msgid "%{link_start}What information does GitLab Inc. collect?%{link_end}"
msgstr ""
msgid "%{listToShow}, and %{awardsListLength} more"
msgstr ""
@ -876,7 +876,7 @@ msgid_plural "%{securityScanner} results are not available because a pipeline ha
msgstr[0] ""
msgstr[1] ""
msgid "%{service_ping_link_start}Learn more%{service_ping_link_end} about what information is shared with GitLab Inc."
msgid "%{service_ping_link_start}What information is shared with GitLab Inc.?%{service_ping_link_end}"
msgstr ""
msgid "%{size} %{unit}"
@ -5179,9 +5179,6 @@ msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
msgid "Beta"
msgstr ""
msgid "Bi-weekly code coverage"
msgstr ""
@ -5860,6 +5857,9 @@ msgstr ""
msgid "BulkImport|From source group"
msgstr ""
msgid "BulkImport|Group import history"
msgstr ""
msgid "BulkImport|Import failed: Destination cannot be a subgroup of the source group. Change the destination and try again."
msgstr ""
@ -5878,6 +5878,12 @@ msgstr ""
msgid "BulkImport|Name already exists."
msgstr ""
msgid "BulkImport|New group"
msgstr ""
msgid "BulkImport|No history is available"
msgstr ""
msgid "BulkImport|No parent"
msgstr ""
@ -5890,6 +5896,9 @@ msgstr ""
msgid "BulkImport|Showing %{start}-%{end} of %{total} matching filter \"%{filter}\" from %{link}"
msgstr ""
msgid "BulkImport|Source group"
msgstr ""
msgid "BulkImport|To new group"
msgstr ""
@ -5899,6 +5908,9 @@ msgstr ""
msgid "BulkImport|You have no groups to import"
msgstr ""
msgid "BulkImport|Your imported groups will appear here."
msgstr ""
msgid "BulkImport|expected an associated Group but has an associated Project"
msgstr ""
@ -12580,6 +12592,9 @@ msgstr ""
msgid "Enable SSL verification"
msgstr ""
msgid "Enable Service Ping"
msgstr ""
msgid "Enable Snowplow tracking"
msgstr ""
@ -12673,7 +12688,7 @@ msgstr ""
msgid "Enable or disable the Pseudonymizer data collection."
msgstr ""
msgid "Enable or disable version check and service ping."
msgid "Enable or disable version check and Service Ping."
msgstr ""
msgid "Enable protected paths rate limit"
@ -12691,9 +12706,6 @@ msgstr ""
msgid "Enable repository checks"
msgstr ""
msgid "Enable service ping"
msgstr ""
msgid "Enable shared runners for all projects and subgroups in this group."
msgstr ""
@ -15514,6 +15526,9 @@ msgstr ""
msgid "GitLab group: %{source_link}"
msgstr ""
msgid "GitLab informs you if a new version is available."
msgstr ""
msgid "GitLab is a complete DevOps platform, delivered as a single application, fundamentally changing the way Development, Security, and Ops teams collaborate"
msgstr ""
@ -15556,9 +15571,6 @@ msgstr ""
msgid "GitLab version"
msgstr ""
msgid "GitLab will inform you if a new version is available."
msgstr ""
msgid "GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory."
msgstr ""
@ -16603,7 +16615,7 @@ msgstr ""
msgid "GroupsNew|No import options available"
msgstr ""
msgid "GroupsNew|Not all related objects are migrated, as %{docs_link_start}described here%{docs_link_end}. Please %{feedback_link_start}leave feedback%{feedback_link_end} on this feature."
msgid "GroupsNew|Not all related objects are migrated. %{docs_link_start}More info%{docs_link_end}."
msgstr ""
msgid "GroupsNew|Personal access token"
@ -17208,12 +17220,18 @@ msgstr ""
msgid "Import from Jira"
msgstr ""
msgid "Import group"
msgstr ""
msgid "Import group from file"
msgstr ""
msgid "Import groups"
msgstr ""
msgid "Import history"
msgstr ""
msgid "Import in progress"
msgstr ""
@ -20397,7 +20415,7 @@ msgstr ""
msgid "Limit namespaces and projects that can be indexed"
msgstr ""
msgid "Limit project size at a global, group and project level. %{link_start}Learn more%{link_end}."
msgid "Limit project size at a global, group, and project level. %{link_start}Learn more%{link_end}."
msgstr ""
msgid "Limit sign in from multiple IP addresses"
@ -24649,7 +24667,7 @@ msgstr ""
msgid "PerformanceBar|First Contentful Paint"
msgstr ""
msgid "PerformanceBar|Flamegraph"
msgid "PerformanceBar|Flamegraph with mode:"
msgstr ""
msgid "PerformanceBar|Frontend resources"
@ -24685,6 +24703,15 @@ msgstr ""
msgid "PerformanceBar|Trace"
msgstr ""
msgid "PerformanceBar|cpu"
msgstr ""
msgid "PerformanceBar|object"
msgstr ""
msgid "PerformanceBar|wall"
msgstr ""
msgid "Period in seconds"
msgstr ""
@ -35451,7 +35478,7 @@ msgstr ""
msgid "To define internal users, first enable new users set to external"
msgstr ""
msgid "To enable Registration Features, make sure \"Enable service ping\" is checked."
msgid "To enable Registration Features, first enable Service Ping."
msgstr ""
msgid "To ensure no loss of personal content, this account should only be used for matters related to %{group_name}."
@ -35478,7 +35505,7 @@ msgstr ""
msgid "To get started, use the link below to confirm your account."
msgstr ""
msgid "To help improve GitLab and its user experience, GitLab will periodically collect usage information."
msgid "To help improve GitLab and its user experience, GitLab periodically collects usage information."
msgstr ""
msgid "To help improve GitLab, we would like to periodically %{docs_link}. This can be changed at any time in %{settings_link}."

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Import/Export - GitLab migration history', :js do
let_it_be(:user) { create(:user) }
let_it_be(:user_import_1) { create(:bulk_import, user: user) }
let_it_be(:finished_entity_1) { create(:bulk_import_entity, :finished, bulk_import: user_import_1) }
let_it_be(:user_import_2) { create(:bulk_import, user: user) }
let_it_be(:failed_entity_2) { create(:bulk_import_entity, :failed, bulk_import: user_import_2) }
before do
gitlab_sign_in(user)
visit new_group_path
click_link 'Import group'
end
it 'successfully displays import history' do
click_link 'History'
wait_for_requests
expect(page).to have_content 'Group import history'
expect(page.find('tbody')).to have_css('tr', count: 2)
end
end

View File

@ -0,0 +1,92 @@
import { GlPagination, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import PaginationBar from '~/import_entities/components/pagination_bar.vue';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
describe('Pagination bar', () => {
const DEFAULT_PROPS = {
pageInfo: {
total: 50,
page: 1,
perPage: 20,
},
itemsCount: 17,
};
let wrapper;
const createComponent = (propsData) => {
wrapper = mount(PaginationBar, {
propsData: {
...DEFAULT_PROPS,
...propsData,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('events', () => {
beforeEach(() => {
createComponent();
});
it('emits set-page event when page is selected', () => {
const NEXT_PAGE = 3;
// PaginationLinks uses prop instead of event for handling page change
// So we go one level deep to test this
wrapper
.findComponent(PaginationLinks)
.findComponent(GlPagination)
.vm.$emit('input', NEXT_PAGE);
expect(wrapper.emitted('set-page')).toEqual([[NEXT_PAGE]]);
});
it('emits set-page-size event when page size is selected', () => {
const firstItemInPageSizeDropdown = wrapper.findComponent(GlDropdownItem);
firstItemInPageSizeDropdown.vm.$emit('click');
const [emittedPageSizeChange] = wrapper.emitted('set-page-size')[0];
expect(firstItemInPageSizeDropdown.text()).toMatchInterpolatedText(
`${emittedPageSizeChange} items per page`,
);
});
});
it('renders current page size', () => {
const CURRENT_PAGE_SIZE = 40;
createComponent({
pageInfo: {
...DEFAULT_PROPS.pageInfo,
perPage: CURRENT_PAGE_SIZE,
},
});
expect(wrapper.find(GlDropdown).find('button').text()).toMatchInterpolatedText(
`${CURRENT_PAGE_SIZE} items per page`,
);
});
it('renders current page information', () => {
createComponent();
expect(wrapper.find('[data-testid="information"]').text()).toMatchInterpolatedText(
'Showing 1 - 17 of 50',
);
});
it('renders current page information when total count is over 1000', () => {
createComponent({
pageInfo: {
...DEFAULT_PROPS.pageInfo,
total: 1200,
},
});
expect(wrapper.find('[data-testid="information"]').text()).toMatchInterpolatedText(
'Showing 1 - 17 of 1000+',
);
});
});

View File

@ -0,0 +1,175 @@
import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import PaginationBar from '~/import_entities/components/pagination_bar.vue';
import BulkImportsHistoryApp from '~/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('BulkImportsHistoryApp', () => {
const API_URL = '/api/v4/bulk_imports/entities';
const DEFAULT_HEADERS = {
'x-page': 1,
'x-per-page': 20,
'x-next-page': 2,
'x-total': 22,
'x-total-pages': 2,
'x-prev-page': null,
};
const DUMMY_RESPONSE = [
{
id: 1,
bulk_import_id: 1,
status: 'finished',
source_full_path: 'top-level-group-12',
destination_name: 'top-level-group-12',
destination_namespace: 'h5bp',
created_at: '2021-07-08T10:03:44.743Z',
failures: [],
},
{
id: 2,
bulk_import_id: 2,
status: 'failed',
source_full_path: 'autodevops-demo',
destination_name: 'autodevops-demo',
destination_namespace: 'flightjs',
parent_id: null,
namespace_id: null,
project_id: null,
created_at: '2021-07-13T12:52:26.664Z',
updated_at: '2021-07-13T13:34:49.403Z',
failures: [
{
pipeline_class: 'BulkImports::Groups::Pipelines::GroupPipeline',
pipeline_step: 'loader',
exception_class: 'ActiveRecord::RecordNotUnique',
correlation_id_value: '01FAFYSYZ7XPF3P9NSMTS693SZ',
created_at: '2021-07-13T13:34:49.344Z',
},
],
},
];
let wrapper;
let mock;
function createComponent({ shallow = true } = {}) {
const mountFn = shallow ? shallowMount : mount;
wrapper = mountFn(BulkImportsHistoryApp);
}
const originalApiVersion = gon.api_version;
beforeAll(() => {
gon.api_version = 'v4';
});
afterAll(() => {
gon.api_version = originalApiVersion;
});
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
wrapper.destroy();
});
describe('general behavior', () => {
it('renders loading state when loading', () => {
createComponent();
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('renders empty state when no data is available', async () => {
mock.onGet(API_URL).reply(200, [], DEFAULT_HEADERS);
createComponent();
await axios.waitForAll();
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(GlEmptyState).exists()).toBe(true);
});
it('renders table with data when history is available', async () => {
mock.onGet(API_URL).reply(200, DUMMY_RESPONSE, DEFAULT_HEADERS);
createComponent();
await axios.waitForAll();
const table = wrapper.find(GlTable);
expect(table.exists()).toBe(true);
// can't use .props() or .attributes() here
expect(table.vm.$attrs.items).toHaveLength(DUMMY_RESPONSE.length);
});
it('changes page when requested by pagination bar', async () => {
const NEW_PAGE = 4;
mock.onGet(API_URL).reply(200, DUMMY_RESPONSE, DEFAULT_HEADERS);
createComponent();
await axios.waitForAll();
mock.resetHistory();
wrapper.findComponent(PaginationBar).vm.$emit('set-page', NEW_PAGE);
await axios.waitForAll();
expect(mock.history.get.length).toBe(1);
expect(mock.history.get[0].params).toStrictEqual(expect.objectContaining({ page: NEW_PAGE }));
});
});
it('changes page size when requested by pagination bar', async () => {
const NEW_PAGE_SIZE = 4;
mock.onGet(API_URL).reply(200, DUMMY_RESPONSE, DEFAULT_HEADERS);
createComponent();
await axios.waitForAll();
mock.resetHistory();
wrapper.findComponent(PaginationBar).vm.$emit('set-page-size', NEW_PAGE_SIZE);
await axios.waitForAll();
expect(mock.history.get.length).toBe(1);
expect(mock.history.get[0].params).toStrictEqual(
expect.objectContaining({ per_page: NEW_PAGE_SIZE }),
);
});
describe('details button', () => {
beforeEach(() => {
mock.onGet(API_URL).reply(200, DUMMY_RESPONSE, DEFAULT_HEADERS);
createComponent({ shallow: false });
return axios.waitForAll();
});
it('renders details button if relevant item has failures', async () => {
expect(
extendedWrapper(wrapper.find('tbody').findAll('tr').at(1)).findByText('Details').exists(),
).toBe(true);
});
it('does not render details button if relevant item has no failures', () => {
expect(
extendedWrapper(wrapper.find('tbody').findAll('tr').at(0)).findByText('Details').exists(),
).toBe(false);
});
it('expands details when details button is clicked', async () => {
const ORIGINAL_ROW_INDEX = 1;
await extendedWrapper(wrapper.find('tbody').findAll('tr').at(ORIGINAL_ROW_INDEX))
.findByText('Details')
.trigger('click');
const detailsRowContent = wrapper
.find('tbody')
.findAll('tr')
.at(ORIGINAL_ROW_INDEX + 1)
.find('pre');
expect(detailsRowContent.exists()).toBe(true);
expect(JSON.parse(detailsRowContent.text())).toStrictEqual(DUMMY_RESPONSE[1].failures);
});
});
});

View File

@ -904,6 +904,14 @@ RSpec.describe ProjectsHelper do
it { is_expected.to be_falsey }
end
context 'the :show_terraform_banner feature flag is disabled' do
before do
stub_feature_flags(show_terraform_banner: false)
end
it { is_expected.to be_falsey }
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Trace::Archive do
let_it_be(:job) { create(:ci_build, :success, :trace_live) }
let_it_be(:trace_metadata) { create(:ci_build_trace_metadata, build: job) }
let_it_be(:src_checksum) do
job.trace.read { |stream| Digest::MD5.hexdigest(stream.raw) }
end
describe '#execute' do
subject { described_class.new(job, trace_metadata) }
it 'computes and assigns checksum' do
Gitlab::Ci::Trace::ChunkedIO.new(job) do |stream|
expect { subject.execute!(stream) }.to change { Ci::JobArtifact.count }.by(1)
end
expect(trace_metadata.checksum).to eq(src_checksum)
expect(trace_metadata.trace_artifact).to eq(job.job_artifacts_trace)
end
end
end

View File

@ -46,7 +46,7 @@ RSpec.describe Gitlab::Middleware::Speedscope do
allow(env).to receive(:[]).with('warden').and_return(double('Warden', user: create(:admin)))
end
it 'runs StackProf and returns a flamegraph' do
it 'returns a flamegraph' do
expect(StackProf).to receive(:run).and_call_original
status, headers, body = middleware.call(env)
@ -55,6 +55,56 @@ RSpec.describe Gitlab::Middleware::Speedscope do
expect(headers).to eq({ 'Content-Type' => 'text/html' })
expect(body.first).to include('speedscope-iframe')
end
context 'when the stackprof_mode parameter is set and valid' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'cpu' }) }
it 'runs StackProf in the mode specified in the stackprof_mode parameter' do
expect(StackProf).to receive(:run).with(hash_including(mode: :cpu))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is not set' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph' }) }
it 'runs StackProf in wall mode' do
expect(StackProf).to receive(:run).with(hash_including(mode: :wall))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is invalid' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'invalid' }) }
it 'runs StackProf in wall mode' do
expect(StackProf).to receive(:run).with(hash_including(mode: :wall))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is set to object mode' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'object' }) }
it 'runs StackProf with an interval of 100' do
expect(StackProf).to receive(:run).with(hash_including(interval: 100))
middleware.call(env)
end
end
context 'when the stackprof_mode parameter is not set to object mode' do
let(:env) { Rack::MockRequest.env_for('/', params: { 'performance_bar' => 'flamegraph', 'stackprof_mode' => 'wall' }) }
it 'runs StackProf with an interval of 10_100' do
expect(StackProf).to receive(:run).with(hash_including(interval: 10_100))
middleware.call(env)
end
end
end
end
end

View File

@ -88,14 +88,16 @@ RSpec.describe Ci::BuildTraceMetadata do
describe '#track_archival!' do
let(:trace_artifact) { create(:ci_job_artifact) }
let(:metadata) { create(:ci_build_trace_metadata) }
let(:checksum) { SecureRandom.hex }
it 'stores the artifact id and timestamp' do
expect(metadata.trace_artifact_id).to be_nil
metadata.track_archival!(trace_artifact.id)
metadata.track_archival!(trace_artifact.id, checksum)
metadata.reload
expect(metadata.trace_artifact_id).to eq(trace_artifact.id)
expect(metadata.checksum).to eq(checksum)
expect(metadata.archived_at).to be_like_time(Time.current)
end
end

View File

@ -2684,26 +2684,9 @@ RSpec.describe API::Projects do
context 'when authenticated' do
context 'valid request' do
context 'when sort_by_project_authorizations_user_id FF is off' do
before do
stub_feature_flags(sort_by_project_users_by_project_authorizations_user_id: false)
end
it_behaves_like 'project users response' do
let(:project) { project4 }
let(:current_user) { user4 }
end
end
context 'when sort_by_project_authorizations_user_id FF is on' do
before do
stub_feature_flags(sort_by_project_users_by_project_authorizations_user_id: true)
end
it_behaves_like 'project users response' do
let(:project) { project4 }
let(:current_user) { user4 }
end
it_behaves_like 'project users response' do
let(:project) { project4 }
let(:current_user) { user4 }
end
end

View File

@ -251,16 +251,6 @@ RSpec.describe 'merge requests discussions' do
let(:changed_notes) { [first_note, second_note] }
end
end
context 'when merge_request_discussion_cache is disabled' do
before do
stub_feature_flags(merge_request_discussion_cache: false)
end
it_behaves_like 'cache miss' do
let(:changed_notes) { [first_note, second_note] }
end
end
end
end
end