Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0327ce54a7
commit
e9ab418709
|
|
@ -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 });
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 = __(
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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'));
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export const DEFAULT_ERROR = __('Something went wrong on our end.');
|
||||
|
|
@ -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">{{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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+',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue