Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
ecc11e5d60
commit
ca5de52835
|
@ -342,8 +342,8 @@ rspec fast_spec_helper minimal:
|
|||
db:rollback:
|
||||
extends: .db-job-base
|
||||
script:
|
||||
- bundle exec rake db:migrate VERSION=20181228175414
|
||||
- bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true
|
||||
- bundle exec rake db:migrate:main VERSION=20181228175414
|
||||
- bundle exec rake db:migrate:main SKIP_SCHEMA_VERSION_CHECK=true
|
||||
|
||||
db:migrate:reset:
|
||||
extends: .db-job-base
|
||||
|
@ -368,7 +368,7 @@ db:migrate-from-previous-major-version:
|
|||
- git checkout -f $CI_COMMIT_SHA
|
||||
- SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh
|
||||
script:
|
||||
- run_timed_command "bundle exec rake db:migrate"
|
||||
- run_timed_command "bundle exec rake db:migrate:main"
|
||||
|
||||
db:check-schema:
|
||||
extends:
|
||||
|
@ -377,7 +377,7 @@ db:check-schema:
|
|||
variables:
|
||||
TAG_TO_CHECKOUT: "v14.4.0"
|
||||
script:
|
||||
- run_timed_command "bundle exec rake db:migrate"
|
||||
- run_timed_command "bundle exec rake db:migrate:main"
|
||||
- scripts/schema_changed.sh
|
||||
- scripts/validate_migration_timestamps
|
||||
|
||||
|
@ -900,8 +900,8 @@ db:rollback geo:
|
|||
- db:rollback
|
||||
- .rails:rules:ee-only-migration
|
||||
script:
|
||||
- bundle exec rake geo:db:migrate VERSION=20170627195211
|
||||
- bundle exec rake geo:db:migrate
|
||||
- bundle exec rake db:migrate:geo VERSION=20170627195211
|
||||
- bundle exec rake db:migrate:geo
|
||||
# EE: default refs (MRs, default branch, schedules) jobs #
|
||||
##################################################
|
||||
|
||||
|
|
|
@ -543,7 +543,7 @@ Rails/LexicallyScopedActionFilter:
|
|||
Rails/LinkToBlank:
|
||||
Exclude:
|
||||
- 'app/helpers/projects_helper.rb'
|
||||
- 'ee/app/helpers/ee/user_callouts_helper.rb'
|
||||
- 'ee/app/helpers/ee/users/callouts_helper.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
|
|
|
@ -16,7 +16,7 @@ Cop/UserAdmin:
|
|||
- app/helpers/nav_helper.rb
|
||||
- app/helpers/projects_helper.rb
|
||||
- app/helpers/search_helper.rb
|
||||
- app/helpers/user_callouts_helper.rb
|
||||
- app/helpers/users/callouts_helper.rb
|
||||
- app/helpers/users_helper.rb
|
||||
- app/helpers/visibility_level_helper.rb
|
||||
- app/models/concerns/protected_ref_access.rb
|
||||
|
@ -38,7 +38,7 @@ Cop/UserAdmin:
|
|||
- ee/app/helpers/ee/dashboard_helper.rb
|
||||
- ee/app/helpers/ee/import_helper.rb
|
||||
- ee/app/helpers/ee/subscribable_banner_helper.rb
|
||||
- ee/app/helpers/ee/user_callouts_helper.rb
|
||||
- ee/app/helpers/ee/users/callouts_helper.rb
|
||||
- ee/app/helpers/license_monitoring_helper.rb
|
||||
- ee/app/helpers/push_rules_helper.rb
|
||||
- ee/app/models/concerns/ee/protected_ref_access.rb
|
||||
|
|
|
@ -32,7 +32,6 @@ Gitlab/NamespacedClass:
|
|||
- app/controllers/sessions_controller.rb
|
||||
- app/controllers/snippets_controller.rb
|
||||
- app/controllers/uploads_controller.rb
|
||||
- app/controllers/user_callouts_controller.rb
|
||||
- app/controllers/users_controller.rb
|
||||
- app/controllers/whats_new_controller.rb
|
||||
- app/finders/abuse_reports_finder.rb
|
||||
|
@ -351,7 +350,6 @@ Gitlab/NamespacedClass:
|
|||
- app/models/upload.rb
|
||||
- app/models/user.rb
|
||||
- app/models/user_agent_detail.rb
|
||||
- app/models/user_callout.rb
|
||||
- app/models/user_canonical_email.rb
|
||||
- app/models/user_custom_attribute.rb
|
||||
- app/models/user_detail.rb
|
||||
|
|
|
@ -1 +1 @@
|
|||
1de88e4247d4b940f843003781cb2bf75582b826
|
||||
f9af7fbcbfda556c61dcbb2280cda6c6e210cb77
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -476,7 +476,7 @@ gem 'sshkey', '~> 2.0'
|
|||
# Required for ED25519 SSH host key support
|
||||
group :ed25519 do
|
||||
gem 'ed25519', '~> 1.2'
|
||||
gem 'bcrypt_pbkdf', '~> 1.0'
|
||||
gem 'bcrypt_pbkdf', '~> 1.1'
|
||||
end
|
||||
|
||||
# Spamcheck GRPC protocol definitions
|
||||
|
|
|
@ -137,7 +137,7 @@ GEM
|
|||
base32 (0.3.2)
|
||||
batch-loader (2.0.1)
|
||||
bcrypt (3.1.16)
|
||||
bcrypt_pbkdf (1.0.0)
|
||||
bcrypt_pbkdf (1.1.0)
|
||||
benchmark (0.1.1)
|
||||
benchmark-ips (2.3.0)
|
||||
benchmark-memory (0.1.2)
|
||||
|
@ -1410,7 +1410,7 @@ DEPENDENCIES
|
|||
base32 (~> 0.3.0)
|
||||
batch-loader (~> 2.0.1)
|
||||
bcrypt (~> 3.1, >= 3.1.14)
|
||||
bcrypt_pbkdf (~> 1.0)
|
||||
bcrypt_pbkdf (~> 1.1)
|
||||
benchmark-ips (~> 2.3.0)
|
||||
benchmark-memory (~> 0.1)
|
||||
better_errors (~> 2.9.0)
|
||||
|
|
|
@ -18,5 +18,6 @@ export default {
|
|||
<span v-for="(token, tokenIndex) in tokens" :key="tokenIndex" :class="token.class">{{
|
||||
token.value
|
||||
}}</span>
|
||||
<br />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -170,7 +170,7 @@ export default {
|
|||
},
|
||||
|
||||
availableGroupsForImport() {
|
||||
return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && g.flags.isInvalid);
|
||||
return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && !g.flags.isInvalid);
|
||||
},
|
||||
|
||||
humanizedTotal() {
|
||||
|
@ -521,13 +521,15 @@ export default {
|
|||
/>
|
||||
<template v-else>
|
||||
<div
|
||||
class="gl-bg-gray-10 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-p-4 gl-display-flex gl-align-items-center"
|
||||
class="gl-bg-gray-10 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-px-4 gl-display-flex gl-align-items-center import-table-bar"
|
||||
>
|
||||
<span data-test-id="selection-count">
|
||||
<gl-sprintf :message="__('%{count} selected')">
|
||||
<template #count>
|
||||
{{ selectedGroupsIds.length }}
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
<gl-button
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
|
@ -539,7 +541,7 @@ export default {
|
|||
</div>
|
||||
<gl-table
|
||||
ref="table"
|
||||
class="gl-w-full"
|
||||
class="gl-w-full import-table"
|
||||
data-qa-selector="import_table"
|
||||
:tbody-tr-class="rowClasses"
|
||||
:tbody-tr-attr="qaRowAttributes"
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
import BridgeEmptyState from './components/empty_state.vue';
|
||||
import BridgeSidebar from './components/sidebar.vue';
|
||||
|
||||
export default {
|
||||
name: 'BridgePageApp',
|
||||
components: {
|
||||
BridgeEmptyState,
|
||||
BridgeSidebar,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<!-- TODO: get job details and show CI header -->
|
||||
<!-- TODO: add downstream pipeline path -->
|
||||
<bridge-empty-state downstream-pipeline-path="#" />
|
||||
<bridge-sidebar />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1 @@
|
|||
export const SIDEBAR_COLLAPSE_BREAKPOINTS = ['xs', 'sm'];
|
|
@ -0,0 +1,45 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'BridgeEmptyState',
|
||||
i18n: {
|
||||
title: __('This job triggers a downstream pipeline'),
|
||||
linkBtnText: __('View downstream pipeline'),
|
||||
},
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
inject: {
|
||||
emptyStateIllustrationPath: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
downstreamPipelinePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
|
||||
<img :src="emptyStateIllustrationPath" />
|
||||
<h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
|
||||
<gl-button
|
||||
v-if="downstreamPipelinePath"
|
||||
class="gl-mt-3"
|
||||
category="secondary"
|
||||
variant="confirm"
|
||||
size="medium"
|
||||
:href="downstreamPipelinePath"
|
||||
>
|
||||
{{ $options.i18n.linkBtnText }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,98 @@
|
|||
<script>
|
||||
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
|
||||
import { __ } from '~/locale';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import { JOB_SIDEBAR } from '../../constants';
|
||||
import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './constants';
|
||||
|
||||
export default {
|
||||
styles: {
|
||||
top: '75px',
|
||||
width: '290px',
|
||||
},
|
||||
name: 'BridgeSidebar',
|
||||
i18n: {
|
||||
...JOB_SIDEBAR,
|
||||
retryButton: __('Retry'),
|
||||
retryTriggerJob: __('Retry the trigger job'),
|
||||
retryDownstreamPipeline: __('Retry the downstream pipeline'),
|
||||
},
|
||||
borderTopClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100'],
|
||||
components: {
|
||||
GlButton,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
TooltipOnTruncate,
|
||||
},
|
||||
inject: {
|
||||
buildName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSidebarExpanded: true,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
},
|
||||
mounted() {
|
||||
this.onResize();
|
||||
},
|
||||
methods: {
|
||||
toggleSidebar() {
|
||||
this.isSidebarExpanded = !this.isSidebarExpanded;
|
||||
},
|
||||
onResize() {
|
||||
const breakpoint = bp.getBreakpointSize();
|
||||
if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) {
|
||||
this.isSidebarExpanded = false;
|
||||
} else if (!this.isSidebarExpanded) {
|
||||
this.isSidebarExpanded = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<aside
|
||||
class="gl-fixed gl-right-0 gl-px-5 gl-bg-gray-10 gl-h-full gl-border-l-solid gl-border-1 gl-border-gray-100 gl-z-index-200 gl-overflow-hidden"
|
||||
:style="this.$options.styles"
|
||||
:class="{
|
||||
'gl-display-none': !isSidebarExpanded,
|
||||
}"
|
||||
>
|
||||
<div class="gl-py-5 gl-display-flex gl-align-items-center">
|
||||
<tooltip-on-truncate :title="buildName" truncate-target="child"
|
||||
><h4 class="gl-mb-0 gl-mr-2 gl-text-truncate">
|
||||
{{ buildName }}
|
||||
</h4>
|
||||
</tooltip-on-truncate>
|
||||
<!-- TODO: implement retry actions -->
|
||||
<div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right">
|
||||
<gl-dropdown
|
||||
:text="$options.i18n.retryButton"
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
right
|
||||
size="medium"
|
||||
>
|
||||
<gl-dropdown-item>{{ $options.i18n.retryTriggerJob }}</gl-dropdown-item>
|
||||
<gl-dropdown-item>{{ $options.i18n.retryDownstreamPipeline }}</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
<gl-button
|
||||
:aria-label="$options.i18n.toggleSidebar"
|
||||
data-testid="sidebar-expansion-toggle"
|
||||
category="tertiary"
|
||||
class="gl-md-display-none gl-ml-2"
|
||||
icon="chevron-double-lg-right"
|
||||
@click="toggleSidebar"
|
||||
/>
|
||||
</div>
|
||||
<!-- TODO: get job details and show commit block, stage dropdown, jobs list -->
|
||||
</aside>
|
||||
</template>
|
|
@ -5,7 +5,7 @@ import { __, s__, sprintf } from '~/locale';
|
|||
|
||||
export default {
|
||||
i18n: {
|
||||
eraseLogButtonLabel: s__('Job|Erase job log'),
|
||||
eraseLogButtonLabel: s__('Job|Erase job log and artifacts'),
|
||||
scrollToBottomButtonLabel: s__('Job|Scroll to bottom'),
|
||||
scrollToTopButtonLabel: s__('Job|Scroll to top'),
|
||||
showRawButtonLabel: s__('Job|Show complete raw'),
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import BridgeApp from './bridge/app.vue';
|
||||
import JobApp from './components/job_app.vue';
|
||||
import createStore from './store';
|
||||
|
||||
export default () => {
|
||||
const element = document.getElementById('js-job-vue-app');
|
||||
|
||||
const initializeJobPage = (element) => {
|
||||
const store = createStore();
|
||||
|
||||
// Let's start initializing the store (i.e. fetching data) right away
|
||||
|
@ -51,3 +52,35 @@ export default () => {
|
|||
},
|
||||
});
|
||||
};
|
||||
|
||||
const initializeBridgePage = (el) => {
|
||||
const { buildName, emptyStateIllustrationPath } = el.dataset;
|
||||
|
||||
Vue.use(VueApollo);
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
apolloProvider,
|
||||
provide: {
|
||||
buildName,
|
||||
emptyStateIllustrationPath,
|
||||
},
|
||||
render(h) {
|
||||
return h(BridgeApp);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const jobElement = document.getElementById('js-job-page');
|
||||
const bridgeElement = document.getElementById('js-bridge-page');
|
||||
|
||||
if (jobElement) {
|
||||
initializeJobPage(jobElement);
|
||||
} else {
|
||||
initializeBridgePage(bridgeElement);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
import $ from 'jquery';
|
||||
import createFlash from '~/flash';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { historyPushState } from '~/lib/utils/common_utils';
|
||||
import { __ } from '~/locale';
|
||||
import { GlTabsBehavior, TAB_SHOWN_EVENT } from '~/tabs';
|
||||
|
||||
export default class Milestone {
|
||||
constructor() {
|
||||
this.tabsEl = document.querySelector('.js-milestone-tabs');
|
||||
this.glTabs = new GlTabsBehavior(this.tabsEl);
|
||||
this.loadedTabs = new WeakSet();
|
||||
|
||||
this.bindTabsSwitching();
|
||||
this.loadInitialTab();
|
||||
}
|
||||
|
||||
bindTabsSwitching() {
|
||||
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
|
||||
const $target = $(e.target);
|
||||
|
||||
window.location.hash = $target.attr('href');
|
||||
this.loadTab($target);
|
||||
this.tabsEl.addEventListener(TAB_SHOWN_EVENT, (event) => {
|
||||
const tab = event.target;
|
||||
const { activeTabPanel } = event.detail;
|
||||
historyPushState(tab.getAttribute('href'));
|
||||
this.loadTab(tab, activeTabPanel);
|
||||
});
|
||||
}
|
||||
|
||||
loadInitialTab() {
|
||||
const $target = $(`.js-milestone-tabs a:not(.active)[href="${window.location.hash}"]`);
|
||||
|
||||
if ($target.length) {
|
||||
$target.tab('show');
|
||||
} else {
|
||||
this.loadTab($('.js-milestone-tabs a.active'));
|
||||
const tab = this.tabsEl.querySelector(`a[href="${window.location.hash}"]`);
|
||||
this.glTabs.activateTab(tab || this.glTabs.activeTab);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
loadTab($target) {
|
||||
const endpoint = $target.data('endpoint');
|
||||
const tabElId = $target.attr('href');
|
||||
loadTab(tab, tabPanel) {
|
||||
const { endpoint } = tab.dataset;
|
||||
|
||||
if (endpoint && !$target.hasClass('is-loaded')) {
|
||||
if (endpoint && !this.loadedTabs.has(tab)) {
|
||||
axios
|
||||
.get(endpoint)
|
||||
.then(({ data }) => {
|
||||
$(tabElId).html(data.html);
|
||||
$target.addClass('is-loaded');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
tabPanel.innerHTML = sanitize(data.html);
|
||||
this.loadedTabs.add(tab);
|
||||
})
|
||||
.catch(() =>
|
||||
createFlash({
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
GlEmptyState,
|
||||
GlFormGroup,
|
||||
GlFormInputGroup,
|
||||
GlLink,
|
||||
GlSkeletonLoader,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
|
@ -16,10 +15,7 @@ import {
|
|||
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
|
||||
DEPENDENCY_PROXY_DOCS_PATH,
|
||||
} from '~/packages_and_registries/settings/group/constants';
|
||||
import {
|
||||
GRAPHQL_PAGE_SIZE,
|
||||
ENABLE_DEPENDENCY_PROXY_DOCS_PATH,
|
||||
} from '~/packages_and_registries/dependency_proxy/constants';
|
||||
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
|
||||
|
||||
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
|
||||
|
||||
|
@ -29,7 +25,6 @@ export default {
|
|||
GlEmptyState,
|
||||
GlFormGroup,
|
||||
GlFormInputGroup,
|
||||
GlLink,
|
||||
GlSkeletonLoader,
|
||||
GlSprintf,
|
||||
ClipboardButton,
|
||||
|
@ -41,9 +36,6 @@ export default {
|
|||
proxyNotAvailableText: s__(
|
||||
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
|
||||
),
|
||||
proxyDisabledText: s__(
|
||||
'DependencyProxy|The Dependency Proxy is disabled. %{docLinkStart}Learn how to enable it%{docLinkEnd}.',
|
||||
),
|
||||
proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'),
|
||||
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
|
||||
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
|
||||
|
@ -52,7 +44,6 @@ export default {
|
|||
},
|
||||
links: {
|
||||
DEPENDENCY_PROXY_DOCS_PATH,
|
||||
ENABLE_DEPENDENCY_PROXY_DOCS_PATH,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -79,9 +70,7 @@ export default {
|
|||
},
|
||||
];
|
||||
},
|
||||
dependencyProxyEnabled() {
|
||||
return this.group?.dependencyProxySetting?.enabled;
|
||||
},
|
||||
|
||||
queryVariables() {
|
||||
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
|
||||
},
|
||||
|
@ -131,7 +120,7 @@ export default {
|
|||
|
||||
<gl-skeleton-loader v-else-if="$apollo.queries.group.loading" />
|
||||
|
||||
<div v-else-if="dependencyProxyEnabled" data-testid="main-area">
|
||||
<div v-else data-testid="main-area">
|
||||
<gl-form-group :label="$options.i18n.proxyImagePrefix">
|
||||
<gl-form-input-group
|
||||
readonly
|
||||
|
@ -170,12 +159,5 @@ export default {
|
|||
:title="$options.i18n.noManifestTitle"
|
||||
/>
|
||||
</div>
|
||||
<gl-alert v-else :dismissible="false" data-testid="proxy-disabled">
|
||||
<gl-sprintf :message="$options.i18n.proxyDisabledText">
|
||||
<template #docLink="{ content }">
|
||||
<gl-link :href="$options.links.ENABLE_DEPENDENCY_PROXY_DOCS_PATH">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1 @@
|
|||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
||||
export const GRAPHQL_PAGE_SIZE = 20;
|
||||
export const ENABLE_DEPENDENCY_PROXY_DOCS_PATH = helpPagePath(
|
||||
'user/packages/dependency_proxy/index',
|
||||
{ anchor: 'enable-or-disable-the-dependency-proxy-for-a-group' },
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
I18N_STALE_RUNNER_DESCRIPTION,
|
||||
STATUS_ONLINE,
|
||||
STATUS_NOT_CONNECTED,
|
||||
STATUS_NEVER_CONTACTED,
|
||||
STATUS_OFFLINE,
|
||||
STATUS_STALE,
|
||||
} from '../constants';
|
||||
|
@ -45,6 +46,7 @@ export default {
|
|||
}),
|
||||
};
|
||||
case STATUS_NOT_CONNECTED:
|
||||
case STATUS_NEVER_CONTACTED:
|
||||
return {
|
||||
variant: 'muted',
|
||||
label: s__('Runners|not connected'),
|
||||
|
|
|
@ -61,6 +61,7 @@ export const STATUS_PAUSED = 'PAUSED';
|
|||
|
||||
export const STATUS_ONLINE = 'ONLINE';
|
||||
export const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
|
||||
export const STATUS_NEVER_CONTACTED = 'NEVER_CONTACTED';
|
||||
export const STATUS_OFFLINE = 'OFFLINE';
|
||||
export const STATUS_STALE = 'STALE';
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
export const ACTIVE_TAB_CLASSES = Object.freeze([
|
||||
'active',
|
||||
'gl-tab-nav-item-active',
|
||||
'gl-tab-nav-item-active-indigo',
|
||||
]);
|
||||
|
||||
export const ACTIVE_PANEL_CLASS = 'active';
|
||||
|
||||
export const KEY_CODE_LEFT = 'ArrowLeft';
|
||||
export const KEY_CODE_UP = 'ArrowUp';
|
||||
export const KEY_CODE_RIGHT = 'ArrowRight';
|
||||
export const KEY_CODE_DOWN = 'ArrowDown';
|
||||
|
||||
export const ATTR_ARIA_CONTROLS = 'aria-controls';
|
||||
export const ATTR_ARIA_LABELLEDBY = 'aria-labelledby';
|
||||
export const ATTR_ARIA_SELECTED = 'aria-selected';
|
||||
export const ATTR_ROLE = 'role';
|
||||
export const ATTR_TABINDEX = 'tabindex';
|
||||
|
||||
export const TAB_SHOWN_EVENT = 'gl-tab-shown';
|
|
@ -0,0 +1,239 @@
|
|||
import { uniqueId } from 'lodash';
|
||||
import {
|
||||
ACTIVE_TAB_CLASSES,
|
||||
ATTR_ROLE,
|
||||
ATTR_ARIA_CONTROLS,
|
||||
ATTR_TABINDEX,
|
||||
ATTR_ARIA_SELECTED,
|
||||
ATTR_ARIA_LABELLEDBY,
|
||||
ACTIVE_PANEL_CLASS,
|
||||
KEY_CODE_LEFT,
|
||||
KEY_CODE_UP,
|
||||
KEY_CODE_RIGHT,
|
||||
KEY_CODE_DOWN,
|
||||
TAB_SHOWN_EVENT,
|
||||
} from './constants';
|
||||
|
||||
export { TAB_SHOWN_EVENT };
|
||||
|
||||
/**
|
||||
* The `GlTabsBehavior` class adds interactivity to tabs created by the `gl_tabs_nav` and
|
||||
* `gl_tab_link_to` Rails helpers.
|
||||
*
|
||||
* Example using `href` references:
|
||||
*
|
||||
* ```haml
|
||||
* = gl_tabs_nav({ class: 'js-my-tabs' }) do
|
||||
* = gl_tab_link_to '#foo', item_active: true do
|
||||
* = _('Foo')
|
||||
* = gl_tab_link_to '#bar' do
|
||||
* = _('Bar')
|
||||
*
|
||||
* .tab-content
|
||||
* .tab-pane.active#foo
|
||||
* .tab-pane#bar
|
||||
* ```
|
||||
*
|
||||
* ```javascript
|
||||
* import { GlTabsBehavior } from '~/tabs';
|
||||
*
|
||||
* const glTabs = new GlTabsBehavior(document.querySelector('.js-my-tabs'));
|
||||
* ```
|
||||
*
|
||||
* Example using `aria-controls` references:
|
||||
*
|
||||
* ```haml
|
||||
* = gl_tabs_nav({ class: 'js-my-tabs' }) do
|
||||
* = gl_tab_link_to '#', item_active: true, 'aria-controls': 'foo' do
|
||||
* = _('Foo')
|
||||
* = gl_tab_link_to '#', 'aria-controls': 'bar' do
|
||||
* = _('Bar')
|
||||
*
|
||||
* .tab-content
|
||||
* .tab-pane.active#foo
|
||||
* .tab-pane#bar
|
||||
* ```
|
||||
*
|
||||
* ```javascript
|
||||
* import { GlTabsBehavior } from '~/tabs';
|
||||
*
|
||||
* const glTabs = new GlTabsBehavior(document.querySelector('.js-my-tabs'));
|
||||
* ```
|
||||
*
|
||||
* `GlTabsBehavior` can be used to replace Bootstrap tab implementations that cannot
|
||||
* easily be rewritten in Vue.
|
||||
*
|
||||
* NOTE: Do *not* use `GlTabsBehavior` with markup generated by other means, as it may not
|
||||
* work correctly.
|
||||
*
|
||||
* Tab panels must exist somewhere in the page for the tabs to control. Tab panels
|
||||
* must:
|
||||
* - be immediate children of a `.tab-content` element
|
||||
* - have the `tab-pane` class
|
||||
* - if the panel is active, have the `active` class
|
||||
* - have a unique `id` attribute
|
||||
*
|
||||
* In order to associate tabs with panels, the tabs must reference their panel's
|
||||
* `id` by having one of the following attributes:
|
||||
* - `href`, e.g., `href="#the-panel-id"` (note the leading `#` in the value)
|
||||
* - `aria-controls`, e.g., `aria-controls="the-panel-id"` (no leading `#`)
|
||||
*
|
||||
* Exactly one tab/panel must be active in the original markup.
|
||||
*
|
||||
* Call the `destroy` method on an instance to remove event listeners that were
|
||||
* added during construction. Other DOM mutations (like ARIA attributes) are
|
||||
* _not_ reverted.
|
||||
*/
|
||||
export class GlTabsBehavior {
|
||||
/**
|
||||
* Create a GlTabsBehavior instance.
|
||||
*
|
||||
* @param {HTMLElement} el The element created by the Rails `gl_tabs_nav` helper.
|
||||
*/
|
||||
constructor(el) {
|
||||
if (!el) {
|
||||
throw new Error('Cannot instantiate GlTabsBehavior without an element');
|
||||
}
|
||||
|
||||
this.destroyFns = [];
|
||||
this.tabList = el;
|
||||
this.tabs = this.getTabs();
|
||||
this.activeTab = null;
|
||||
|
||||
this.setAccessibilityAttrs();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
setAccessibilityAttrs() {
|
||||
this.tabList.setAttribute(ATTR_ROLE, 'tablist');
|
||||
this.tabs.forEach((tab) => {
|
||||
if (!tab.hasAttribute('id')) {
|
||||
tab.setAttribute('id', uniqueId('gl_tab_nav__tab_'));
|
||||
}
|
||||
|
||||
if (!this.activeTab && tab.classList.contains(ACTIVE_TAB_CLASSES[0])) {
|
||||
this.activeTab = tab;
|
||||
tab.setAttribute(ATTR_ARIA_SELECTED, 'true');
|
||||
tab.removeAttribute(ATTR_TABINDEX);
|
||||
} else {
|
||||
tab.setAttribute(ATTR_ARIA_SELECTED, 'false');
|
||||
tab.setAttribute(ATTR_TABINDEX, '-1');
|
||||
}
|
||||
|
||||
tab.setAttribute(ATTR_ROLE, 'tab');
|
||||
tab.closest('.nav-item').setAttribute(ATTR_ROLE, 'presentation');
|
||||
|
||||
const tabPanel = this.getPanelForTab(tab);
|
||||
if (!tab.hasAttribute(ATTR_ARIA_CONTROLS)) {
|
||||
tab.setAttribute(ATTR_ARIA_CONTROLS, tabPanel.id);
|
||||
}
|
||||
|
||||
tabPanel.setAttribute(ATTR_ROLE, 'tabpanel');
|
||||
tabPanel.setAttribute(ATTR_ARIA_LABELLEDBY, tab.id);
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
this.tabs.forEach((tab) => {
|
||||
this.bindEvent(tab, 'click', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (tab !== this.activeTab) {
|
||||
this.activateTab(tab);
|
||||
}
|
||||
});
|
||||
|
||||
this.bindEvent(tab, 'keydown', (event) => {
|
||||
const { code } = event;
|
||||
if (code === KEY_CODE_UP || code === KEY_CODE_LEFT) {
|
||||
event.preventDefault();
|
||||
this.activatePreviousTab();
|
||||
} else if (code === KEY_CODE_DOWN || code === KEY_CODE_RIGHT) {
|
||||
event.preventDefault();
|
||||
this.activateNextTab();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bindEvent(el, ...args) {
|
||||
el.addEventListener(...args);
|
||||
|
||||
this.destroyFns.push(() => {
|
||||
el.removeEventListener(...args);
|
||||
});
|
||||
}
|
||||
|
||||
activatePreviousTab() {
|
||||
const currentTabIndex = this.tabs.indexOf(this.activeTab);
|
||||
|
||||
if (currentTabIndex <= 0) return;
|
||||
|
||||
const previousTab = this.tabs[currentTabIndex - 1];
|
||||
this.activateTab(previousTab);
|
||||
previousTab.focus();
|
||||
}
|
||||
|
||||
activateNextTab() {
|
||||
const currentTabIndex = this.tabs.indexOf(this.activeTab);
|
||||
|
||||
if (currentTabIndex >= this.tabs.length - 1) return;
|
||||
|
||||
const nextTab = this.tabs[currentTabIndex + 1];
|
||||
this.activateTab(nextTab);
|
||||
nextTab.focus();
|
||||
}
|
||||
|
||||
getTabs() {
|
||||
return Array.from(this.tabList.querySelectorAll('.gl-tab-nav-item'));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
getPanelForTab(tab) {
|
||||
const ariaControls = tab.getAttribute(ATTR_ARIA_CONTROLS);
|
||||
|
||||
if (ariaControls) {
|
||||
return document.querySelector(`#${ariaControls}`);
|
||||
}
|
||||
|
||||
return document.querySelector(tab.getAttribute('href'));
|
||||
}
|
||||
|
||||
activateTab(tabToActivate) {
|
||||
// Deactivate active tab first
|
||||
this.activeTab.setAttribute(ATTR_ARIA_SELECTED, 'false');
|
||||
this.activeTab.setAttribute(ATTR_TABINDEX, '-1');
|
||||
this.activeTab.classList.remove(...ACTIVE_TAB_CLASSES);
|
||||
|
||||
const activePanel = this.getPanelForTab(this.activeTab);
|
||||
activePanel.classList.remove(ACTIVE_PANEL_CLASS);
|
||||
|
||||
// Now activate the given tab/panel
|
||||
tabToActivate.setAttribute(ATTR_ARIA_SELECTED, 'true');
|
||||
tabToActivate.removeAttribute(ATTR_TABINDEX);
|
||||
tabToActivate.classList.add(...ACTIVE_TAB_CLASSES);
|
||||
|
||||
const tabPanel = this.getPanelForTab(tabToActivate);
|
||||
tabPanel.classList.add(ACTIVE_PANEL_CLASS);
|
||||
|
||||
this.activeTab = tabToActivate;
|
||||
|
||||
this.dispatchTabShown(tabToActivate, tabPanel);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
dispatchTabShown(tab, activeTabPanel) {
|
||||
const event = new CustomEvent(TAB_SHOWN_EVENT, {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
activeTabPanel,
|
||||
},
|
||||
});
|
||||
|
||||
tab.dispatchEvent(event);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.destroyFns.forEach((destroy) => destroy());
|
||||
}
|
||||
}
|
|
@ -1,12 +1,5 @@
|
|||
@import 'mixins_and_variables_and_functions';
|
||||
|
||||
// Fixing double scrollbar issue
|
||||
// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1156 and
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54837
|
||||
.import-entities-namespace-dropdown.show.dropdown .dropdown-menu {
|
||||
max-height: initial;
|
||||
}
|
||||
|
||||
.import-jobs-to-col {
|
||||
width: 39%;
|
||||
}
|
||||
|
@ -38,3 +31,31 @@
|
|||
box-shadow: inset 0 0 0 1px var(--gray-200, $gray-200);
|
||||
}
|
||||
}
|
||||
|
||||
$import-bar-height: $gl-spacing-scale-11;
|
||||
|
||||
.import-table-bar {
|
||||
@include gl-sticky;
|
||||
height: $import-bar-height;
|
||||
top: $header-height;
|
||||
z-index: 3;
|
||||
|
||||
html.with-performance-bar & {
|
||||
top: $header-height + $performance-bar-height;
|
||||
}
|
||||
}
|
||||
|
||||
.import-table {
|
||||
border-collapse: separate;
|
||||
|
||||
thead {
|
||||
@include gl-sticky;
|
||||
background-color: var(--gray-10, $gray-10);
|
||||
top: calc(#{$header-height} + #{$import-bar-height});
|
||||
z-index: 3;
|
||||
|
||||
html.with-performance-bar & {
|
||||
top: calc(#{$header-height + $performance-bar-height} + #{$import-bar-height});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@ module DependencyProxy
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :verify_dependency_proxy_enabled!
|
||||
before_action :verify_dependency_proxy_available!
|
||||
before_action :authorize_read_dependency_proxy!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify_dependency_proxy_enabled!
|
||||
def verify_dependency_proxy_available!
|
||||
render_404 unless group&.dependency_proxy_feature_available?
|
||||
end
|
||||
|
||||
|
|
|
@ -5,30 +5,19 @@ module Groups
|
|||
include ::DependencyProxy::GroupAccess
|
||||
|
||||
before_action :authorize_admin_dependency_proxy!, only: :update
|
||||
before_action :dependency_proxy
|
||||
before_action :verify_dependency_proxy_enabled!
|
||||
|
||||
feature_category :package_registry
|
||||
|
||||
def show
|
||||
@blobs_count = group.dependency_proxy_blobs.count
|
||||
@blobs_total_size = group.dependency_proxy_blobs.total_size
|
||||
end
|
||||
|
||||
def update
|
||||
dependency_proxy.update(dependency_proxy_params)
|
||||
|
||||
redirect_to group_dependency_proxy_path(group)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dependency_proxy
|
||||
@dependency_proxy ||=
|
||||
group.dependency_proxy_setting || group.create_dependency_proxy_setting
|
||||
group.dependency_proxy_setting || group.create_dependency_proxy_setting!
|
||||
end
|
||||
|
||||
def dependency_proxy_params
|
||||
params.require(:dependency_proxy_group_setting).permit(:enabled)
|
||||
def verify_dependency_proxy_enabled!
|
||||
render_404 unless dependency_proxy.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,8 +4,8 @@ class Projects::JobsController < Projects::ApplicationController
|
|||
include SendFileUpload
|
||||
include ContinueParams
|
||||
|
||||
before_action :find_job_as_build, except: [:index, :play]
|
||||
before_action :find_job_as_processable, only: [:play]
|
||||
before_action :find_job_as_build, except: [:index, :play, :show]
|
||||
before_action :find_job_as_processable, only: [:play, :show]
|
||||
before_action :authorize_read_build_trace!, only: [:trace, :raw]
|
||||
before_action :authorize_read_build!
|
||||
before_action :authorize_update_build!,
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserCalloutsController < ApplicationController
|
||||
feature_category :navigation
|
||||
|
||||
def create
|
||||
if callout.persisted?
|
||||
respond_to do |format|
|
||||
format.json { head :ok }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json { head :bad_request }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def callout
|
||||
Users::DismissUserCalloutService.new(
|
||||
container: nil, current_user: current_user, params: { feature_name: feature_name }
|
||||
).execute
|
||||
end
|
||||
|
||||
def feature_name
|
||||
params.require(:feature_name)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class CalloutsController < ApplicationController
|
||||
feature_category :navigation
|
||||
|
||||
def create
|
||||
if callout.persisted?
|
||||
respond_to do |format|
|
||||
format.json { head :ok }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json { head :bad_request }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def callout
|
||||
Users::DismissCalloutService.new(
|
||||
container: nil, current_user: current_user, params: { feature_name: feature_name }
|
||||
).execute
|
||||
end
|
||||
|
||||
def feature_name
|
||||
params.require(:feature_name)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class GroupCalloutsController < UserCalloutsController
|
||||
class GroupCalloutsController < Users::CalloutsController
|
||||
private
|
||||
|
||||
def callout
|
||||
|
|
|
@ -15,7 +15,7 @@ module Mutations
|
|||
description: 'User callout dismissed.'
|
||||
|
||||
def resolve(feature_name:)
|
||||
callout = Users::DismissUserCalloutService.new(
|
||||
callout = Users::DismissCalloutService.new(
|
||||
container: nil, current_user: current_user, params: { feature_name: feature_name }
|
||||
).execute
|
||||
errors = errors_on_object(callout)
|
||||
|
|
|
@ -25,13 +25,17 @@ module Types
|
|||
value: :offline
|
||||
|
||||
value 'STALE',
|
||||
description: "Runner that has not contacted this instance within the last #{::Ci::Runner::STALE_TIMEOUT.inspect}. Only available if legacyMode is null. Will be a possible return value starting in 15.0",
|
||||
description: "Runner that has not contacted this instance within the last #{::Ci::Runner::STALE_TIMEOUT.inspect}. Only available if legacyMode is null. Will be a possible return value starting in 15.0.",
|
||||
value: :stale
|
||||
|
||||
value 'NOT_CONNECTED',
|
||||
description: 'Runner that has never contacted this instance.',
|
||||
deprecated: { reason: 'This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period of no contact', milestone: '14.6' },
|
||||
deprecated: { reason: "Use NEVER_CONTACTED instead. NEVER_CONTACTED will have a slightly different scope starting in 15.0, with STALE being returned instead after #{::Ci::Runner::STALE_TIMEOUT.inspect} of no contact", milestone: '14.6' },
|
||||
value: :not_connected
|
||||
|
||||
value 'NEVER_CONTACTED',
|
||||
description: 'Runner that has never contacted this instance. Set legacyMode to null to utilize this value. Will replace NOT_CONNECTED starting in 15.0.',
|
||||
value: :never_contacted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module Types
|
|||
graphql_name 'UserCalloutFeatureNameEnum'
|
||||
description 'Name of the feature that the callout is for.'
|
||||
|
||||
::UserCallout.feature_names.keys.each do |feature_name|
|
||||
::Users::Callout.feature_names.keys.each do |feature_name|
|
||||
value feature_name.upcase, value: feature_name, description: "Callout feature name for #{feature_name}."
|
||||
end
|
||||
end
|
||||
|
|
|
@ -206,10 +206,6 @@ module ApplicationHelper
|
|||
'https://' + promo_host
|
||||
end
|
||||
|
||||
def contact_sales_url
|
||||
promo_url + '/sales'
|
||||
end
|
||||
|
||||
def support_url
|
||||
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
|
||||
end
|
||||
|
|
|
@ -19,6 +19,13 @@ module Ci
|
|||
}
|
||||
end
|
||||
|
||||
def bridge_data(build)
|
||||
{
|
||||
"build_name" => build.name,
|
||||
"empty-state-illustration-path" => image_path('illustrations/job-trigger-md.svg')
|
||||
}
|
||||
end
|
||||
|
||||
def job_counts
|
||||
{
|
||||
"all" => limited_counter_with_delimiter(@all_builds),
|
||||
|
|
|
@ -23,7 +23,7 @@ module Ci
|
|||
icon = 'status-paused'
|
||||
span_class = 'gl-text-gray-600'
|
||||
end
|
||||
when :not_connected
|
||||
when :not_connected, :never_contacted
|
||||
title = s_("Runners|New runner, has not connected yet")
|
||||
icon = 'warning-solid'
|
||||
when :offline
|
||||
|
|
|
@ -182,7 +182,7 @@ module MergeRequestsHelper
|
|||
project_path: project_path(merge_request.project),
|
||||
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
|
||||
is_fluid_layout: fluid_layout.to_s,
|
||||
dismiss_endpoint: user_callouts_path,
|
||||
dismiss_endpoint: callouts_path,
|
||||
show_suggest_popover: show_suggest_popover?.to_s,
|
||||
show_whitespace_default: @show_whitespace_default.to_s,
|
||||
file_by_file_default: @file_by_file_default.to_s,
|
||||
|
|
|
@ -14,8 +14,7 @@ module TabHelper
|
|||
gl_tabs_classes = %w[nav gl-tabs-nav]
|
||||
|
||||
html_options = html_options.merge(
|
||||
class: [*html_options[:class], gl_tabs_classes].join(' '),
|
||||
role: 'tablist'
|
||||
class: [*html_options[:class], gl_tabs_classes].join(' ')
|
||||
)
|
||||
|
||||
content = capture(&block) if block_given?
|
||||
|
@ -54,7 +53,7 @@ module TabHelper
|
|||
extra_tab_classes = html_options.delete(:tab_class)
|
||||
tab_class = %w[nav-item].push(*extra_tab_classes)
|
||||
|
||||
content_tag(:li, class: tab_class, role: 'presentation') do
|
||||
content_tag(:li, class: tab_class) do
|
||||
if block_given?
|
||||
link_to(options, html_options, &block)
|
||||
else
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UserCalloutsHelper
|
||||
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'
|
||||
GCP_SIGNUP_OFFER = 'gcp_signup_offer'
|
||||
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'
|
||||
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
|
||||
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
|
||||
FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version'
|
||||
REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout'
|
||||
UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout'
|
||||
INVITE_MEMBERS_BANNER = 'invite_members_banner'
|
||||
SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout'
|
||||
|
||||
def show_gke_cluster_integration_callout?(project)
|
||||
active_nav_link?(controller: sidebar_operations_paths) &&
|
||||
can?(current_user, :create_cluster, project) &&
|
||||
!user_dismissed?(GKE_CLUSTER_INTEGRATION)
|
||||
end
|
||||
|
||||
def show_gcp_signup_offer?
|
||||
!user_dismissed?(GCP_SIGNUP_OFFER)
|
||||
end
|
||||
|
||||
def render_flash_user_callout(flash_type, message, feature_name)
|
||||
render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name
|
||||
end
|
||||
|
||||
def render_dashboard_ultimate_trial(user)
|
||||
end
|
||||
|
||||
def render_two_factor_auth_recovery_settings_check
|
||||
end
|
||||
|
||||
def show_suggest_popover?
|
||||
!user_dismissed?(SUGGEST_POPOVER_DISMISSED)
|
||||
end
|
||||
|
||||
def show_customize_homepage_banner?
|
||||
current_user.default_dashboard? && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
|
||||
end
|
||||
|
||||
def show_feature_flags_new_version?
|
||||
!user_dismissed?(FEATURE_FLAGS_NEW_VERSION)
|
||||
end
|
||||
|
||||
def show_unfinished_tag_cleanup_callout?
|
||||
!user_dismissed?(UNFINISHED_TAG_CLEANUP_CALLOUT)
|
||||
end
|
||||
|
||||
def show_registration_enabled_user_callout?
|
||||
!Gitlab.com? &&
|
||||
current_user&.admin? &&
|
||||
signup_enabled? &&
|
||||
!user_dismissed?(REGISTRATION_ENABLED_CALLOUT)
|
||||
end
|
||||
|
||||
def dismiss_two_factor_auth_recovery_settings_check
|
||||
end
|
||||
|
||||
def show_invite_banner?(group)
|
||||
Ability.allowed?(current_user, :admin_group, group) &&
|
||||
!just_created? &&
|
||||
!user_dismissed_for_group(INVITE_MEMBERS_BANNER, group) &&
|
||||
!multiple_members?(group)
|
||||
end
|
||||
|
||||
def show_security_newsletter_user_callout?
|
||||
current_user&.admin? &&
|
||||
!user_dismissed?(SECURITY_NEWSLETTER_CALLOUT)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
|
||||
return false unless current_user
|
||||
|
||||
current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
|
||||
end
|
||||
|
||||
def user_dismissed_for_group(feature_name, group, ignore_dismissal_earlier_than = nil)
|
||||
return false unless current_user
|
||||
|
||||
current_user.dismissed_callout_for_group?(feature_name: feature_name,
|
||||
group: group,
|
||||
ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
|
||||
end
|
||||
|
||||
def just_created?
|
||||
flash[:notice]&.include?('successfully created')
|
||||
end
|
||||
|
||||
def multiple_members?(group)
|
||||
group.member_count > 1 || group.members_with_parents.count > 1
|
||||
end
|
||||
end
|
||||
|
||||
UserCalloutsHelper.prepend_mod
|
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
module CalloutsHelper
|
||||
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'
|
||||
GCP_SIGNUP_OFFER = 'gcp_signup_offer'
|
||||
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'
|
||||
TABS_POSITION_HIGHLIGHT = 'tabs_position_highlight'
|
||||
CUSTOMIZE_HOMEPAGE = 'customize_homepage'
|
||||
FEATURE_FLAGS_NEW_VERSION = 'feature_flags_new_version'
|
||||
REGISTRATION_ENABLED_CALLOUT = 'registration_enabled_callout'
|
||||
UNFINISHED_TAG_CLEANUP_CALLOUT = 'unfinished_tag_cleanup_callout'
|
||||
SECURITY_NEWSLETTER_CALLOUT = 'security_newsletter_callout'
|
||||
|
||||
def show_gke_cluster_integration_callout?(project)
|
||||
active_nav_link?(controller: sidebar_operations_paths) &&
|
||||
can?(current_user, :create_cluster, project) &&
|
||||
!user_dismissed?(GKE_CLUSTER_INTEGRATION)
|
||||
end
|
||||
|
||||
def show_gcp_signup_offer?
|
||||
!user_dismissed?(GCP_SIGNUP_OFFER)
|
||||
end
|
||||
|
||||
def render_flash_user_callout(flash_type, message, feature_name)
|
||||
render 'shared/flash_user_callout', flash_type: flash_type, message: message, feature_name: feature_name
|
||||
end
|
||||
|
||||
def render_dashboard_ultimate_trial(user)
|
||||
end
|
||||
|
||||
def render_two_factor_auth_recovery_settings_check
|
||||
end
|
||||
|
||||
def show_suggest_popover?
|
||||
!user_dismissed?(SUGGEST_POPOVER_DISMISSED)
|
||||
end
|
||||
|
||||
def show_customize_homepage_banner?
|
||||
current_user.default_dashboard? && !user_dismissed?(CUSTOMIZE_HOMEPAGE)
|
||||
end
|
||||
|
||||
def show_feature_flags_new_version?
|
||||
!user_dismissed?(FEATURE_FLAGS_NEW_VERSION)
|
||||
end
|
||||
|
||||
def show_unfinished_tag_cleanup_callout?
|
||||
!user_dismissed?(UNFINISHED_TAG_CLEANUP_CALLOUT)
|
||||
end
|
||||
|
||||
def show_registration_enabled_user_callout?
|
||||
!Gitlab.com? &&
|
||||
current_user&.admin? &&
|
||||
signup_enabled? &&
|
||||
!user_dismissed?(REGISTRATION_ENABLED_CALLOUT)
|
||||
end
|
||||
|
||||
def dismiss_two_factor_auth_recovery_settings_check
|
||||
end
|
||||
|
||||
def show_security_newsletter_user_callout?
|
||||
current_user&.admin? &&
|
||||
!user_dismissed?(SECURITY_NEWSLETTER_CALLOUT)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil)
|
||||
return false unless current_user
|
||||
|
||||
current_user.dismissed_callout?(feature_name: feature_name, ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Users::CalloutsHelper.prepend_mod
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
module GroupCalloutsHelper
|
||||
INVITE_MEMBERS_BANNER = 'invite_members_banner'
|
||||
|
||||
def show_invite_banner?(group)
|
||||
Ability.allowed?(current_user, :admin_group, group) &&
|
||||
!just_created? &&
|
||||
!user_dismissed_for_group(INVITE_MEMBERS_BANNER, group) &&
|
||||
!multiple_members?(group)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_dismissed_for_group(feature_name, group, ignore_dismissal_earlier_than = nil)
|
||||
return false unless current_user
|
||||
|
||||
current_user.dismissed_callout_for_group?(feature_name: feature_name,
|
||||
group: group,
|
||||
ignore_dismissal_earlier_than: ignore_dismissal_earlier_than)
|
||||
end
|
||||
|
||||
def just_created?
|
||||
flash[:notice]&.include?('successfully created')
|
||||
end
|
||||
|
||||
def multiple_members?(group)
|
||||
group.member_count > 1 || group.members_with_parents.count > 1
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,7 +44,7 @@ module Ci
|
|||
|
||||
AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze
|
||||
AVAILABLE_TYPES = runner_types.keys.freeze
|
||||
AVAILABLE_STATUSES = %w[active paused online offline not_connected stale].freeze
|
||||
AVAILABLE_STATUSES = %w[active paused online offline not_connected never_contacted stale].freeze # TODO: Remove in %15.0: active, paused, not_connected. Relevant issues: https://gitlab.com/gitlab-org/gitlab/-/issues/347303, https://gitlab.com/gitlab-org/gitlab/-/issues/347305, https://gitlab.com/gitlab-org/gitlab/-/issues/344648
|
||||
AVAILABLE_SCOPES = (AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
|
||||
|
||||
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
|
||||
|
@ -66,7 +66,8 @@ module Ci
|
|||
scope :recent, -> { where('ci_runners.created_at >= :date OR ci_runners.contacted_at >= :date', date: stale_deadline) }
|
||||
scope :stale, -> { where('ci_runners.created_at < :date AND (ci_runners.contacted_at IS NULL OR ci_runners.contacted_at < :date)', date: stale_deadline) }
|
||||
scope :offline, -> { where(arel_table[:contacted_at].lteq(online_contact_time_deadline)) }
|
||||
scope :not_connected, -> { where(contacted_at: nil) }
|
||||
scope :not_connected, -> { where(contacted_at: nil) } # TODO: Remove in 15.0
|
||||
scope :never_contacted, -> { where(contacted_at: nil) }
|
||||
scope :ordered, -> { order(id: :desc) }
|
||||
|
||||
scope :with_recent_runner_queue, -> { where('contacted_at > ?', recent_queue_deadline) }
|
||||
|
@ -284,7 +285,7 @@ module Ci
|
|||
return deprecated_rest_status if legacy_mode == '14.5'
|
||||
|
||||
return :stale if stale?
|
||||
return :not_connected unless contacted_at
|
||||
return :never_contacted unless contacted_at
|
||||
|
||||
online? ? :online : :offline
|
||||
end
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Calloutable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
end
|
||||
|
||||
def dismissed_after?(dismissed_after)
|
||||
dismissed_at > dismissed_after
|
||||
end
|
||||
end
|
|
@ -204,7 +204,7 @@ class User < ApplicationRecord
|
|||
has_many :bulk_imports
|
||||
|
||||
has_many :custom_attributes, class_name: 'UserCustomAttribute'
|
||||
has_many :callouts, class_name: 'UserCallout'
|
||||
has_many :callouts, class_name: 'Users::Callout'
|
||||
has_many :group_callouts, class_name: 'Users::GroupCallout'
|
||||
has_many :term_agreements
|
||||
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
|
||||
|
@ -1947,7 +1947,7 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def find_or_initialize_callout(feature_name)
|
||||
callouts.find_or_initialize_by(feature_name: ::UserCallout.feature_names[feature_name])
|
||||
callouts.find_or_initialize_by(feature_name: ::Users::Callout.feature_names[feature_name])
|
||||
end
|
||||
|
||||
def find_or_initialize_group_callout(feature_name, group_id)
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserCallout < ApplicationRecord
|
||||
include Calloutable
|
||||
|
||||
enum feature_name: {
|
||||
gke_cluster_integration: 1,
|
||||
gcp_signup_offer: 2,
|
||||
cluster_security_warning: 3,
|
||||
ultimate_trial: 4, # EE-only
|
||||
geo_enable_hashed_storage: 5, # EE-only
|
||||
geo_migrate_hashed_storage: 6, # EE-only
|
||||
canary_deployment: 7, # EE-only
|
||||
gold_trial_billings: 8, # EE-only
|
||||
suggest_popover_dismissed: 9,
|
||||
tabs_position_highlight: 10,
|
||||
threat_monitoring_info: 11, # EE-only
|
||||
two_factor_auth_recovery_settings_check: 12, # EE-only
|
||||
web_ide_alert_dismissed: 16, # no longer in use
|
||||
active_user_count_threshold: 18, # EE-only
|
||||
buy_pipeline_minutes_notification_dot: 19, # EE-only
|
||||
personal_access_token_expiry: 21, # EE-only
|
||||
suggest_pipeline: 22,
|
||||
customize_homepage: 23,
|
||||
feature_flags_new_version: 24,
|
||||
registration_enabled_callout: 25,
|
||||
new_user_signups_cap_reached: 26, # EE-only
|
||||
unfinished_tag_cleanup_callout: 27,
|
||||
eoa_bronze_plan_banner: 28, # EE-only
|
||||
pipeline_needs_banner: 29,
|
||||
pipeline_needs_hover_tip: 30,
|
||||
web_ide_ci_environments_guidance: 31,
|
||||
security_configuration_upgrade_banner: 32,
|
||||
cloud_licensing_subscription_activation_banner: 33, # EE-only
|
||||
trial_status_reminder_d14: 34, # EE-only
|
||||
trial_status_reminder_d3: 35, # EE-only
|
||||
security_configuration_devops_alert: 36, # EE-only
|
||||
profile_personal_access_token_expiry: 37, # EE-only
|
||||
terraform_notification_dismissed: 38,
|
||||
security_newsletter_callout: 39,
|
||||
verification_reminder: 40 # EE-only
|
||||
}
|
||||
|
||||
validates :feature_name,
|
||||
presence: true,
|
||||
uniqueness: { scope: :user_id },
|
||||
inclusion: { in: UserCallout.feature_names.keys }
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class Callout < ApplicationRecord
|
||||
include Users::Calloutable
|
||||
|
||||
self.table_name = 'user_callouts'
|
||||
|
||||
enum feature_name: {
|
||||
gke_cluster_integration: 1,
|
||||
gcp_signup_offer: 2,
|
||||
cluster_security_warning: 3,
|
||||
ultimate_trial: 4, # EE-only
|
||||
geo_enable_hashed_storage: 5, # EE-only
|
||||
geo_migrate_hashed_storage: 6, # EE-only
|
||||
canary_deployment: 7, # EE-only
|
||||
gold_trial_billings: 8, # EE-only
|
||||
suggest_popover_dismissed: 9,
|
||||
tabs_position_highlight: 10,
|
||||
threat_monitoring_info: 11, # EE-only
|
||||
two_factor_auth_recovery_settings_check: 12, # EE-only
|
||||
web_ide_alert_dismissed: 16, # no longer in use
|
||||
active_user_count_threshold: 18, # EE-only
|
||||
buy_pipeline_minutes_notification_dot: 19, # EE-only
|
||||
personal_access_token_expiry: 21, # EE-only
|
||||
suggest_pipeline: 22,
|
||||
customize_homepage: 23,
|
||||
feature_flags_new_version: 24,
|
||||
registration_enabled_callout: 25,
|
||||
new_user_signups_cap_reached: 26, # EE-only
|
||||
unfinished_tag_cleanup_callout: 27,
|
||||
eoa_bronze_plan_banner: 28, # EE-only
|
||||
pipeline_needs_banner: 29,
|
||||
pipeline_needs_hover_tip: 30,
|
||||
web_ide_ci_environments_guidance: 31,
|
||||
security_configuration_upgrade_banner: 32,
|
||||
cloud_licensing_subscription_activation_banner: 33, # EE-only
|
||||
trial_status_reminder_d14: 34, # EE-only
|
||||
trial_status_reminder_d3: 35, # EE-only
|
||||
security_configuration_devops_alert: 36, # EE-only
|
||||
profile_personal_access_token_expiry: 37, # EE-only
|
||||
terraform_notification_dismissed: 38,
|
||||
security_newsletter_callout: 39,
|
||||
verification_reminder: 40 # EE-only
|
||||
}
|
||||
|
||||
validates :feature_name,
|
||||
presence: true,
|
||||
uniqueness: { scope: :user_id },
|
||||
inclusion: { in: Users::Callout.feature_names.keys }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
module Calloutable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
end
|
||||
|
||||
def dismissed_after?(dismissed_after)
|
||||
dismissed_at > dismissed_after
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Users
|
||||
class GroupCallout < ApplicationRecord
|
||||
include Calloutable
|
||||
include Users::Calloutable
|
||||
|
||||
self.table_name = 'user_group_callouts'
|
||||
|
||||
|
|
|
@ -15,19 +15,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
|
||||
Gitlab::Highlight.highlight(
|
||||
blob.path,
|
||||
limited_blob_data(to: to),
|
||||
language: language,
|
||||
plain: plain
|
||||
)
|
||||
end
|
||||
|
||||
def highlight_transformed(plain: nil)
|
||||
load_all_blob_data
|
||||
|
||||
Gitlab::Highlight.highlight(
|
||||
blob.path,
|
||||
transformed_blob_data,
|
||||
language: transformed_blob_language,
|
||||
blob_data(to),
|
||||
language: blob_language,
|
||||
plain: plain
|
||||
)
|
||||
end
|
||||
|
@ -38,6 +27,14 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
highlight(plain: false)
|
||||
end
|
||||
|
||||
def blob_data(to)
|
||||
@_blob_data ||= Gitlab::Diff::CustomDiff.transformed_blob_data(blob) || limited_blob_data(to: to)
|
||||
end
|
||||
|
||||
def blob_language
|
||||
@_blob_language ||= Gitlab::Diff::CustomDiff.transformed_blob_language(blob) || language
|
||||
end
|
||||
|
||||
def raw_plain_data
|
||||
blob.data unless blob.binary?
|
||||
end
|
||||
|
@ -134,23 +131,6 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
|
|||
def language
|
||||
blob.language_from_gitattributes
|
||||
end
|
||||
|
||||
def transformed_blob_language
|
||||
@transformed_blob_language ||= blob.path.ends_with?('.ipynb') ? 'md' : language
|
||||
end
|
||||
|
||||
def transformed_blob_data
|
||||
@transformed_blob ||= if blob.path.ends_with?('.ipynb') && blob.transformed_for_diff
|
||||
IpynbDiff.transform(blob.data,
|
||||
raise_errors: true,
|
||||
options: { include_metadata: false, cell_decorator: :percent })
|
||||
end
|
||||
|
||||
@transformed_blob ||= blob.data
|
||||
rescue IpynbDiff::InvalidNotebookError => e
|
||||
Gitlab::ErrorTracking.log_exception(e)
|
||||
blob.data
|
||||
end
|
||||
end
|
||||
|
||||
BlobPresenter.prepend_mod_with('BlobPresenter')
|
||||
|
|
|
@ -73,7 +73,7 @@ class MergeRequestWidgetEntity < Grape::Entity
|
|||
end
|
||||
|
||||
expose :user_callouts_path do |_merge_request|
|
||||
user_callouts_path
|
||||
callouts_path
|
||||
end
|
||||
|
||||
expose :suggest_pipeline_feature_id do |_merge_request|
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Ci
|
||||
class RetryBuildService < ::BaseService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def self.clone_accessors
|
||||
%i[pipeline project ref tag options name
|
||||
allow_failure stage stage_id stage_idx trigger_request
|
||||
|
@ -45,6 +47,11 @@ module Ci
|
|||
job.save!
|
||||
end
|
||||
end
|
||||
|
||||
if create_deployment_in_separate_transaction?
|
||||
clone_deployment!(new_build, build)
|
||||
end
|
||||
|
||||
build.reset # refresh the data to get new values of `retried` and `processed`.
|
||||
|
||||
new_build
|
||||
|
@ -63,15 +70,22 @@ module Ci
|
|||
|
||||
def clone_build(build)
|
||||
project.builds.new(build_attributes(build)).tap do |new_build|
|
||||
unless create_deployment_in_separate_transaction?
|
||||
new_build.assign_attributes(deployment_attributes_for(new_build, build))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_attributes(build)
|
||||
attributes = self.class.clone_accessors.to_h do |attribute|
|
||||
[attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
if create_deployment_in_separate_transaction? && build.persisted_environment.present?
|
||||
attributes[:metadata_attributes] ||= {}
|
||||
attributes[:metadata_attributes][:expanded_environment_name] = build.expanded_environment_name
|
||||
end
|
||||
|
||||
attributes[:user] = current_user
|
||||
attributes
|
||||
end
|
||||
|
@ -80,6 +94,26 @@ module Ci
|
|||
::Gitlab::Ci::Pipeline::Seed::Build
|
||||
.deployment_attributes_for(new_build, old_build.persisted_environment)
|
||||
end
|
||||
|
||||
def clone_deployment!(new_build, old_build)
|
||||
return unless old_build.deployment.present?
|
||||
|
||||
# We should clone the previous deployment attributes instead of initializing
|
||||
# new object with `Seed::Deployment`.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/347206
|
||||
deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment
|
||||
.new(new_build, old_build.persisted_environment).to_resource
|
||||
|
||||
return unless deployment
|
||||
|
||||
new_build.create_deployment!(deployment.attributes)
|
||||
end
|
||||
|
||||
def create_deployment_in_separate_transaction?
|
||||
strong_memoize(:create_deployment_in_separate_transaction) do
|
||||
::Feature.enabled?(:create_deployment_in_separate_transaction, project, default_enabled: :yaml)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -15,12 +15,22 @@ module MergeRequests
|
|||
|
||||
def execute
|
||||
line_position = position.line_range["end"] || position.line_range["start"]
|
||||
diff_line_index = diff_lines.find_index do |l|
|
||||
if line_position["new_line"]
|
||||
l.new_line == line_position["new_line"]
|
||||
elsif line_position["old_line"]
|
||||
l.old_line == line_position["old_line"]
|
||||
found_line = false
|
||||
diff_line_index = -1
|
||||
diff_lines.each_with_index do |l, i|
|
||||
if found_line
|
||||
if !l.type
|
||||
break
|
||||
elsif l.type == 'new'
|
||||
diff_line_index = i
|
||||
break
|
||||
end
|
||||
else
|
||||
# Find the old line
|
||||
found_line = l.old_line == line_position["new_line"]
|
||||
end
|
||||
|
||||
diff_line_index = i
|
||||
end
|
||||
initial_line_index = [diff_line_index - OVERFLOW_LINES_COUNT, 0].max
|
||||
last_line_index = [diff_line_index + OVERFLOW_LINES_COUNT, diff_lines.length].min
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class DismissUserCalloutService < BaseContainerService
|
||||
class DismissCalloutService < BaseContainerService
|
||||
def execute
|
||||
callout.tap do |record|
|
||||
record.update(dismissed_at: Time.current) if record.valid?
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class DismissGroupCalloutService < DismissUserCalloutService
|
||||
class DismissGroupCalloutService < DismissCalloutService
|
||||
private
|
||||
|
||||
def callout
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
variant: :tip,
|
||||
alert_class: 'js-security-newsletter-callout',
|
||||
is_contained: true,
|
||||
alert_data: { feature_id: UserCalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: user_callouts_path, defer_links: 'true' },
|
||||
alert_data: { feature_id: Users::CalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' },
|
||||
close_button_data: { testid: 'close-security-newsletter-callout' } do
|
||||
.gl-alert-body
|
||||
= s_('AdminArea|Sign up for the GitLab Security Newsletter to get notified for security updates.')
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
|
||||
.gcp-signup-offer.gl-alert.gl-alert-info.gl-my-3{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
|
||||
.gcp-signup-offer.gl-alert.gl-alert-info.gl-my-3{ role: 'alert', data: { feature_id: Users::CalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: callouts_path } }
|
||||
.gl-alert-container
|
||||
%button.js-close.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon{ type: 'button', 'aria-label' => _('Dismiss') }
|
||||
= sprite_icon('close', size: 16, css_class: 'gl-icon')
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
%ul.nav-links.new-session-tabs.single-tab.nav-tabs.nav
|
||||
%li.nav-item
|
||||
%a.nav-link.active= tab_title
|
||||
= gl_tabs_nav({ class: 'new-session-tabs gl-border-0' }) do
|
||||
= gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1' }
|
||||
|
|
|
@ -18,6 +18,6 @@
|
|||
"gid_prefix": container_repository_gid_prefix,
|
||||
connection_error: (!!@connection_error).to_s,
|
||||
invalid_path_error: (!!@invalid_path_error).to_s,
|
||||
user_callouts_path: user_callouts_path,
|
||||
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
|
||||
user_callouts_path: callouts_path,
|
||||
user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
|
||||
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } }
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
track_label: 'invite_members_banner',
|
||||
invite_members_path: group_group_members_path(@group),
|
||||
callouts_path: group_callouts_path,
|
||||
callouts_feature_id: UserCalloutsHelper::INVITE_MEMBERS_BANNER,
|
||||
callouts_feature_id: Users::GroupCalloutsHelper::INVITE_MEMBERS_BANNER,
|
||||
group_id: @group.id } }
|
||||
= render 'groups/invite_members_modal', group: @group
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
title: _('Open registration is enabled on your instance.'),
|
||||
variant: :warning,
|
||||
alert_class: 'js-registration-enabled-callout',
|
||||
alert_data: { feature_id: UserCalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: user_callouts_path },
|
||||
alert_data: { feature_id: Users::CalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: callouts_path },
|
||||
close_button_data: { testid: 'close-registration-enabled-callout' } do
|
||||
.gl-alert-body
|
||||
= html_escape(_('%{anchorOpen}Learn more%{anchorClose} about how you can customize / disable registration on your instance.')) % { anchorOpen: "<a href=\"#{help_page_path('user/admin_area/settings/sign_up_restrictions')}\" class=\"gl-link\">".html_safe, anchorClose: '</a>'.html_safe }
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
#js-new-feature-flag{ data: { endpoint: project_feature_flags_path(@project, format: :json),
|
||||
feature_flags_path: project_feature_flags_path(@project),
|
||||
environments_endpoint: search_project_environments_path(@project, format: :json),
|
||||
user_callouts_path: user_callouts_path,
|
||||
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
|
||||
user_callouts_path: callouts_path,
|
||||
user_callout_id: Users::CalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
|
||||
show_user_callout: show_feature_flags_new_version?.to_s,
|
||||
strategy_type_docs_page_path: help_page_path('operations/feature_flags', anchor: 'feature-flag-strategies'),
|
||||
environments_scope_docs_path: help_page_path('ci/environments/index.md', anchor: 'scope-environments-with-specs'),
|
||||
|
|
|
@ -7,4 +7,7 @@
|
|||
|
||||
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
|
||||
|
||||
#js-job-vue-app{ data: jobs_data }
|
||||
- if @build.is_a? ::Ci::Build
|
||||
#js-job-page{ data: jobs_data }
|
||||
- else
|
||||
#js-bridge-page{ data: bridge_data(@build) }
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
|
||||
connection_error: (!!@connection_error).to_s,
|
||||
invalid_path_error: (!!@invalid_path_error).to_s,
|
||||
user_callouts_path: user_callouts_path,
|
||||
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
|
||||
user_callouts_path: callouts_path,
|
||||
user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
|
||||
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } }
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
.gl-display-none.gl-md-display-block{ class: "gl-pt-6! gl-pb-2! #{(container_class unless @no_container)} #{@content_class}" }
|
||||
.js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
|
||||
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
|
||||
callouts_path: user_callouts_path,
|
||||
callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE,
|
||||
callouts_path: callouts_path,
|
||||
callouts_feature_id: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE,
|
||||
track_label: 'home_page' } }
|
||||
|
||||
= render template: 'dashboard/projects/index'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- callout_data = { uid: "callout_feature_#{feature_name}_dismissed", feature_id: feature_name, dismiss_endpoint: user_callouts_path }
|
||||
- callout_data = { uid: "callout_feature_#{feature_name}_dismissed", feature_id: feature_name, dismiss_endpoint: callouts_path }
|
||||
- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil)
|
||||
|
||||
.flash-container.flash-container-page.user-callout{ data: callout_data }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
= render 'shared/global_alert',
|
||||
variant: :warning,
|
||||
alert_class: 'js-recovery-settings-callout',
|
||||
alert_data: { feature_id: UserCalloutsHelper::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, dismiss_endpoint: user_callouts_path, defer_links: 'true' },
|
||||
alert_data: { feature_id: Users::CalloutsHelper::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, dismiss_endpoint: callouts_path, defer_links: 'true' },
|
||||
close_button_data: { testid: 'close-account-recovery-regular-check-callout' } do
|
||||
.gl-alert-body
|
||||
= s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.')
|
||||
|
|
|
@ -3,24 +3,20 @@
|
|||
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
|
||||
.fade-left= sprite_icon('chevron-lg-left', size: 12)
|
||||
.fade-right= sprite_icon('chevron-lg-right', size: 12)
|
||||
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
|
||||
%li.nav-item
|
||||
= link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
|
||||
= gl_tabs_nav({ class: %w[scrolling-tabs js-milestone-tabs] }) do
|
||||
= gl_tab_link_to '#tab-issues', item_active: true, data: { endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
|
||||
= _('Issues')
|
||||
%span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
|
||||
= gl_tab_counter_badge milestone.issues_visible_to_user(current_user).size
|
||||
- if milestone.merge_requests_enabled?
|
||||
%li.nav-item
|
||||
= link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests', show_project_name: show_project_name) } do
|
||||
= gl_tab_link_to '#tab-merge-requests', data: { endpoint: milestone_tab_path(milestone, 'merge_requests', show_project_name: show_project_name) } do
|
||||
= _('Merge requests')
|
||||
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
|
||||
%li.nav-item
|
||||
= link_to '#tab-participants', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'participants') } do
|
||||
= gl_tab_counter_badge milestone.merge_requests_visible_to_user(current_user).size
|
||||
= gl_tab_link_to '#tab-participants', data: { endpoint: milestone_tab_path(milestone, 'participants') } do
|
||||
= _('Participants')
|
||||
%span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count
|
||||
%li.nav-item
|
||||
= link_to '#tab-labels', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'labels') } do
|
||||
= gl_tab_counter_badge milestone.issue_participants_visible_by_user(current_user).count
|
||||
= gl_tab_link_to '#tab-labels', data: { endpoint: milestone_tab_path(milestone, 'labels') } do
|
||||
= _('Labels')
|
||||
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
|
||||
= gl_tab_counter_badge milestone.issue_labels_visible_by_user(current_user).count
|
||||
|
||||
.tab-content.milestone-content
|
||||
.tab-pane.active#tab-issues
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ci_retry_downstream_pipeline
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76115
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347424
|
||||
milestone: '14.16'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345744
|
|||
milestone: '14.6'
|
||||
type: development
|
||||
group: group::project management
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344957
|
|||
milestone: '14.5'
|
||||
type: development
|
||||
group: group::workspace
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
return unless Gitlab.ee?
|
||||
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(Gitlab::Patch::GeoDatabaseTasks)
|
||||
end
|
|
@ -8,11 +8,11 @@ Gitlab.ee do
|
|||
config.geo_database = config_for(:database_geo)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab.ee do
|
||||
if Gitlab::Runtime.sidekiq? && Gitlab::Geo.geo_database_configured?
|
||||
Rails.configuration.geo_database['pool'] = Gitlab::Database.default_pool_size
|
||||
Geo::TrackingBase.establish_connection(Rails.configuration.geo_database)
|
||||
# The Geo::TrackingBase model does not yet use connects_to. So,
|
||||
# this will not properly support geo: from config/databse.yml
|
||||
# file yet. This is ACK of the current state and will be fixed.
|
||||
Geo::TrackingBase.establish_connection(Gitlab::Database.geo_db_config_with_default_pool_size)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,11 +16,11 @@ if configurations = ActiveRecord::Base.configurations.configurations
|
|||
"The `main:` database needs to be defined as a first configuration item instead of `#{configurations.first.name}`."
|
||||
end
|
||||
|
||||
rejected_config_names = configurations.map(&:name).to_set - Gitlab::Database::DATABASE_NAMES
|
||||
rejected_config_names = configurations.map(&:name).to_set - Gitlab::Database.all_database_names
|
||||
if rejected_config_names.any?
|
||||
raise "ERROR: This installation of GitLab uses unsupported database names " \
|
||||
"in 'config/database.yml': #{rejected_config_names.to_a.join(", ")}. The only supported ones are " \
|
||||
"#{Gitlab::Database::DATABASE_NAMES.join(", ")}."
|
||||
"#{Gitlab::Database.all_database_names.join(", ")}."
|
||||
end
|
||||
|
||||
replicas_config_names = configurations.select(&:replica?).map(&:name)
|
||||
|
|
|
@ -145,7 +145,7 @@ Rails.application.routes.draw do
|
|||
get 'acme-challenge/' => 'acme_challenges#show'
|
||||
|
||||
# UserCallouts
|
||||
resources :user_callouts, only: [:create]
|
||||
resources :user_callouts, controller: 'users/callouts', only: [:create] # remove after 14.6 2021-12-22 to handle mixed deployments
|
||||
|
||||
scope :ide, as: :ide, format: false do
|
||||
get '/', to: 'ide#index'
|
||||
|
|
|
@ -61,6 +61,7 @@ scope '-/users', module: :users do
|
|||
post :decline, on: :member
|
||||
end
|
||||
|
||||
resources :callouts, only: [:create]
|
||||
resources :group_callouts, only: [:create]
|
||||
end
|
||||
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
|
||||
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
Runner REST API will not return `paused` as a status in GitLab 15.0.
|
||||
The GitLab Runner REST and GraphQL API endpoints will not return `paused` or `active` as a status in GitLab 15.0.
|
||||
|
||||
Paused runners' status will only relate to runner contact status, such as:
|
||||
`online`, `offline`, or `not_connected`. Status `paused` will not appear when the runner is
|
||||
not active.
|
||||
A runner's status will only relate to runner contact status, such as:
|
||||
`online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
|
||||
|
||||
When checking if a runner is `paused`, API users are advised to check the boolean attribute
|
||||
`active` to be `false` instead.
|
||||
`active` to be `false` instead. When checking if a runner is `active`, check if `active` is `true`.
|
||||
stage: Verify
|
||||
tiers: [Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
- name: "Deprecation of Runner status `not_connected` API value"
|
||||
announcement_milestone: "14.6" # The milestone when this feature was first announced as deprecated.
|
||||
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
The GitLab Runner REST and GraphQL [API](https://docs.gitlab.com/ee/api/runners.html#runners-api) endpoints
|
||||
will return `never_contacted` instead of `not_connected` as the status values in 15.0.
|
||||
|
||||
Runners that have never contacted the GitLab instance will also return `stale` if created more than 3 months ago.
|
||||
stage: Verify
|
||||
tiers: [Core, Premium, Ultimate]
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347305
|
||||
documentation_url: https://docs.gitlab.com/ee/api/runners.html
|
||||
announcement_date: "2021-12-22"
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateLfsObjectStates < Gitlab::Database::Migration[1.0]
|
||||
VERIFICATION_STATE_INDEX_NAME = "index_lfs_object_states_on_verification_state"
|
||||
PENDING_VERIFICATION_INDEX_NAME = "index_lfs_object_states_pending_verification"
|
||||
FAILED_VERIFICATION_INDEX_NAME = "index_lfs_object_states_failed_verification"
|
||||
NEEDS_VERIFICATION_INDEX_NAME = "index_lfs_object_states_needs_verification"
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
create_table :lfs_object_states, id: false do |t|
|
||||
t.datetime_with_timezone :verification_started_at
|
||||
t.datetime_with_timezone :verification_retry_at
|
||||
t.datetime_with_timezone :verified_at
|
||||
t.references :lfs_object, primary_key: true, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.integer :verification_state, default: 0, limit: 2, null: false
|
||||
t.integer :verification_retry_count, limit: 2
|
||||
t.binary :verification_checksum, using: 'verification_checksum::bytea'
|
||||
t.text :verification_failure, limit: 255
|
||||
|
||||
t.index :verification_state, name: VERIFICATION_STATE_INDEX_NAME
|
||||
t.index :verified_at, where: "(verification_state = 0)", order: { verified_at: 'ASC NULLS FIRST' }, name: PENDING_VERIFICATION_INDEX_NAME
|
||||
t.index :verification_retry_at, where: "(verification_state = 3)", order: { verification_retry_at: 'ASC NULLS FIRST' }, name: FAILED_VERIFICATION_INDEX_NAME
|
||||
t.index :verification_state, where: "(verification_state = 0 OR verification_state = 3)", name: NEEDS_VERIFICATION_INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :lfs_object_states
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
0d27ca1250d10b8915fa4523707044f9a8c2372110537f5639a1811aeb0858b8
|
|
@ -15769,6 +15769,27 @@ CREATE SEQUENCE lfs_file_locks_id_seq
|
|||
|
||||
ALTER SEQUENCE lfs_file_locks_id_seq OWNED BY lfs_file_locks.id;
|
||||
|
||||
CREATE TABLE lfs_object_states (
|
||||
verification_started_at timestamp with time zone,
|
||||
verification_retry_at timestamp with time zone,
|
||||
verified_at timestamp with time zone,
|
||||
lfs_object_id bigint NOT NULL,
|
||||
verification_state smallint DEFAULT 0 NOT NULL,
|
||||
verification_retry_count smallint,
|
||||
verification_checksum bytea,
|
||||
verification_failure text,
|
||||
CONSTRAINT check_efe45a8ab3 CHECK ((char_length(verification_failure) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE lfs_object_states_lfs_object_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE lfs_object_states_lfs_object_id_seq OWNED BY lfs_object_states.lfs_object_id;
|
||||
|
||||
CREATE TABLE lfs_objects (
|
||||
id integer NOT NULL,
|
||||
oid character varying NOT NULL,
|
||||
|
@ -21795,6 +21816,8 @@ ALTER TABLE ONLY ldap_group_links ALTER COLUMN id SET DEFAULT nextval('ldap_grou
|
|||
|
||||
ALTER TABLE ONLY lfs_file_locks ALTER COLUMN id SET DEFAULT nextval('lfs_file_locks_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY lfs_object_states ALTER COLUMN lfs_object_id SET DEFAULT nextval('lfs_object_states_lfs_object_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY lfs_objects ALTER COLUMN id SET DEFAULT nextval('lfs_objects_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY lfs_objects_projects ALTER COLUMN id SET DEFAULT nextval('lfs_objects_projects_id_seq'::regclass);
|
||||
|
@ -23513,6 +23536,9 @@ ALTER TABLE ONLY ldap_group_links
|
|||
ALTER TABLE ONLY lfs_file_locks
|
||||
ADD CONSTRAINT lfs_file_locks_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY lfs_object_states
|
||||
ADD CONSTRAINT lfs_object_states_pkey PRIMARY KEY (lfs_object_id);
|
||||
|
||||
ALTER TABLE ONLY lfs_objects
|
||||
ADD CONSTRAINT lfs_objects_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -26529,6 +26555,16 @@ CREATE UNIQUE INDEX index_lfs_file_locks_on_project_id_and_path ON lfs_file_lock
|
|||
|
||||
CREATE INDEX index_lfs_file_locks_on_user_id ON lfs_file_locks USING btree (user_id);
|
||||
|
||||
CREATE INDEX index_lfs_object_states_failed_verification ON lfs_object_states USING btree (verification_retry_at NULLS FIRST) WHERE (verification_state = 3);
|
||||
|
||||
CREATE INDEX index_lfs_object_states_needs_verification ON lfs_object_states USING btree (verification_state) WHERE ((verification_state = 0) OR (verification_state = 3));
|
||||
|
||||
CREATE INDEX index_lfs_object_states_on_lfs_object_id ON lfs_object_states USING btree (lfs_object_id);
|
||||
|
||||
CREATE INDEX index_lfs_object_states_on_verification_state ON lfs_object_states USING btree (verification_state);
|
||||
|
||||
CREATE INDEX index_lfs_object_states_pending_verification ON lfs_object_states USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0);
|
||||
|
||||
CREATE INDEX index_lfs_objects_on_file_store ON lfs_objects USING btree (file_store);
|
||||
|
||||
CREATE UNIQUE INDEX index_lfs_objects_on_oid ON lfs_objects USING btree (oid);
|
||||
|
@ -30333,6 +30369,9 @@ ALTER TABLE ONLY description_versions
|
|||
ALTER TABLE ONLY clusters_kubernetes_namespaces
|
||||
ADD CONSTRAINT fk_rails_40cc7ccbc3 FOREIGN KEY (cluster_project_id) REFERENCES cluster_projects(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY lfs_object_states
|
||||
ADD CONSTRAINT fk_rails_4188448cd5 FOREIGN KEY (lfs_object_id) REFERENCES lfs_objects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY geo_node_namespace_links
|
||||
ADD CONSTRAINT fk_rails_41ff5fb854 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ verification methods:
|
|||
| Git | Group wiki repository | Geo with Gitaly | _Not implemented_ |
|
||||
| Blobs | User uploads _(file system)_ | Geo with API | _Not implemented_ |
|
||||
| Blobs | User uploads _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
| Blobs | LFS objects _(file system)_ | Geo with API | _Not implemented_ |
|
||||
| Blobs | LFS objects _(file system)_ | Geo with API | SHA256 checksum |
|
||||
| Blobs | LFS objects _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
| Blobs | CI job artifacts _(file system)_ | Geo with API | _Not implemented_ |
|
||||
| Blobs | CI job artifacts _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
|
@ -190,7 +190,7 @@ successfully, you must replicate their data using some other means.
|
|||
|[Project wiki repository](../../../user/project/wiki/) | **Yes** (10.2) | **Yes** (10.7) | No | |
|
||||
|[Group wiki repository](../../../user/project/wiki/group.md) | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | No | No | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. |
|
||||
|[Uploads](../../uploads.md) | **Yes** (10.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | No | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. |
|
||||
|[LFS objects](../../lfs/index.md) | **Yes** (10.2) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br><br>Behind feature flag `geo_lfs_object_replication`, enabled by default. |
|
||||
|[LFS objects](../../lfs/index.md) | **Yes** (10.2) | **Yes**(14.6) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br /><br />Replication is behind the feature flag `geo_lfs_object_replication`, enabled by default. Verification is under development behind the feature flag `geo_lfs_object_verification` introduced in 14.6. |
|
||||
|[Personal snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | No | |
|
||||
|[Project snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | No | |
|
||||
|[CI job artifacts](../../../ci/pipelines/job_artifacts.md) | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. Job logs also verified on transfer. |
|
||||
|
|
|
@ -187,16 +187,25 @@ configuration option in `gitlab.yml`. These metrics are served from the
|
|||
| `geo_repositories` | Gauge | 10.2 | Total number of repositories available on primary | `url` |
|
||||
| `geo_repositories_synced` | Gauge | 10.2 | Number of repositories synced on secondary | `url` |
|
||||
| `geo_repositories_failed` | Gauge | 10.2 | Number of repositories failed to sync on secondary | `url` |
|
||||
| `geo_lfs_objects` | Gauge | 10.2 | Total number of LFS objects available on primary | `url` |
|
||||
| `geo_lfs_objects_synced` | Gauge | 10.2 | Number of LFS objects synced on secondary | `url` |
|
||||
| `geo_lfs_objects_failed` | Gauge | 10.2 | Number of LFS objects failed to sync on secondary | `url` |
|
||||
| `geo_lfs_objects` | Gauge | 10.2 | Number of LFS objects on primary | `url` |
|
||||
| `geo_lfs_objects_checksummed` | Gauge | 14.6 | Number of LFS objects checksummed successfully on primary | `url` |
|
||||
| `geo_lfs_objects_checksum_failed` | Gauge | 14.6 | Number of LFS objects failed to calculate the checksum on primary | `url` |
|
||||
| `geo_lfs_objects_checksum_total` | Gauge | 14.6 | Number of LFS objects tried to checksum on primary | `url` |
|
||||
| `geo_lfs_objects_synced` | Gauge | 10.2 | Number of syncable LFS objects synced on secondary | `url` |
|
||||
| `geo_lfs_objects_failed` | Gauge | 10.2 | Number of syncable LFS objects failed to sync on secondary | `url` |
|
||||
| `geo_lfs_objects_registry` | Gauge | 14.6 | Number of LFS objects in the registry | `url` |
|
||||
| `geo_lfs_objects_verified` | Gauge | 14.6 | Number of LFS objects verified on secondary | `url` |
|
||||
| `geo_lfs_objects_verification_failed` | Gauge | 14.6 | Number of LFS objects' verifications failed on secondary | `url` |
|
||||
| `geo_lfs_objects_verification_total` | Gauge | 14.6 | Number of LFS objects' verifications tried on secondary | `url` |LFS objects failed to sync on secondary | `url` |
|
||||
| `geo_attachments` | Gauge | 10.2 | Total number of file attachments available on primary | `url` |
|
||||
| `geo_attachments_synced` | Gauge | 10.2 | Number of attachments synced on secondary | `url` |
|
||||
| `geo_attachments_failed` | Gauge | 10.2 | Number of attachments failed to sync on secondary | `url` |
|
||||
| `geo_last_event_id` | Gauge | 10.2 | Database ID of the latest event log entry on the primary | `url` |
|
||||
| `geo_last_event_timestamp` | Gauge | 10.2 | UNIX timestamp of the latest event log entry on the primary | `url` |
|
||||
| `geo_cursor_last_event_id` | Gauge | 10.2 | Last database ID of the event log processed by the secondary | `url` |
|
||||
| `geo_cursor_last_event_timestamp` | Gauge | 10.2 | Last UNIX timestamp of the event log processed by the secondary | `url` |
|
||||
| `geo_status_failed_total` | Counter | 10.2 | Number of times retrieving the status from the Geo Node failed | `url` |
|
||||
| `geo_last_successful_status_check_timestamp` | Gauge | 10.2 | Last timestamp when the status was successfully updated | `url` |
|
||||
| `geo_lfs_objects_synced_missing_on_primary` | Gauge | 10.7 | Number of LFS objects marked as synced due to the file missing on the primary | `url` |
|
||||
| `geo_job_artifacts_synced_missing_on_primary` | Gauge | 10.7 | Number of job artifacts marked as synced due to the file missing on the primary | `url` |
|
||||
| `geo_repositories_checksummed` | Gauge | 10.7 | Number of repositories checksummed on primary | `url` |
|
||||
| `geo_repositories_checksum_failed` | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | `url` |
|
||||
|
|
|
@ -307,11 +307,18 @@ Example response:
|
|||
"health_status": "Healthy",
|
||||
"missing_oauth_application": false,
|
||||
"db_replication_lag_seconds": null,
|
||||
"lfs_objects_count": 0,
|
||||
"lfs_objects_count": 5,
|
||||
"lfs_objects_checksum_total_count": 5,
|
||||
"lfs_objects_checksummed_count": 5,
|
||||
"lfs_objects_checksum_failed_count": 0,
|
||||
"lfs_objects_synced_count": null,
|
||||
"lfs_objects_failed_count": null,
|
||||
"lfs_objects_synced_missing_on_primary_count": 0,
|
||||
"lfs_objects_registry_count": null,
|
||||
"lfs_objects_verification_total_count": null,
|
||||
"lfs_objects_verified_count": null,
|
||||
"lfs_objects_verification_failed_count": null,
|
||||
"lfs_objects_synced_in_percentage": "0.00%",
|
||||
"lfs_objects_verified_in_percentage": "0.00%",
|
||||
"job_artifacts_count": 2,
|
||||
"job_artifacts_synced_count": null,
|
||||
"job_artifacts_failed_count": null,
|
||||
|
@ -468,11 +475,18 @@ Example response:
|
|||
"health_status": "Healthy",
|
||||
"missing_oauth_application": false,
|
||||
"db_replication_lag_seconds": 0,
|
||||
"lfs_objects_count": 0,
|
||||
"lfs_objects_synced_count": 0,
|
||||
"lfs_objects_failed_count": 0,
|
||||
"lfs_objects_synced_missing_on_primary_count": 0,
|
||||
"lfs_objects_count": 5,
|
||||
"lfs_objects_checksum_total_count": 5,
|
||||
"lfs_objects_checksummed_count": 5,
|
||||
"lfs_objects_checksum_failed_count": 0,
|
||||
"lfs_objects_synced_count": null,
|
||||
"lfs_objects_failed_count": null,
|
||||
"lfs_objects_registry_count": null,
|
||||
"lfs_objects_verification_total_count": null,
|
||||
"lfs_objects_verified_count": null,
|
||||
"lfs_objects_verification_failed_count": null,
|
||||
"lfs_objects_synced_in_percentage": "0.00%",
|
||||
"lfs_objects_verified_in_percentage": "0.00%",
|
||||
"job_artifacts_count": 2,
|
||||
"job_artifacts_synced_count": 1,
|
||||
"job_artifacts_failed_count": 1,
|
||||
|
@ -633,11 +647,18 @@ Example response:
|
|||
"health_status": "Healthy",
|
||||
"missing_oauth_application": false,
|
||||
"db_replication_lag_seconds": 0,
|
||||
"lfs_objects_count": 0,
|
||||
"lfs_objects_synced_count": 0,
|
||||
"lfs_objects_failed_count": 0,
|
||||
"lfs_objects_synced_missing_on_primary_count": 0,
|
||||
"lfs_objects_count": 5,
|
||||
"lfs_objects_checksum_total_count": 5,
|
||||
"lfs_objects_checksummed_count": 5,
|
||||
"lfs_objects_checksum_failed_count": 0,
|
||||
"lfs_objects_synced_count": null,
|
||||
"lfs_objects_failed_count": null,
|
||||
"lfs_objects_registry_count": null,
|
||||
"lfs_objects_verification_total_count": null,
|
||||
"lfs_objects_verified_count": null,
|
||||
"lfs_objects_verification_failed_count": null,
|
||||
"lfs_objects_synced_in_percentage": "0.00%",
|
||||
"lfs_objects_verified_in_percentage": "0.00%",
|
||||
"job_artifacts_count": 2,
|
||||
"job_artifacts_synced_count": 1,
|
||||
"job_artifacts_failed_count": 1,
|
||||
|
|
|
@ -15975,7 +15975,8 @@ Values for sorting runners.
|
|||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="cirunnerstatusactive"></a>`ACTIVE` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
|
||||
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` **{warning-solid}** | **Deprecated** in 14.6. This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period of no contact. |
|
||||
| <a id="cirunnerstatusnever_contacted"></a>`NEVER_CONTACTED` | Runner that has never contacted this instance. Set legacyMode to null to utilize this value. Will replace NOT_CONNECTED starting in 15.0. |
|
||||
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` **{warning-solid}** | **Deprecated** in 14.6. Use NEVER_CONTACTED instead. NEVER_CONTACTED will have a slightly different scope starting in 15.0, with STALE being returned instead after 3 months of no contact. |
|
||||
| <a id="cirunnerstatusoffline"></a>`OFFLINE` **{warning-solid}** | **Deprecated** in 14.6. This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period offline. |
|
||||
| <a id="cirunnerstatusonline"></a>`ONLINE` | Runner that contacted this instance within the last 2 hours. |
|
||||
| <a id="cirunnerstatuspaused"></a>`PAUSED` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
|
||||
|
|
|
@ -124,6 +124,15 @@ Long term service and support (LTSS) for SUSE Linux Enterprise Server (SLES) 12
|
|||
|
||||
Announced: 2021-11-22
|
||||
|
||||
### Deprecation of Runner status `not_connected` API value
|
||||
|
||||
The GitLab Runner REST and GraphQL [API](https://docs.gitlab.com/ee/api/runners.html#runners-api) endpoints
|
||||
will return `never_contacted` instead of `not_connected` as the status values in 15.0.
|
||||
|
||||
Runners that have never contacted the GitLab instance will also return `stale` if created more than 3 months ago.
|
||||
|
||||
Announced: 2021-12-22
|
||||
|
||||
### Deprecation of bundler-audit Dependency Scanning tool
|
||||
|
||||
As of 14.6 bundler-audit is being deprecated from Dependency Scanning. It will continue to be in our CI/CD template while deprecated. We are removing bundler-audit from Dependency Scanning on May 22, 2022 in 15.0. After this removal Ruby scanning functionality will not be affected as it is still being covered by Gemnasium.
|
||||
|
@ -200,14 +209,13 @@ Announced: 2021-11-22
|
|||
|
||||
### REST API Runner will not contain `paused`
|
||||
|
||||
Runner REST API will not return `paused` as a status in GitLab 15.0.
|
||||
The GitLab Runner REST and GraphQL API endpoints will not return `paused` or `active` as a status in GitLab 15.0.
|
||||
|
||||
Paused runners' status will only relate to runner contact status, such as:
|
||||
`online`, `offline`, or `not_connected`. Status `paused` will not appear when the runner is
|
||||
not active.
|
||||
A runner's status will only relate to runner contact status, such as:
|
||||
`online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
|
||||
|
||||
When checking if a runner is `paused`, API users are advised to check the boolean attribute
|
||||
`active` to be `false` instead.
|
||||
`active` to be `false` instead. When checking if a runner is `active`, check if `active` is `true`.
|
||||
|
||||
Announced: 2021-11-22
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ module Banzai
|
|||
XPATH_SECTION_OLD = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION_OLD).freeze
|
||||
|
||||
def call
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
# Sanitization stripped off the section class - add it back in
|
||||
return doc unless section_node = doc.at_xpath(XPATH_SECTION)
|
||||
|
||||
|
@ -52,26 +52,26 @@ module Banzai
|
|||
rand_suffix = "-#{random_number}"
|
||||
modified_footnotes = {}
|
||||
|
||||
xpath_footnote = if Feature.enabled?(:use_cmark_renderer)
|
||||
xpath_footnote = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
XPATH_FOOTNOTE
|
||||
else
|
||||
Gitlab::Utils::Nokogiri.css_to_xpath('sup > a[id]')
|
||||
end
|
||||
|
||||
doc.xpath(xpath_footnote).each do |link_node|
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
|
||||
ref_num.gsub!(/[[:punct:]]/, '\\\\\&')
|
||||
else
|
||||
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX_OLD)
|
||||
end
|
||||
|
||||
css = Feature.enabled?(:use_cmark_renderer) ? "section[data-footnotes] li[id=#{fn_id(ref_num)}]" : "li[id=#{fn_id(ref_num)}]"
|
||||
css = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? "section[data-footnotes] li[id=#{fn_id(ref_num)}]" : "li[id=#{fn_id(ref_num)}]"
|
||||
node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath(css)
|
||||
footnote_node = doc.at_xpath(node_xpath)
|
||||
|
||||
if footnote_node || modified_footnotes[ref_num]
|
||||
next if Feature.disabled?(:use_cmark_renderer) && !INTEGER_PATTERN.match?(ref_num)
|
||||
next if Feature.disabled?(:use_cmark_renderer, default_enabled: :yaml) && !INTEGER_PATTERN.match?(ref_num)
|
||||
|
||||
link_node[:href] += rand_suffix
|
||||
link_node[:id] += rand_suffix
|
||||
|
@ -103,12 +103,12 @@ module Banzai
|
|||
end
|
||||
|
||||
def fn_id(num)
|
||||
prefix = Feature.enabled?(:use_cmark_renderer) ? FOOTNOTE_ID_PREFIX : FOOTNOTE_ID_PREFIX_OLD
|
||||
prefix = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? FOOTNOTE_ID_PREFIX : FOOTNOTE_ID_PREFIX_OLD
|
||||
"#{prefix}#{num}"
|
||||
end
|
||||
|
||||
def fnref_id(num)
|
||||
prefix = Feature.enabled?(:use_cmark_renderer) ? FOOTNOTE_LINK_ID_PREFIX : FOOTNOTE_LINK_ID_PREFIX_OLD
|
||||
prefix = Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? FOOTNOTE_LINK_ID_PREFIX : FOOTNOTE_LINK_ID_PREFIX_OLD
|
||||
"#{prefix}#{num}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,11 +42,11 @@ module Banzai
|
|||
|
||||
def initialize(context)
|
||||
@context = context
|
||||
@renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer)
|
||||
@renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
def render(text)
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
CommonMarker.render_html(text, render_options, extensions)
|
||||
else
|
||||
doc = CommonMarker.render_doc(text, PARSE_OPTIONS, extensions)
|
||||
|
@ -58,7 +58,7 @@ module Banzai
|
|||
private
|
||||
|
||||
def extensions
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
EXTENSIONS
|
||||
else
|
||||
EXTENSIONS + [
|
||||
|
@ -72,7 +72,7 @@ module Banzai
|
|||
end
|
||||
|
||||
def render_options_no_sourcepos
|
||||
Feature.enabled?(:use_cmark_renderer) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
|
||||
Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
|
||||
end
|
||||
|
||||
def render_options_sourcepos
|
||||
|
|
|
@ -42,7 +42,7 @@ module Banzai
|
|||
private
|
||||
|
||||
def lang_tag
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
Gitlab::Utils::Nokogiri.css_to_xpath('pre')
|
||||
else
|
||||
Gitlab::Utils::Nokogiri.css_to_xpath('code')
|
||||
|
|
|
@ -26,7 +26,7 @@ module Banzai
|
|||
|
||||
def lang_tag
|
||||
@lang_tag ||=
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
Gitlab::Utils::Nokogiri.css_to_xpath('pre[lang="plantuml"] > code').freeze
|
||||
else
|
||||
Gitlab::Utils::Nokogiri.css_to_xpath('pre > code[lang="plantuml"]').freeze
|
||||
|
|
|
@ -28,7 +28,7 @@ module Banzai
|
|||
allowlist[:attributes]['li'] = %w[id]
|
||||
allowlist[:transformers].push(self.class.remove_non_footnote_ids)
|
||||
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
# Allow section elements with data-footnotes attribute
|
||||
allowlist[:elements].push('section')
|
||||
allowlist[:attributes]['section'] = %w(data-footnotes)
|
||||
|
@ -61,7 +61,7 @@ module Banzai
|
|||
return unless node.name == 'a' || node.name == 'li'
|
||||
return unless node.has_attribute?('id')
|
||||
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
return if node.name == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
|
||||
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
|
||||
else
|
||||
|
|
|
@ -70,7 +70,7 @@ module Banzai
|
|||
private
|
||||
|
||||
def parse_lang_params(node)
|
||||
node = node.parent if Feature.enabled?(:use_cmark_renderer)
|
||||
node = node.parent if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
|
||||
# Commonmarker's FULL_INFO_STRING render option works with the space delimiter.
|
||||
# But the current behavior of GitLab's markdown renderer is different - it grabs everything as the single
|
||||
|
@ -92,7 +92,7 @@ module Banzai
|
|||
|
||||
language, language_params = language.split(LANG_PARAMS_DELIMITER, 2)
|
||||
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
language_params = [node.attr('data-meta'), language_params].compact.join(' ')
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ module Gitlab
|
|||
register_for 'gitlab-html-pipeline'
|
||||
|
||||
def format(node, lang, opts)
|
||||
if Feature.enabled?(:use_cmark_renderer)
|
||||
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
|
||||
%(<pre #{lang ? %[lang="#{lang}"] : ''}><code>#{node.content}</code></pre>)
|
||||
else
|
||||
%(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>)
|
||||
|
|
|
@ -16,8 +16,12 @@ module Gitlab
|
|||
def details_path
|
||||
return unless can?(user, :read_pipeline, downstream_pipeline)
|
||||
|
||||
if Feature.enabled?(:ci_retry_downstream_pipeline, subject.project, default_enabled: :yaml)
|
||||
project_job_path(subject.project, subject)
|
||||
else
|
||||
project_pipeline_path(downstream_project, downstream_pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
def has_action?
|
||||
false
|
||||
|
|
|
@ -72,6 +72,10 @@ module Gitlab
|
|||
}.with_indifferent_access.freeze
|
||||
end
|
||||
|
||||
def self.all_database_names
|
||||
DATABASE_NAMES
|
||||
end
|
||||
|
||||
# We configure the database connection pool size automatically based on the
|
||||
# configured concurrency. We also add some headroom, to make sure we don't
|
||||
# run out of connections when more threads besides the 'user-facing' ones
|
||||
|
|
|
@ -287,6 +287,7 @@ ldap_group_links: :gitlab_main
|
|||
lfs_file_locks: :gitlab_main
|
||||
lfs_objects: :gitlab_main
|
||||
lfs_objects_projects: :gitlab_main
|
||||
lfs_object_states: :gitlab_main
|
||||
licenses: :gitlab_main
|
||||
lists: :gitlab_main
|
||||
list_user_preferences: :gitlab_main
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
module Gitlab
|
||||
module Diff
|
||||
module CustomDiff
|
||||
class << self
|
||||
def preprocess_before_diff(path, old_blob, new_blob)
|
||||
return unless path.ends_with? '.ipynb'
|
||||
|
||||
transformed_diff(old_blob&.data, new_blob&.data)&.tap do
|
||||
transformed_for_diff(new_blob, old_blob)
|
||||
Gitlab::AppLogger.info({ message: 'IPYNB_DIFF_GENERATED' })
|
||||
end
|
||||
rescue IpynbDiff::InvalidNotebookError => e
|
||||
Gitlab::ErrorTracking.log_exception(e)
|
||||
nil
|
||||
end
|
||||
|
||||
def transformed_diff(before, after)
|
||||
transformed_diff = IpynbDiff.diff(before, after,
|
||||
diff_opts: { context: 5, include_diff_info: true },
|
||||
transform_options: { cell_decorator: :percent },
|
||||
raise_if_invalid_notebook: true)
|
||||
strip_diff_frontmatter(transformed_diff)
|
||||
end
|
||||
|
||||
def transformed_blob_language(blob)
|
||||
'md' if transformed_for_diff?(blob)
|
||||
end
|
||||
|
||||
def transformed_blob_data(blob)
|
||||
if transformed_for_diff?(blob)
|
||||
IpynbDiff.transform(blob.data,
|
||||
raise_errors: true,
|
||||
options: { include_metadata: false, cell_decorator: :percent })
|
||||
end
|
||||
end
|
||||
|
||||
def strip_diff_frontmatter(diff_content)
|
||||
diff_content.scan(/.*\n/)[2..-1]&.join('') if diff_content.present?
|
||||
end
|
||||
|
||||
def blobs_with_transformed_diffs
|
||||
@blobs_with_transformed_diffs ||= {}
|
||||
end
|
||||
|
||||
def transformed_for_diff?(blob)
|
||||
blobs_with_transformed_diffs[blob]
|
||||
end
|
||||
|
||||
def transformed_for_diff(*blobs)
|
||||
blobs.each do |b|
|
||||
blobs_with_transformed_diffs[b] = true if b
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,7 +44,11 @@ module Gitlab
|
|||
new_blob_lazy
|
||||
old_blob_lazy
|
||||
|
||||
preprocess_before_diff(diff) if Feature.enabled?(:jupyter_clean_diffs, repository.project, default_enabled: true)
|
||||
diff.diff = Gitlab::Diff::CustomDiff.preprocess_before_diff(diff.new_path, old_blob_lazy, new_blob_lazy) || diff.diff if use_custom_diff?
|
||||
end
|
||||
|
||||
def use_custom_diff?
|
||||
strong_memoize(:_custom_diff_enabled) { Feature.enabled?(:jupyter_clean_diffs, repository.project, default_enabled: true) }
|
||||
end
|
||||
|
||||
def position(position_marker, position_type: :text)
|
||||
|
@ -450,33 +454,6 @@ module Gitlab
|
|||
find_renderable_viewer_class(classes)
|
||||
end
|
||||
|
||||
def preprocess_before_diff(diff)
|
||||
return unless diff.new_path.ends_with? '.ipynb'
|
||||
|
||||
from = old_blob_lazy&.data
|
||||
to = new_blob_lazy&.data
|
||||
|
||||
transformed_diff = IpynbDiff.diff(from, to,
|
||||
diff_opts: { context: 5, include_diff_info: true },
|
||||
transform_options: { cell_decorator: :percent },
|
||||
raise_if_invalid_notebook: true)
|
||||
new_diff = strip_diff_frontmatter(transformed_diff)
|
||||
|
||||
if new_diff
|
||||
diff.diff = new_diff
|
||||
new_blob_lazy.transformed_for_diff = true if new_blob_lazy
|
||||
old_blob_lazy.transformed_for_diff = true if old_blob_lazy
|
||||
end
|
||||
|
||||
Gitlab::AppLogger.info({ message: new_diff ? 'IPYNB_DIFF_GENERATED' : 'IPYNB_DIFF_NIL' })
|
||||
rescue IpynbDiff::InvalidNotebookError => e
|
||||
Gitlab::ErrorTracking.log_exception(e)
|
||||
end
|
||||
|
||||
def strip_diff_frontmatter(diff_content)
|
||||
diff_content.scan(/.*\n/)[2..-1]&.join('') if diff_content.present?
|
||||
end
|
||||
|
||||
def alternate_viewer_class
|
||||
return unless viewer.instance_of?(DiffViewer::Renamed)
|
||||
|
||||
|
|
|
@ -153,8 +153,6 @@ module Gitlab
|
|||
|
||||
blob.load_all_data!
|
||||
|
||||
return blob.present.highlight_transformed.lines if Feature.enabled?(:jupyter_clean_diffs, @project, default_enabled: true)
|
||||
|
||||
blob.present.highlight.lines
|
||||
end
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ module Gitlab
|
|||
LFS_POINTER_MIN_SIZE = 120.bytes
|
||||
LFS_POINTER_MAX_SIZE = 200.bytes
|
||||
|
||||
attr_accessor :size, :mode, :id, :commit_id, :loaded_size, :binary, :transformed_for_diff
|
||||
attr_accessor :size, :mode, :id, :commit_id, :loaded_size, :binary
|
||||
attr_writer :name, :path, :data
|
||||
|
||||
def self.gitlab_blob_truncated_true
|
||||
|
@ -127,7 +127,6 @@ module Gitlab
|
|||
# Retain the actual size before it is encoded
|
||||
@loaded_size = @data.bytesize if @data
|
||||
@loaded_all_data = @loaded_size == size
|
||||
@transformed_for_diff = false
|
||||
|
||||
record_metric_blob_size
|
||||
record_metric_truncated(truncated?)
|
||||
|
|
|
@ -31,6 +31,10 @@ module Gitlab
|
|||
else
|
||||
import_with_legacy_diff_note
|
||||
end
|
||||
rescue ::DiffNote::NoteDiffFileCreationError => e
|
||||
Logger.warn(message: e.message, 'error.class': e.class.name)
|
||||
|
||||
import_with_legacy_diff_note
|
||||
rescue ActiveRecord::InvalidForeignKey => e
|
||||
# It's possible the project and the issue have been deleted since
|
||||
# scheduling this job. In this case we'll just skip creating the note
|
||||
|
|
|
@ -29,6 +29,7 @@ module Gitlab
|
|||
project_id: project.id,
|
||||
author_id: author_id,
|
||||
note: note_body,
|
||||
discussion_id: note.discussion_id,
|
||||
system: false,
|
||||
created_at: note.created_at,
|
||||
updated_at: note.updated_at
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue