Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-10 18:14:42 +00:00
parent ecc11e5d60
commit ca5de52835
164 changed files with 2443 additions and 846 deletions

View File

@ -342,8 +342,8 @@ rspec fast_spec_helper minimal:
db:rollback: db:rollback:
extends: .db-job-base extends: .db-job-base
script: script:
- bundle exec rake db:migrate VERSION=20181228175414 - bundle exec rake db:migrate:main VERSION=20181228175414
- bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true - bundle exec rake db:migrate:main SKIP_SCHEMA_VERSION_CHECK=true
db:migrate:reset: db:migrate:reset:
extends: .db-job-base extends: .db-job-base
@ -368,7 +368,7 @@ db:migrate-from-previous-major-version:
- git checkout -f $CI_COMMIT_SHA - git checkout -f $CI_COMMIT_SHA
- SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh - SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh
script: script:
- run_timed_command "bundle exec rake db:migrate" - run_timed_command "bundle exec rake db:migrate:main"
db:check-schema: db:check-schema:
extends: extends:
@ -377,7 +377,7 @@ db:check-schema:
variables: variables:
TAG_TO_CHECKOUT: "v14.4.0" TAG_TO_CHECKOUT: "v14.4.0"
script: script:
- run_timed_command "bundle exec rake db:migrate" - run_timed_command "bundle exec rake db:migrate:main"
- scripts/schema_changed.sh - scripts/schema_changed.sh
- scripts/validate_migration_timestamps - scripts/validate_migration_timestamps
@ -900,8 +900,8 @@ db:rollback geo:
- db:rollback - db:rollback
- .rails:rules:ee-only-migration - .rails:rules:ee-only-migration
script: script:
- bundle exec rake geo:db:migrate VERSION=20170627195211 - bundle exec rake db:migrate:geo VERSION=20170627195211
- bundle exec rake geo:db:migrate - bundle exec rake db:migrate:geo
# EE: default refs (MRs, default branch, schedules) jobs # # EE: default refs (MRs, default branch, schedules) jobs #
################################################## ##################################################

View File

@ -543,7 +543,7 @@ Rails/LexicallyScopedActionFilter:
Rails/LinkToBlank: Rails/LinkToBlank:
Exclude: Exclude:
- 'app/helpers/projects_helper.rb' - 'app/helpers/projects_helper.rb'
- 'ee/app/helpers/ee/user_callouts_helper.rb' - 'ee/app/helpers/ee/users/callouts_helper.rb'
# Offense count: 1 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.

View File

@ -16,7 +16,7 @@ Cop/UserAdmin:
- app/helpers/nav_helper.rb - app/helpers/nav_helper.rb
- app/helpers/projects_helper.rb - app/helpers/projects_helper.rb
- app/helpers/search_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/users_helper.rb
- app/helpers/visibility_level_helper.rb - app/helpers/visibility_level_helper.rb
- app/models/concerns/protected_ref_access.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/dashboard_helper.rb
- ee/app/helpers/ee/import_helper.rb - ee/app/helpers/ee/import_helper.rb
- ee/app/helpers/ee/subscribable_banner_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/license_monitoring_helper.rb
- ee/app/helpers/push_rules_helper.rb - ee/app/helpers/push_rules_helper.rb
- ee/app/models/concerns/ee/protected_ref_access.rb - ee/app/models/concerns/ee/protected_ref_access.rb

View File

@ -32,7 +32,6 @@ Gitlab/NamespacedClass:
- app/controllers/sessions_controller.rb - app/controllers/sessions_controller.rb
- app/controllers/snippets_controller.rb - app/controllers/snippets_controller.rb
- app/controllers/uploads_controller.rb - app/controllers/uploads_controller.rb
- app/controllers/user_callouts_controller.rb
- app/controllers/users_controller.rb - app/controllers/users_controller.rb
- app/controllers/whats_new_controller.rb - app/controllers/whats_new_controller.rb
- app/finders/abuse_reports_finder.rb - app/finders/abuse_reports_finder.rb
@ -351,7 +350,6 @@ Gitlab/NamespacedClass:
- app/models/upload.rb - app/models/upload.rb
- app/models/user.rb - app/models/user.rb
- app/models/user_agent_detail.rb - app/models/user_agent_detail.rb
- app/models/user_callout.rb
- app/models/user_canonical_email.rb - app/models/user_canonical_email.rb
- app/models/user_custom_attribute.rb - app/models/user_custom_attribute.rb
- app/models/user_detail.rb - app/models/user_detail.rb

View File

@ -1 +1 @@
1de88e4247d4b940f843003781cb2bf75582b826 f9af7fbcbfda556c61dcbb2280cda6c6e210cb77

View File

@ -476,7 +476,7 @@ gem 'sshkey', '~> 2.0'
# Required for ED25519 SSH host key support # Required for ED25519 SSH host key support
group :ed25519 do group :ed25519 do
gem 'ed25519', '~> 1.2' gem 'ed25519', '~> 1.2'
gem 'bcrypt_pbkdf', '~> 1.0' gem 'bcrypt_pbkdf', '~> 1.1'
end end
# Spamcheck GRPC protocol definitions # Spamcheck GRPC protocol definitions

View File

@ -137,7 +137,7 @@ GEM
base32 (0.3.2) base32 (0.3.2)
batch-loader (2.0.1) batch-loader (2.0.1)
bcrypt (3.1.16) bcrypt (3.1.16)
bcrypt_pbkdf (1.0.0) bcrypt_pbkdf (1.1.0)
benchmark (0.1.1) benchmark (0.1.1)
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
benchmark-memory (0.1.2) benchmark-memory (0.1.2)
@ -1410,7 +1410,7 @@ DEPENDENCIES
base32 (~> 0.3.0) base32 (~> 0.3.0)
batch-loader (~> 2.0.1) batch-loader (~> 2.0.1)
bcrypt (~> 3.1, >= 3.1.14) bcrypt (~> 3.1, >= 3.1.14)
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.1)
benchmark-ips (~> 2.3.0) benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1) benchmark-memory (~> 0.1)
better_errors (~> 2.9.0) better_errors (~> 2.9.0)

View File

@ -18,5 +18,6 @@ export default {
<span v-for="(token, tokenIndex) in tokens" :key="tokenIndex" :class="token.class">{{ <span v-for="(token, tokenIndex) in tokens" :key="tokenIndex" :class="token.class">{{
token.value token.value
}}</span> }}</span>
<br />
</span> </span>
</template> </template>

View File

@ -170,7 +170,7 @@ export default {
}, },
availableGroupsForImport() { availableGroupsForImport() {
return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && g.flags.isInvalid); return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && !g.flags.isInvalid);
}, },
humanizedTotal() { humanizedTotal() {
@ -521,13 +521,15 @@ export default {
/> />
<template v-else> <template v-else>
<div <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')"> <gl-sprintf :message="__('%{count} selected')">
<template #count> <template #count>
{{ selectedGroupsIds.length }} {{ selectedGroupsIds.length }}
</template> </template>
</gl-sprintf> </gl-sprintf>
</span>
<gl-button <gl-button
category="primary" category="primary"
variant="confirm" variant="confirm"
@ -539,7 +541,7 @@ export default {
</div> </div>
<gl-table <gl-table
ref="table" ref="table"
class="gl-w-full" class="gl-w-full import-table"
data-qa-selector="import_table" data-qa-selector="import_table"
:tbody-tr-class="rowClasses" :tbody-tr-class="rowClasses"
:tbody-tr-attr="qaRowAttributes" :tbody-tr-attr="qaRowAttributes"

View File

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

View File

@ -0,0 +1 @@
export const SIDEBAR_COLLAPSE_BREAKPOINTS = ['xs', 'sm'];

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import { __, s__, sprintf } from '~/locale';
export default { export default {
i18n: { i18n: {
eraseLogButtonLabel: s__('Job|Erase job log'), eraseLogButtonLabel: s__('Job|Erase job log and artifacts'),
scrollToBottomButtonLabel: s__('Job|Scroll to bottom'), scrollToBottomButtonLabel: s__('Job|Scroll to bottom'),
scrollToTopButtonLabel: s__('Job|Scroll to top'), scrollToTopButtonLabel: s__('Job|Scroll to top'),
showRawButtonLabel: s__('Job|Show complete raw'), showRawButtonLabel: s__('Job|Show complete raw'),

View File

@ -1,10 +1,11 @@
import Vue from 'vue'; 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 JobApp from './components/job_app.vue';
import createStore from './store'; import createStore from './store';
export default () => { const initializeJobPage = (element) => {
const element = document.getElementById('js-job-vue-app');
const store = createStore(); const store = createStore();
// Let's start initializing the store (i.e. fetching data) right away // 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);
}
};

View File

@ -1,43 +1,43 @@
import $ from 'jquery';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { sanitize } from '~/lib/dompurify';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { historyPushState } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { GlTabsBehavior, TAB_SHOWN_EVENT } from '~/tabs';
export default class Milestone { export default class Milestone {
constructor() { constructor() {
this.tabsEl = document.querySelector('.js-milestone-tabs');
this.glTabs = new GlTabsBehavior(this.tabsEl);
this.loadedTabs = new WeakSet();
this.bindTabsSwitching(); this.bindTabsSwitching();
this.loadInitialTab(); this.loadInitialTab();
} }
bindTabsSwitching() { bindTabsSwitching() {
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => { this.tabsEl.addEventListener(TAB_SHOWN_EVENT, (event) => {
const $target = $(e.target); const tab = event.target;
const { activeTabPanel } = event.detail;
window.location.hash = $target.attr('href'); historyPushState(tab.getAttribute('href'));
this.loadTab($target); this.loadTab(tab, activeTabPanel);
}); });
} }
loadInitialTab() { loadInitialTab() {
const $target = $(`.js-milestone-tabs a:not(.active)[href="${window.location.hash}"]`); const tab = this.tabsEl.querySelector(`a[href="${window.location.hash}"]`);
this.glTabs.activateTab(tab || this.glTabs.activeTab);
if ($target.length) {
$target.tab('show');
} else {
this.loadTab($('.js-milestone-tabs a.active'));
} }
} loadTab(tab, tabPanel) {
// eslint-disable-next-line class-methods-use-this const { endpoint } = tab.dataset;
loadTab($target) {
const endpoint = $target.data('endpoint');
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) { if (endpoint && !this.loadedTabs.has(tab)) {
axios axios
.get(endpoint) .get(endpoint)
.then(({ data }) => { .then(({ data }) => {
$(tabElId).html(data.html); // eslint-disable-next-line no-param-reassign
$target.addClass('is-loaded'); tabPanel.innerHTML = sanitize(data.html);
this.loadedTabs.add(tab);
}) })
.catch(() => .catch(() =>
createFlash({ createFlash({

View File

@ -4,7 +4,6 @@ import {
GlEmptyState, GlEmptyState,
GlFormGroup, GlFormGroup,
GlFormInputGroup, GlFormInputGroup,
GlLink,
GlSkeletonLoader, GlSkeletonLoader,
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
@ -16,10 +15,7 @@ import {
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION, DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
DEPENDENCY_PROXY_DOCS_PATH, DEPENDENCY_PROXY_DOCS_PATH,
} from '~/packages_and_registries/settings/group/constants'; } from '~/packages_and_registries/settings/group/constants';
import { import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
GRAPHQL_PAGE_SIZE,
ENABLE_DEPENDENCY_PROXY_DOCS_PATH,
} from '~/packages_and_registries/dependency_proxy/constants';
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql'; import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
@ -29,7 +25,6 @@ export default {
GlEmptyState, GlEmptyState,
GlFormGroup, GlFormGroup,
GlFormInputGroup, GlFormInputGroup,
GlLink,
GlSkeletonLoader, GlSkeletonLoader,
GlSprintf, GlSprintf,
ClipboardButton, ClipboardButton,
@ -41,9 +36,6 @@ export default {
proxyNotAvailableText: s__( proxyNotAvailableText: s__(
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.', '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'), proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'),
copyImagePrefixText: s__('DependencyProxy|Copy prefix'), copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'), blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
@ -52,7 +44,6 @@ export default {
}, },
links: { links: {
DEPENDENCY_PROXY_DOCS_PATH, DEPENDENCY_PROXY_DOCS_PATH,
ENABLE_DEPENDENCY_PROXY_DOCS_PATH,
}, },
data() { data() {
return { return {
@ -79,9 +70,7 @@ export default {
}, },
]; ];
}, },
dependencyProxyEnabled() {
return this.group?.dependencyProxySetting?.enabled;
},
queryVariables() { queryVariables() {
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE }; return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
}, },
@ -131,7 +120,7 @@ export default {
<gl-skeleton-loader v-else-if="$apollo.queries.group.loading" /> <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-group :label="$options.i18n.proxyImagePrefix">
<gl-form-input-group <gl-form-input-group
readonly readonly
@ -170,12 +159,5 @@ export default {
:title="$options.i18n.noManifestTitle" :title="$options.i18n.noManifestTitle"
/> />
</div> </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> </div>
</template> </template>

View File

@ -1,7 +1 @@
import { helpPagePath } from '~/helpers/help_page_helper';
export const GRAPHQL_PAGE_SIZE = 20; 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' },
);

View File

@ -9,6 +9,7 @@ import {
I18N_STALE_RUNNER_DESCRIPTION, I18N_STALE_RUNNER_DESCRIPTION,
STATUS_ONLINE, STATUS_ONLINE,
STATUS_NOT_CONNECTED, STATUS_NOT_CONNECTED,
STATUS_NEVER_CONTACTED,
STATUS_OFFLINE, STATUS_OFFLINE,
STATUS_STALE, STATUS_STALE,
} from '../constants'; } from '../constants';
@ -45,6 +46,7 @@ export default {
}), }),
}; };
case STATUS_NOT_CONNECTED: case STATUS_NOT_CONNECTED:
case STATUS_NEVER_CONTACTED:
return { return {
variant: 'muted', variant: 'muted',
label: s__('Runners|not connected'), label: s__('Runners|not connected'),

View File

@ -61,6 +61,7 @@ export const STATUS_PAUSED = 'PAUSED';
export const STATUS_ONLINE = 'ONLINE'; export const STATUS_ONLINE = 'ONLINE';
export const STATUS_NOT_CONNECTED = 'NOT_CONNECTED'; export const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
export const STATUS_NEVER_CONTACTED = 'NEVER_CONTACTED';
export const STATUS_OFFLINE = 'OFFLINE'; export const STATUS_OFFLINE = 'OFFLINE';
export const STATUS_STALE = 'STALE'; export const STATUS_STALE = 'STALE';

View File

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

View File

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

View File

@ -1,12 +1,5 @@
@import 'mixins_and_variables_and_functions'; @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 { .import-jobs-to-col {
width: 39%; width: 39%;
} }
@ -38,3 +31,31 @@
box-shadow: inset 0 0 0 1px var(--gray-200, $gray-200); 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});
}
}
}

View File

@ -5,13 +5,13 @@ module DependencyProxy
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
before_action :verify_dependency_proxy_enabled! before_action :verify_dependency_proxy_available!
before_action :authorize_read_dependency_proxy! before_action :authorize_read_dependency_proxy!
end end
private private
def verify_dependency_proxy_enabled! def verify_dependency_proxy_available!
render_404 unless group&.dependency_proxy_feature_available? render_404 unless group&.dependency_proxy_feature_available?
end end

View File

@ -5,30 +5,19 @@ module Groups
include ::DependencyProxy::GroupAccess include ::DependencyProxy::GroupAccess
before_action :authorize_admin_dependency_proxy!, only: :update before_action :authorize_admin_dependency_proxy!, only: :update
before_action :dependency_proxy before_action :verify_dependency_proxy_enabled!
feature_category :package_registry 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 private
def dependency_proxy def dependency_proxy
@dependency_proxy ||= @dependency_proxy ||=
group.dependency_proxy_setting || group.create_dependency_proxy_setting group.dependency_proxy_setting || group.create_dependency_proxy_setting!
end end
def dependency_proxy_params def verify_dependency_proxy_enabled!
params.require(:dependency_proxy_group_setting).permit(:enabled) render_404 unless dependency_proxy.enabled?
end end
end end
end end

View File

@ -4,8 +4,8 @@ class Projects::JobsController < Projects::ApplicationController
include SendFileUpload include SendFileUpload
include ContinueParams include ContinueParams
before_action :find_job_as_build, except: [:index, :play] before_action :find_job_as_build, except: [:index, :play, :show]
before_action :find_job_as_processable, only: [:play] before_action :find_job_as_processable, only: [:play, :show]
before_action :authorize_read_build_trace!, only: [:trace, :raw] before_action :authorize_read_build_trace!, only: [:trace, :raw]
before_action :authorize_read_build! before_action :authorize_read_build!
before_action :authorize_update_build!, before_action :authorize_update_build!,

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module Users module Users
class GroupCalloutsController < UserCalloutsController class GroupCalloutsController < Users::CalloutsController
private private
def callout def callout

View File

@ -15,7 +15,7 @@ module Mutations
description: 'User callout dismissed.' description: 'User callout dismissed.'
def resolve(feature_name:) def resolve(feature_name:)
callout = Users::DismissUserCalloutService.new( callout = Users::DismissCalloutService.new(
container: nil, current_user: current_user, params: { feature_name: feature_name } container: nil, current_user: current_user, params: { feature_name: feature_name }
).execute ).execute
errors = errors_on_object(callout) errors = errors_on_object(callout)

View File

@ -25,13 +25,17 @@ module Types
value: :offline value: :offline
value 'STALE', 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: :stale
value 'NOT_CONNECTED', value 'NOT_CONNECTED',
description: 'Runner that has never contacted this instance.', 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: :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 end
end end

View File

@ -5,7 +5,7 @@ module Types
graphql_name 'UserCalloutFeatureNameEnum' graphql_name 'UserCalloutFeatureNameEnum'
description 'Name of the feature that the callout is for.' 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}." value feature_name.upcase, value: feature_name, description: "Callout feature name for #{feature_name}."
end end
end end

View File

@ -206,10 +206,6 @@ module ApplicationHelper
'https://' + promo_host 'https://' + promo_host
end end
def contact_sales_url
promo_url + '/sales'
end
def support_url def support_url
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/' Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
end end

View File

@ -19,6 +19,13 @@ module Ci
} }
end end
def bridge_data(build)
{
"build_name" => build.name,
"empty-state-illustration-path" => image_path('illustrations/job-trigger-md.svg')
}
end
def job_counts def job_counts
{ {
"all" => limited_counter_with_delimiter(@all_builds), "all" => limited_counter_with_delimiter(@all_builds),

View File

@ -23,7 +23,7 @@ module Ci
icon = 'status-paused' icon = 'status-paused'
span_class = 'gl-text-gray-600' span_class = 'gl-text-gray-600'
end end
when :not_connected when :not_connected, :never_contacted
title = s_("Runners|New runner, has not connected yet") title = s_("Runners|New runner, has not connected yet")
icon = 'warning-solid' icon = 'warning-solid'
when :offline when :offline

View File

@ -182,7 +182,7 @@ module MergeRequestsHelper
project_path: project_path(merge_request.project), project_path: project_path(merge_request.project),
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'), changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
is_fluid_layout: fluid_layout.to_s, 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_suggest_popover: show_suggest_popover?.to_s,
show_whitespace_default: @show_whitespace_default.to_s, show_whitespace_default: @show_whitespace_default.to_s,
file_by_file_default: @file_by_file_default.to_s, file_by_file_default: @file_by_file_default.to_s,

View File

@ -14,8 +14,7 @@ module TabHelper
gl_tabs_classes = %w[nav gl-tabs-nav] gl_tabs_classes = %w[nav gl-tabs-nav]
html_options = html_options.merge( html_options = html_options.merge(
class: [*html_options[:class], gl_tabs_classes].join(' '), class: [*html_options[:class], gl_tabs_classes].join(' ')
role: 'tablist'
) )
content = capture(&block) if block_given? content = capture(&block) if block_given?
@ -54,7 +53,7 @@ module TabHelper
extra_tab_classes = html_options.delete(:tab_class) extra_tab_classes = html_options.delete(:tab_class)
tab_class = %w[nav-item].push(*extra_tab_classes) 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? if block_given?
link_to(options, html_options, &block) link_to(options, html_options, &block)
else else

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ module Ci
AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze
AVAILABLE_TYPES = runner_types.keys.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 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 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 :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 :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 :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 :ordered, -> { order(id: :desc) }
scope :with_recent_runner_queue, -> { where('contacted_at > ?', recent_queue_deadline) } 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 deprecated_rest_status if legacy_mode == '14.5'
return :stale if stale? return :stale if stale?
return :not_connected unless contacted_at return :never_contacted unless contacted_at
online? ? :online : :offline online? ? :online : :offline
end end

View File

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

View File

@ -204,7 +204,7 @@ class User < ApplicationRecord
has_many :bulk_imports has_many :bulk_imports
has_many :custom_attributes, class_name: 'UserCustomAttribute' 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 :group_callouts, class_name: 'Users::GroupCallout'
has_many :term_agreements has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term' belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
@ -1947,7 +1947,7 @@ class User < ApplicationRecord
end end
def find_or_initialize_callout(feature_name) 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 end
def find_or_initialize_group_callout(feature_name, group_id) def find_or_initialize_group_callout(feature_name, group_id)

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
module Users module Users
class GroupCallout < ApplicationRecord class GroupCallout < ApplicationRecord
include Calloutable include Users::Calloutable
self.table_name = 'user_group_callouts' self.table_name = 'user_group_callouts'

View File

@ -15,19 +15,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Highlight.highlight( Gitlab::Highlight.highlight(
blob.path, blob.path,
limited_blob_data(to: to), blob_data(to),
language: language, language: blob_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,
plain: plain plain: plain
) )
end end
@ -38,6 +27,14 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
highlight(plain: false) highlight(plain: false)
end 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 def raw_plain_data
blob.data unless blob.binary? blob.data unless blob.binary?
end end
@ -134,23 +131,6 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def language def language
blob.language_from_gitattributes blob.language_from_gitattributes
end 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 end
BlobPresenter.prepend_mod_with('BlobPresenter') BlobPresenter.prepend_mod_with('BlobPresenter')

View File

@ -73,7 +73,7 @@ class MergeRequestWidgetEntity < Grape::Entity
end end
expose :user_callouts_path do |_merge_request| expose :user_callouts_path do |_merge_request|
user_callouts_path callouts_path
end end
expose :suggest_pipeline_feature_id do |_merge_request| expose :suggest_pipeline_feature_id do |_merge_request|

View File

@ -2,6 +2,8 @@
module Ci module Ci
class RetryBuildService < ::BaseService class RetryBuildService < ::BaseService
include Gitlab::Utils::StrongMemoize
def self.clone_accessors def self.clone_accessors
%i[pipeline project ref tag options name %i[pipeline project ref tag options name
allow_failure stage stage_id stage_idx trigger_request allow_failure stage stage_id stage_idx trigger_request
@ -45,6 +47,11 @@ module Ci
job.save! job.save!
end end
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`. build.reset # refresh the data to get new values of `retried` and `processed`.
new_build new_build
@ -63,15 +70,22 @@ module Ci
def clone_build(build) def clone_build(build)
project.builds.new(build_attributes(build)).tap do |new_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)) new_build.assign_attributes(deployment_attributes_for(new_build, build))
end end
end end
end
def build_attributes(build) def build_attributes(build)
attributes = self.class.clone_accessors.to_h do |attribute| attributes = self.class.clone_accessors.to_h do |attribute|
[attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend [attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end 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[:user] = current_user
attributes attributes
end end
@ -80,6 +94,26 @@ module Ci
::Gitlab::Ci::Pipeline::Seed::Build ::Gitlab::Ci::Pipeline::Seed::Build
.deployment_attributes_for(new_build, old_build.persisted_environment) .deployment_attributes_for(new_build, old_build.persisted_environment)
end 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
end end

View File

@ -15,12 +15,22 @@ module MergeRequests
def execute def execute
line_position = position.line_range["end"] || position.line_range["start"] line_position = position.line_range["end"] || position.line_range["start"]
diff_line_index = diff_lines.find_index do |l| found_line = false
if line_position["new_line"] diff_line_index = -1
l.new_line == line_position["new_line"] diff_lines.each_with_index do |l, i|
elsif line_position["old_line"] if found_line
l.old_line == line_position["old_line"] if !l.type
break
elsif l.type == 'new'
diff_line_index = i
break
end end
else
# Find the old line
found_line = l.old_line == line_position["new_line"]
end
diff_line_index = i
end end
initial_line_index = [diff_line_index - OVERFLOW_LINES_COUNT, 0].max initial_line_index = [diff_line_index - OVERFLOW_LINES_COUNT, 0].max
last_line_index = [diff_line_index + OVERFLOW_LINES_COUNT, diff_lines.length].min last_line_index = [diff_line_index + OVERFLOW_LINES_COUNT, diff_lines.length].min

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module Users module Users
class DismissUserCalloutService < BaseContainerService class DismissCalloutService < BaseContainerService
def execute def execute
callout.tap do |record| callout.tap do |record|
record.update(dismissed_at: Time.current) if record.valid? record.update(dismissed_at: Time.current) if record.valid?

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module Users module Users
class DismissGroupCalloutService < DismissUserCalloutService class DismissGroupCalloutService < DismissCalloutService
private private
def callout def callout

View File

@ -5,7 +5,7 @@
variant: :tip, variant: :tip,
alert_class: 'js-security-newsletter-callout', alert_class: 'js-security-newsletter-callout',
is_contained: true, 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 close_button_data: { testid: 'close-security-newsletter-callout' } do
.gl-alert-body .gl-alert-body
= s_('AdminArea|Sign up for the GitLab Security Newsletter to get notified for security updates.') = s_('AdminArea|Sign up for the GitLab Security Newsletter to get notified for security updates.')

View File

@ -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') - 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 .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') } %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') = sprite_icon('close', size: 16, css_class: 'gl-icon')

View File

@ -1,3 +1,2 @@
%ul.nav-links.new-session-tabs.single-tab.nav-tabs.nav = gl_tabs_nav({ class: 'new-session-tabs gl-border-0' }) do
%li.nav-item = gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1' }
%a.nav-link.active= tab_title

View File

@ -18,6 +18,6 @@
"gid_prefix": container_repository_gid_prefix, "gid_prefix": container_repository_gid_prefix,
connection_error: (!!@connection_error).to_s, connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s, invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path, user_callouts_path: callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } } show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s } }

View File

@ -15,7 +15,7 @@
track_label: 'invite_members_banner', track_label: 'invite_members_banner',
invite_members_path: group_group_members_path(@group), invite_members_path: group_group_members_path(@group),
callouts_path: group_callouts_path, callouts_path: group_callouts_path,
callouts_feature_id: UserCalloutsHelper::INVITE_MEMBERS_BANNER, callouts_feature_id: Users::GroupCalloutsHelper::INVITE_MEMBERS_BANNER,
group_id: @group.id } } group_id: @group.id } }
= render 'groups/invite_members_modal', group: @group = render 'groups/invite_members_modal', group: @group

View File

@ -4,7 +4,7 @@
title: _('Open registration is enabled on your instance.'), title: _('Open registration is enabled on your instance.'),
variant: :warning, variant: :warning,
alert_class: 'js-registration-enabled-callout', 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 close_button_data: { testid: 'close-registration-enabled-callout' } do
.gl-alert-body .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 } = 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 }

View File

@ -6,8 +6,8 @@
#js-new-feature-flag{ data: { endpoint: project_feature_flags_path(@project, format: :json), #js-new-feature-flag{ data: { endpoint: project_feature_flags_path(@project, format: :json),
feature_flags_path: project_feature_flags_path(@project), feature_flags_path: project_feature_flags_path(@project),
environments_endpoint: search_project_environments_path(@project, format: :json), environments_endpoint: search_project_environments_path(@project, format: :json),
user_callouts_path: user_callouts_path, user_callouts_path: callouts_path,
user_callout_id: UserCalloutsHelper::FEATURE_FLAGS_NEW_VERSION, user_callout_id: Users::CalloutsHelper::FEATURE_FLAGS_NEW_VERSION,
show_user_callout: show_feature_flags_new_version?.to_s, 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'), 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'), environments_scope_docs_path: help_page_path('ci/environments/index.md', anchor: 'scope-environments-with-specs'),

View File

@ -7,4 +7,7 @@
= render_if_exists "shared/shared_runners_minutes_limit_flash_message" = 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) }

View File

@ -22,6 +22,6 @@
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project), "cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
connection_error: (!!@connection_error).to_s, connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s, invalid_path_error: (!!@invalid_path_error).to_s,
user_callouts_path: user_callouts_path, user_callouts_path: callouts_path,
user_callout_id: UserCalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT, user_callout_id: Users::CalloutsHelper::UNFINISHED_TAG_CLEANUP_CALLOUT,
show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } } show_unfinished_tag_cleanup_callout: show_unfinished_tag_cleanup_callout?.to_s, } }

View File

@ -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}" } .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'), .js-customize-homepage-banner{ data: { svg_path: image_path('illustrations/monitoring/getting_started.svg'),
preferences_behavior_path: profile_preferences_path(anchor: 'behavior'), preferences_behavior_path: profile_preferences_path(anchor: 'behavior'),
callouts_path: user_callouts_path, callouts_path: callouts_path,
callouts_feature_id: UserCalloutsHelper::CUSTOMIZE_HOMEPAGE, callouts_feature_id: Users::CalloutsHelper::CUSTOMIZE_HOMEPAGE,
track_label: 'home_page' } } track_label: 'home_page' } }
= render template: 'dashboard/projects/index' = render template: 'dashboard/projects/index'

View File

@ -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) - extra_flash_class = local_assigns.fetch(:extra_flash_class, nil)
.flash-container.flash-container-page.user-callout{ data: callout_data } .flash-container.flash-container-page.user-callout{ data: callout_data }

View File

@ -1,7 +1,7 @@
= render 'shared/global_alert', = render 'shared/global_alert',
variant: :warning, variant: :warning,
alert_class: 'js-recovery-settings-callout', 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 close_button_data: { testid: 'close-account-recovery-regular-check-callout' } do
.gl-alert-body .gl-alert-body
= s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.') = s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.')

View File

@ -3,24 +3,20 @@
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12) .fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12) .fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs = gl_tabs_nav({ class: %w[scrolling-tabs js-milestone-tabs] }) do
%li.nav-item = gl_tab_link_to '#tab-issues', item_active: true, data: { endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
= link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
= _('Issues') = _('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? - if milestone.merge_requests_enabled?
%li.nav-item = gl_tab_link_to '#tab-merge-requests', data: { endpoint: milestone_tab_path(milestone, 'merge_requests', show_project_name: show_project_name) } do
= 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
= _('Merge requests') = _('Merge requests')
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size = gl_tab_counter_badge milestone.merge_requests_visible_to_user(current_user).size
%li.nav-item = gl_tab_link_to '#tab-participants', data: { endpoint: milestone_tab_path(milestone, 'participants') } do
= link_to '#tab-participants', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'participants') } do
= _('Participants') = _('Participants')
%span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count = gl_tab_counter_badge milestone.issue_participants_visible_by_user(current_user).count
%li.nav-item = gl_tab_link_to '#tab-labels', data: { endpoint: milestone_tab_path(milestone, 'labels') } do
= link_to '#tab-labels', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'labels') } do
= _('Labels') = _('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-content.milestone-content
.tab-pane.active#tab-issues .tab-pane.active#tab-issues

View File

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

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345744
milestone: '14.6' milestone: '14.6'
type: development type: development
group: group::project management group: group::project management
default_enabled: false default_enabled: true

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344957
milestone: '14.5' milestone: '14.5'
type: development type: development
group: group::workspace group: group::workspace
default_enabled: false default_enabled: true

View File

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

View File

@ -8,11 +8,11 @@ Gitlab.ee do
config.geo_database = config_for(:database_geo) config.geo_database = config_for(:database_geo)
end end
end end
end
Gitlab.ee do
if Gitlab::Runtime.sidekiq? && Gitlab::Geo.geo_database_configured? if Gitlab::Runtime.sidekiq? && Gitlab::Geo.geo_database_configured?
Rails.configuration.geo_database['pool'] = Gitlab::Database.default_pool_size # The Geo::TrackingBase model does not yet use connects_to. So,
Geo::TrackingBase.establish_connection(Rails.configuration.geo_database) # 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
end end

View File

@ -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}`." "The `main:` database needs to be defined as a first configuration item instead of `#{configurations.first.name}`."
end 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? if rejected_config_names.any?
raise "ERROR: This installation of GitLab uses unsupported database names " \ 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 " \ "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 end
replicas_config_names = configurations.select(&:replica?).map(&:name) replicas_config_names = configurations.select(&:replica?).map(&:name)

View File

@ -145,7 +145,7 @@ Rails.application.routes.draw do
get 'acme-challenge/' => 'acme_challenges#show' get 'acme-challenge/' => 'acme_challenges#show'
# UserCallouts # 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 scope :ide, as: :ide, format: false do
get '/', to: 'ide#index' get '/', to: 'ide#index'

View File

@ -61,6 +61,7 @@ scope '-/users', module: :users do
post :decline, on: :member post :decline, on: :member
end end
resources :callouts, only: [:create]
resources :group_callouts, only: [:create] resources :group_callouts, only: [:create]
end end

View File

@ -2,14 +2,13 @@
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated. 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 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. 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: A runner's status will only relate to runner contact status, such as:
`online`, `offline`, or `not_connected`. Status `paused` will not appear when the runner is `online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
not active.
When checking if a runner is `paused`, API users are advised to check the boolean attribute 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 stage: Verify
tiers: [Core, Premium, Ultimate] tiers: [Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648 issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648

View File

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

View File

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

View File

@ -0,0 +1 @@
0d27ca1250d10b8915fa4523707044f9a8c2372110537f5639a1811aeb0858b8

View File

@ -15769,6 +15769,27 @@ CREATE SEQUENCE lfs_file_locks_id_seq
ALTER SEQUENCE lfs_file_locks_id_seq OWNED BY lfs_file_locks.id; 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 ( CREATE TABLE lfs_objects (
id integer NOT NULL, id integer NOT NULL,
oid character varying 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_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 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); 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 ALTER TABLE ONLY lfs_file_locks
ADD CONSTRAINT lfs_file_locks_pkey PRIMARY KEY (id); 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 ALTER TABLE ONLY lfs_objects
ADD CONSTRAINT lfs_objects_pkey PRIMARY KEY (id); 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_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 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); 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 ALTER TABLE ONLY clusters_kubernetes_namespaces
ADD CONSTRAINT fk_rails_40cc7ccbc3 FOREIGN KEY (cluster_project_id) REFERENCES cluster_projects(id) ON DELETE SET NULL; 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 ALTER TABLE ONLY geo_node_namespace_links
ADD CONSTRAINT fk_rails_41ff5fb854 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_41ff5fb854 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;

View File

@ -37,7 +37,7 @@ verification methods:
| Git | Group wiki repository | Geo with Gitaly | _Not implemented_ | | Git | Group wiki repository | Geo with Gitaly | _Not implemented_ |
| Blobs | User uploads _(file system)_ | Geo with API | _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 | 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 | 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 _(file system)_ | Geo with API | _Not implemented_ |
| Blobs | CI job artifacts _(object storage)_ | Geo with API/Managed (*2*) | _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 | | |[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. | |[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. | |[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 | | |[Personal snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | No | |
|[Project 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. | |[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. |

View File

@ -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` | 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_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_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` | Gauge | 10.2 | Number of LFS objects on primary | `url` |
| `geo_lfs_objects_synced` | Gauge | 10.2 | Number of LFS objects synced on secondary | `url` | | `geo_lfs_objects_checksummed` | Gauge | 14.6 | Number of LFS objects checksummed successfully on primary | `url` |
| `geo_lfs_objects_failed` | Gauge | 10.2 | Number of LFS objects failed to sync on secondary | `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_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_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_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_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_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_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_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_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` | | `geo_repositories_checksum_failed` | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | `url` |

View File

@ -307,11 +307,18 @@ Example response:
"health_status": "Healthy", "health_status": "Healthy",
"missing_oauth_application": false, "missing_oauth_application": false,
"db_replication_lag_seconds": null, "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_synced_count": null,
"lfs_objects_failed_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_synced_in_percentage": "0.00%",
"lfs_objects_verified_in_percentage": "0.00%",
"job_artifacts_count": 2, "job_artifacts_count": 2,
"job_artifacts_synced_count": null, "job_artifacts_synced_count": null,
"job_artifacts_failed_count": null, "job_artifacts_failed_count": null,
@ -468,11 +475,18 @@ Example response:
"health_status": "Healthy", "health_status": "Healthy",
"missing_oauth_application": false, "missing_oauth_application": false,
"db_replication_lag_seconds": 0, "db_replication_lag_seconds": 0,
"lfs_objects_count": 0, "lfs_objects_count": 5,
"lfs_objects_synced_count": 0, "lfs_objects_checksum_total_count": 5,
"lfs_objects_failed_count": 0, "lfs_objects_checksummed_count": 5,
"lfs_objects_synced_missing_on_primary_count": 0, "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_synced_in_percentage": "0.00%",
"lfs_objects_verified_in_percentage": "0.00%",
"job_artifacts_count": 2, "job_artifacts_count": 2,
"job_artifacts_synced_count": 1, "job_artifacts_synced_count": 1,
"job_artifacts_failed_count": 1, "job_artifacts_failed_count": 1,
@ -633,11 +647,18 @@ Example response:
"health_status": "Healthy", "health_status": "Healthy",
"missing_oauth_application": false, "missing_oauth_application": false,
"db_replication_lag_seconds": 0, "db_replication_lag_seconds": 0,
"lfs_objects_count": 0, "lfs_objects_count": 5,
"lfs_objects_synced_count": 0, "lfs_objects_checksum_total_count": 5,
"lfs_objects_failed_count": 0, "lfs_objects_checksummed_count": 5,
"lfs_objects_synced_missing_on_primary_count": 0, "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_synced_in_percentage": "0.00%",
"lfs_objects_verified_in_percentage": "0.00%",
"job_artifacts_count": 2, "job_artifacts_count": 2,
"job_artifacts_synced_count": 1, "job_artifacts_synced_count": 1,
"job_artifacts_failed_count": 1, "job_artifacts_failed_count": 1,

View File

@ -15975,7 +15975,8 @@ Values for sorting runners.
| Value | Description | | Value | Description |
| ----- | ----------- | | ----- | ----------- |
| <a id="cirunnerstatusactive"></a>`ACTIVE` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. | | <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="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="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. | | <a id="cirunnerstatuspaused"></a>`PAUSED` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |

View File

@ -124,6 +124,15 @@ Long term service and support (LTSS) for SUSE Linux Enterprise Server (SLES) 12
Announced: 2021-11-22 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 ### 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. 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` ### 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: A runner's status will only relate to runner contact status, such as:
`online`, `offline`, or `not_connected`. Status `paused` will not appear when the runner is `online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
not active.
When checking if a runner is `paused`, API users are advised to check the boolean attribute 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 Announced: 2021-11-22

View File

@ -37,7 +37,7 @@ module Banzai
XPATH_SECTION_OLD = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION_OLD).freeze XPATH_SECTION_OLD = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION_OLD).freeze
def call 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 # Sanitization stripped off the section class - add it back in
return doc unless section_node = doc.at_xpath(XPATH_SECTION) return doc unless section_node = doc.at_xpath(XPATH_SECTION)
@ -52,26 +52,26 @@ module Banzai
rand_suffix = "-#{random_number}" rand_suffix = "-#{random_number}"
modified_footnotes = {} modified_footnotes = {}
xpath_footnote = if Feature.enabled?(:use_cmark_renderer) xpath_footnote = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
XPATH_FOOTNOTE XPATH_FOOTNOTE
else else
Gitlab::Utils::Nokogiri.css_to_xpath('sup > a[id]') Gitlab::Utils::Nokogiri.css_to_xpath('sup > a[id]')
end end
doc.xpath(xpath_footnote).each do |link_node| 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 = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
ref_num.gsub!(/[[:punct:]]/, '\\\\\&') ref_num.gsub!(/[[:punct:]]/, '\\\\\&')
else else
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX_OLD) ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX_OLD)
end 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) node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath(css)
footnote_node = doc.at_xpath(node_xpath) footnote_node = doc.at_xpath(node_xpath)
if footnote_node || modified_footnotes[ref_num] 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[:href] += rand_suffix
link_node[:id] += rand_suffix link_node[:id] += rand_suffix
@ -103,12 +103,12 @@ module Banzai
end end
def fn_id(num) 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}" "#{prefix}#{num}"
end end
def fnref_id(num) 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}" "#{prefix}#{num}"
end end
end end

View File

@ -42,11 +42,11 @@ module Banzai
def initialize(context) def initialize(context)
@context = 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 end
def render(text) 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) CommonMarker.render_html(text, render_options, extensions)
else else
doc = CommonMarker.render_doc(text, PARSE_OPTIONS, extensions) doc = CommonMarker.render_doc(text, PARSE_OPTIONS, extensions)
@ -58,7 +58,7 @@ module Banzai
private private
def extensions def extensions
if Feature.enabled?(:use_cmark_renderer) if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
EXTENSIONS EXTENSIONS
else else
EXTENSIONS + [ EXTENSIONS + [
@ -72,7 +72,7 @@ module Banzai
end end
def render_options_no_sourcepos 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 end
def render_options_sourcepos def render_options_sourcepos

View File

@ -42,7 +42,7 @@ module Banzai
private private
def lang_tag 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') Gitlab::Utils::Nokogiri.css_to_xpath('pre')
else else
Gitlab::Utils::Nokogiri.css_to_xpath('code') Gitlab::Utils::Nokogiri.css_to_xpath('code')

View File

@ -26,7 +26,7 @@ module Banzai
def lang_tag def lang_tag
@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 Gitlab::Utils::Nokogiri.css_to_xpath('pre[lang="plantuml"] > code').freeze
else else
Gitlab::Utils::Nokogiri.css_to_xpath('pre > code[lang="plantuml"]').freeze Gitlab::Utils::Nokogiri.css_to_xpath('pre > code[lang="plantuml"]').freeze

View File

@ -28,7 +28,7 @@ module Banzai
allowlist[:attributes]['li'] = %w[id] allowlist[:attributes]['li'] = %w[id]
allowlist[:transformers].push(self.class.remove_non_footnote_ids) 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 # Allow section elements with data-footnotes attribute
allowlist[:elements].push('section') allowlist[:elements].push('section')
allowlist[:attributes]['section'] = %w(data-footnotes) allowlist[:attributes]['section'] = %w(data-footnotes)
@ -61,7 +61,7 @@ module Banzai
return unless node.name == 'a' || node.name == 'li' return unless node.name == 'a' || node.name == 'li'
return unless node.has_attribute?('id') 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 == 'a' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LINK_REFERENCE_PATTERN
return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN return if node.name == 'li' && node['id'] =~ Banzai::Filter::FootnoteFilter::FOOTNOTE_LI_REFERENCE_PATTERN
else else

View File

@ -70,7 +70,7 @@ module Banzai
private private
def parse_lang_params(node) 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. # 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 # 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) 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(' ') language_params = [node.attr('data-meta'), language_params].compact.join(' ')
end end

View File

@ -7,7 +7,7 @@ module Gitlab
register_for 'gitlab-html-pipeline' register_for 'gitlab-html-pipeline'
def format(node, lang, opts) 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>) %(<pre #{lang ? %[lang="#{lang}"] : ''}><code>#{node.content}</code></pre>)
else else
%(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>) %(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>)

View File

@ -16,8 +16,12 @@ module Gitlab
def details_path def details_path
return unless can?(user, :read_pipeline, downstream_pipeline) 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) project_pipeline_path(downstream_project, downstream_pipeline)
end end
end
def has_action? def has_action?
false false

View File

@ -72,6 +72,10 @@ module Gitlab
}.with_indifferent_access.freeze }.with_indifferent_access.freeze
end end
def self.all_database_names
DATABASE_NAMES
end
# We configure the database connection pool size automatically based on the # We configure the database connection pool size automatically based on the
# configured concurrency. We also add some headroom, to make sure we don't # 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 # run out of connections when more threads besides the 'user-facing' ones

View File

@ -287,6 +287,7 @@ ldap_group_links: :gitlab_main
lfs_file_locks: :gitlab_main lfs_file_locks: :gitlab_main
lfs_objects: :gitlab_main lfs_objects: :gitlab_main
lfs_objects_projects: :gitlab_main lfs_objects_projects: :gitlab_main
lfs_object_states: :gitlab_main
licenses: :gitlab_main licenses: :gitlab_main
lists: :gitlab_main lists: :gitlab_main
list_user_preferences: :gitlab_main list_user_preferences: :gitlab_main

View File

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

View File

@ -44,7 +44,11 @@ module Gitlab
new_blob_lazy new_blob_lazy
old_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 end
def position(position_marker, position_type: :text) def position(position_marker, position_type: :text)
@ -450,33 +454,6 @@ module Gitlab
find_renderable_viewer_class(classes) find_renderable_viewer_class(classes)
end 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 def alternate_viewer_class
return unless viewer.instance_of?(DiffViewer::Renamed) return unless viewer.instance_of?(DiffViewer::Renamed)

View File

@ -153,8 +153,6 @@ module Gitlab
blob.load_all_data! blob.load_all_data!
return blob.present.highlight_transformed.lines if Feature.enabled?(:jupyter_clean_diffs, @project, default_enabled: true)
blob.present.highlight.lines blob.present.highlight.lines
end end

View File

@ -24,7 +24,7 @@ module Gitlab
LFS_POINTER_MIN_SIZE = 120.bytes LFS_POINTER_MIN_SIZE = 120.bytes
LFS_POINTER_MAX_SIZE = 200.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 attr_writer :name, :path, :data
def self.gitlab_blob_truncated_true def self.gitlab_blob_truncated_true
@ -127,7 +127,6 @@ module Gitlab
# Retain the actual size before it is encoded # Retain the actual size before it is encoded
@loaded_size = @data.bytesize if @data @loaded_size = @data.bytesize if @data
@loaded_all_data = @loaded_size == size @loaded_all_data = @loaded_size == size
@transformed_for_diff = false
record_metric_blob_size record_metric_blob_size
record_metric_truncated(truncated?) record_metric_truncated(truncated?)

View File

@ -31,6 +31,10 @@ module Gitlab
else else
import_with_legacy_diff_note import_with_legacy_diff_note
end end
rescue ::DiffNote::NoteDiffFileCreationError => e
Logger.warn(message: e.message, 'error.class': e.class.name)
import_with_legacy_diff_note
rescue ActiveRecord::InvalidForeignKey => e rescue ActiveRecord::InvalidForeignKey => e
# It's possible the project and the issue have been deleted since # 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 # scheduling this job. In this case we'll just skip creating the note

View File

@ -29,6 +29,7 @@ module Gitlab
project_id: project.id, project_id: project.id,
author_id: author_id, author_id: author_id,
note: note_body, note: note_body,
discussion_id: note.discussion_id,
system: false, system: false,
created_at: note.created_at, created_at: note.created_at,
updated_at: note.updated_at updated_at: note.updated_at

Some files were not shown because too many files have changed in this diff Show More