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

View File

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

View File

@ -16,7 +16,7 @@ Cop/UserAdmin:
- app/helpers/nav_helper.rb
- app/helpers/projects_helper.rb
- app/helpers/search_helper.rb
- app/helpers/user_callouts_helper.rb
- app/helpers/users/callouts_helper.rb
- app/helpers/users_helper.rb
- app/helpers/visibility_level_helper.rb
- app/models/concerns/protected_ref_access.rb
@ -38,7 +38,7 @@ Cop/UserAdmin:
- ee/app/helpers/ee/dashboard_helper.rb
- ee/app/helpers/ee/import_helper.rb
- ee/app/helpers/ee/subscribable_banner_helper.rb
- ee/app/helpers/ee/user_callouts_helper.rb
- ee/app/helpers/ee/users/callouts_helper.rb
- ee/app/helpers/license_monitoring_helper.rb
- ee/app/helpers/push_rules_helper.rb
- ee/app/models/concerns/ee/protected_ref_access.rb

View File

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

View File

@ -1 +1 @@
1de88e4247d4b940f843003781cb2bf75582b826
f9af7fbcbfda556c61dcbb2280cda6c6e210cb77

View File

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

View File

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

View File

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

View File

@ -170,7 +170,7 @@ export default {
},
availableGroupsForImport() {
return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && g.flags.isInvalid);
return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && !g.flags.isInvalid);
},
humanizedTotal() {
@ -521,13 +521,15 @@ export default {
/>
<template v-else>
<div
class="gl-bg-gray-10 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-p-4 gl-display-flex gl-align-items-center"
class="gl-bg-gray-10 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-px-4 gl-display-flex gl-align-items-center import-table-bar"
>
<gl-sprintf :message="__('%{count} selected')">
<template #count>
{{ selectedGroupsIds.length }}
</template>
</gl-sprintf>
<span data-test-id="selection-count">
<gl-sprintf :message="__('%{count} selected')">
<template #count>
{{ selectedGroupsIds.length }}
</template>
</gl-sprintf>
</span>
<gl-button
category="primary"
variant="confirm"
@ -539,7 +541,7 @@ export default {
</div>
<gl-table
ref="table"
class="gl-w-full"
class="gl-w-full import-table"
data-qa-selector="import_table"
:tbody-tr-class="rowClasses"
:tbody-tr-attr="qaRowAttributes"

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 {
i18n: {
eraseLogButtonLabel: s__('Job|Erase job log'),
eraseLogButtonLabel: s__('Job|Erase job log and artifacts'),
scrollToBottomButtonLabel: s__('Job|Scroll to bottom'),
scrollToTopButtonLabel: s__('Job|Scroll to top'),
showRawButtonLabel: s__('Job|Show complete raw'),

View File

@ -1,10 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import BridgeApp from './bridge/app.vue';
import JobApp from './components/job_app.vue';
import createStore from './store';
export default () => {
const element = document.getElementById('js-job-vue-app');
const initializeJobPage = (element) => {
const store = createStore();
// Let's start initializing the store (i.e. fetching data) right away
@ -51,3 +52,35 @@ export default () => {
},
});
};
const initializeBridgePage = (el) => {
const { buildName, emptyStateIllustrationPath } = el.dataset;
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
return new Vue({
el,
apolloProvider,
provide: {
buildName,
emptyStateIllustrationPath,
},
render(h) {
return h(BridgeApp);
},
});
};
export default () => {
const jobElement = document.getElementById('js-job-page');
const bridgeElement = document.getElementById('js-bridge-page');
if (jobElement) {
initializeJobPage(jobElement);
} else {
initializeBridgePage(bridgeElement);
}
};

View File

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

View File

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

View File

@ -1,7 +1 @@
import { helpPagePath } from '~/helpers/help_page_helper';
export const GRAPHQL_PAGE_SIZE = 20;
export const ENABLE_DEPENDENCY_PROXY_DOCS_PATH = helpPagePath(
'user/packages/dependency_proxy/index',
{ anchor: 'enable-or-disable-the-dependency-proxy-for-a-group' },
);

View File

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

View File

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

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';
// Fixing double scrollbar issue
// See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1156 and
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54837
.import-entities-namespace-dropdown.show.dropdown .dropdown-menu {
max-height: initial;
}
.import-jobs-to-col {
width: 39%;
}
@ -38,3 +31,31 @@
box-shadow: inset 0 0 0 1px var(--gray-200, $gray-200);
}
}
$import-bar-height: $gl-spacing-scale-11;
.import-table-bar {
@include gl-sticky;
height: $import-bar-height;
top: $header-height;
z-index: 3;
html.with-performance-bar & {
top: $header-height + $performance-bar-height;
}
}
.import-table {
border-collapse: separate;
thead {
@include gl-sticky;
background-color: var(--gray-10, $gray-10);
top: calc(#{$header-height} + #{$import-bar-height});
z-index: 3;
html.with-performance-bar & {
top: calc(#{$header-height + $performance-bar-height} + #{$import-bar-height});
}
}
}

View File

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

View File

@ -5,30 +5,19 @@ module Groups
include ::DependencyProxy::GroupAccess
before_action :authorize_admin_dependency_proxy!, only: :update
before_action :dependency_proxy
before_action :verify_dependency_proxy_enabled!
feature_category :package_registry
def show
@blobs_count = group.dependency_proxy_blobs.count
@blobs_total_size = group.dependency_proxy_blobs.total_size
end
def update
dependency_proxy.update(dependency_proxy_params)
redirect_to group_dependency_proxy_path(group)
end
private
def dependency_proxy
@dependency_proxy ||=
group.dependency_proxy_setting || group.create_dependency_proxy_setting
group.dependency_proxy_setting || group.create_dependency_proxy_setting!
end
def dependency_proxy_params
params.require(:dependency_proxy_group_setting).permit(:enabled)
def verify_dependency_proxy_enabled!
render_404 unless dependency_proxy.enabled?
end
end
end

View File

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

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
module Users
class GroupCalloutsController < UserCalloutsController
class GroupCalloutsController < Users::CalloutsController
private
def callout

View File

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

View File

@ -25,13 +25,17 @@ module Types
value: :offline
value 'STALE',
description: "Runner that has not contacted this instance within the last #{::Ci::Runner::STALE_TIMEOUT.inspect}. Only available if legacyMode is null. Will be a possible return value starting in 15.0",
description: "Runner that has not contacted this instance within the last #{::Ci::Runner::STALE_TIMEOUT.inspect}. Only available if legacyMode is null. Will be a possible return value starting in 15.0.",
value: :stale
value 'NOT_CONNECTED',
description: 'Runner that has never contacted this instance.',
deprecated: { reason: 'This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period of no contact', milestone: '14.6' },
deprecated: { reason: "Use NEVER_CONTACTED instead. NEVER_CONTACTED will have a slightly different scope starting in 15.0, with STALE being returned instead after #{::Ci::Runner::STALE_TIMEOUT.inspect} of no contact", milestone: '14.6' },
value: :not_connected
value 'NEVER_CONTACTED',
description: 'Runner that has never contacted this instance. Set legacyMode to null to utilize this value. Will replace NOT_CONNECTED starting in 15.0.',
value: :never_contacted
end
end
end

View File

@ -5,7 +5,7 @@ module Types
graphql_name 'UserCalloutFeatureNameEnum'
description 'Name of the feature that the callout is for.'
::UserCallout.feature_names.keys.each do |feature_name|
::Users::Callout.feature_names.keys.each do |feature_name|
value feature_name.upcase, value: feature_name, description: "Callout feature name for #{feature_name}."
end
end

View File

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

View File

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

View File

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

View File

@ -182,7 +182,7 @@ module MergeRequestsHelper
project_path: project_path(merge_request.project),
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
is_fluid_layout: fluid_layout.to_s,
dismiss_endpoint: user_callouts_path,
dismiss_endpoint: callouts_path,
show_suggest_popover: show_suggest_popover?.to_s,
show_whitespace_default: @show_whitespace_default.to_s,
file_by_file_default: @file_by_file_default.to_s,

View File

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

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 = runner_types.keys.freeze
AVAILABLE_STATUSES = %w[active paused online offline not_connected stale].freeze
AVAILABLE_STATUSES = %w[active paused online offline not_connected never_contacted stale].freeze # TODO: Remove in %15.0: active, paused, not_connected. Relevant issues: https://gitlab.com/gitlab-org/gitlab/-/issues/347303, https://gitlab.com/gitlab-org/gitlab/-/issues/347305, https://gitlab.com/gitlab-org/gitlab/-/issues/344648
AVAILABLE_SCOPES = (AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
@ -66,7 +66,8 @@ module Ci
scope :recent, -> { where('ci_runners.created_at >= :date OR ci_runners.contacted_at >= :date', date: stale_deadline) }
scope :stale, -> { where('ci_runners.created_at < :date AND (ci_runners.contacted_at IS NULL OR ci_runners.contacted_at < :date)', date: stale_deadline) }
scope :offline, -> { where(arel_table[:contacted_at].lteq(online_contact_time_deadline)) }
scope :not_connected, -> { where(contacted_at: nil) }
scope :not_connected, -> { where(contacted_at: nil) } # TODO: Remove in 15.0
scope :never_contacted, -> { where(contacted_at: nil) }
scope :ordered, -> { order(id: :desc) }
scope :with_recent_runner_queue, -> { where('contacted_at > ?', recent_queue_deadline) }
@ -284,7 +285,7 @@ module Ci
return deprecated_rest_status if legacy_mode == '14.5'
return :stale if stale?
return :not_connected unless contacted_at
return :never_contacted unless contacted_at
online? ? :online : :offline
end

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 :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
has_many :callouts, class_name: 'Users::Callout'
has_many :group_callouts, class_name: 'Users::GroupCallout'
has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
@ -1947,7 +1947,7 @@ class User < ApplicationRecord
end
def find_or_initialize_callout(feature_name)
callouts.find_or_initialize_by(feature_name: ::UserCallout.feature_names[feature_name])
callouts.find_or_initialize_by(feature_name: ::Users::Callout.feature_names[feature_name])
end
def find_or_initialize_group_callout(feature_name, group_id)

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
class GroupCallout < ApplicationRecord
include Calloutable
include Users::Calloutable
self.table_name = 'user_group_callouts'

View File

@ -15,19 +15,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Highlight.highlight(
blob.path,
limited_blob_data(to: to),
language: language,
plain: plain
)
end
def highlight_transformed(plain: nil)
load_all_blob_data
Gitlab::Highlight.highlight(
blob.path,
transformed_blob_data,
language: transformed_blob_language,
blob_data(to),
language: blob_language,
plain: plain
)
end
@ -38,6 +27,14 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
highlight(plain: false)
end
def blob_data(to)
@_blob_data ||= Gitlab::Diff::CustomDiff.transformed_blob_data(blob) || limited_blob_data(to: to)
end
def blob_language
@_blob_language ||= Gitlab::Diff::CustomDiff.transformed_blob_language(blob) || language
end
def raw_plain_data
blob.data unless blob.binary?
end
@ -134,23 +131,6 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def language
blob.language_from_gitattributes
end
def transformed_blob_language
@transformed_blob_language ||= blob.path.ends_with?('.ipynb') ? 'md' : language
end
def transformed_blob_data
@transformed_blob ||= if blob.path.ends_with?('.ipynb') && blob.transformed_for_diff
IpynbDiff.transform(blob.data,
raise_errors: true,
options: { include_metadata: false, cell_decorator: :percent })
end
@transformed_blob ||= blob.data
rescue IpynbDiff::InvalidNotebookError => e
Gitlab::ErrorTracking.log_exception(e)
blob.data
end
end
BlobPresenter.prepend_mod_with('BlobPresenter')

View File

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

View File

@ -2,6 +2,8 @@
module Ci
class RetryBuildService < ::BaseService
include Gitlab::Utils::StrongMemoize
def self.clone_accessors
%i[pipeline project ref tag options name
allow_failure stage stage_id stage_idx trigger_request
@ -45,6 +47,11 @@ module Ci
job.save!
end
end
if create_deployment_in_separate_transaction?
clone_deployment!(new_build, build)
end
build.reset # refresh the data to get new values of `retried` and `processed`.
new_build
@ -63,7 +70,9 @@ module Ci
def clone_build(build)
project.builds.new(build_attributes(build)).tap do |new_build|
new_build.assign_attributes(deployment_attributes_for(new_build, build))
unless create_deployment_in_separate_transaction?
new_build.assign_attributes(deployment_attributes_for(new_build, build))
end
end
end
@ -72,6 +81,11 @@ module Ci
[attribute, build.public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend
end
if create_deployment_in_separate_transaction? && build.persisted_environment.present?
attributes[:metadata_attributes] ||= {}
attributes[:metadata_attributes][:expanded_environment_name] = build.expanded_environment_name
end
attributes[:user] = current_user
attributes
end
@ -80,6 +94,26 @@ module Ci
::Gitlab::Ci::Pipeline::Seed::Build
.deployment_attributes_for(new_build, old_build.persisted_environment)
end
def clone_deployment!(new_build, old_build)
return unless old_build.deployment.present?
# We should clone the previous deployment attributes instead of initializing
# new object with `Seed::Deployment`.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/347206
deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment
.new(new_build, old_build.persisted_environment).to_resource
return unless deployment
new_build.create_deployment!(deployment.attributes)
end
def create_deployment_in_separate_transaction?
strong_memoize(:create_deployment_in_separate_transaction) do
::Feature.enabled?(:create_deployment_in_separate_transaction, project, default_enabled: :yaml)
end
end
end
end

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
variant: :tip,
alert_class: 'js-security-newsletter-callout',
is_contained: true,
alert_data: { feature_id: UserCalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: user_callouts_path, defer_links: 'true' },
alert_data: { feature_id: Users::CalloutsHelper::SECURITY_NEWSLETTER_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' },
close_button_data: { testid: 'close-security-newsletter-callout' } do
.gl-alert-body
= s_('AdminArea|Sign up for the GitLab Security Newsletter to get notified for security updates.')

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')
.gcp-signup-offer.gl-alert.gl-alert-info.gl-my-3{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
.gcp-signup-offer.gl-alert.gl-alert-info.gl-my-3{ role: 'alert', data: { feature_id: Users::CalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: callouts_path } }
.gl-alert-container
%button.js-close.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon{ type: 'button', 'aria-label' => _('Dismiss') }
= sprite_icon('close', size: 16, css_class: 'gl-icon')

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@
title: _('Open registration is enabled on your instance.'),
variant: :warning,
alert_class: 'js-registration-enabled-callout',
alert_data: { feature_id: UserCalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: user_callouts_path },
alert_data: { feature_id: Users::CalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: callouts_path },
close_button_data: { testid: 'close-registration-enabled-callout' } do
.gl-alert-body
= html_escape(_('%{anchorOpen}Learn more%{anchorClose} about how you can customize / disable registration on your instance.')) % { anchorOpen: "<a href=\"#{help_page_path('user/admin_area/settings/sign_up_restrictions')}\" class=\"gl-link\">".html_safe, anchorClose: '</a>'.html_safe }

View File

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

View File

@ -7,4 +7,7 @@
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
#js-job-vue-app{ data: jobs_data }
- if @build.is_a? ::Ci::Build
#js-job-page{ data: jobs_data }
- else
#js-bridge-page{ data: bridge_data(@build) }

View File

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

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

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)
.flash-container.flash-container-page.user-callout{ data: callout_data }

View File

@ -1,7 +1,7 @@
= render 'shared/global_alert',
variant: :warning,
alert_class: 'js-recovery-settings-callout',
alert_data: { feature_id: UserCalloutsHelper::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, dismiss_endpoint: user_callouts_path, defer_links: 'true' },
alert_data: { feature_id: Users::CalloutsHelper::TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK, dismiss_endpoint: callouts_path, defer_links: 'true' },
close_button_data: { testid: 'close-account-recovery-regular-check-callout' } do
.gl-alert-body
= s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.')

View File

@ -3,24 +3,20 @@
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
%li.nav-item
= link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
= _('Issues')
%span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
= gl_tabs_nav({ class: %w[scrolling-tabs js-milestone-tabs] }) do
= gl_tab_link_to '#tab-issues', item_active: true, data: { endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
= _('Issues')
= gl_tab_counter_badge milestone.issues_visible_to_user(current_user).size
- if milestone.merge_requests_enabled?
%li.nav-item
= link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests', show_project_name: show_project_name) } do
= _('Merge requests')
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
%li.nav-item
= link_to '#tab-participants', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'participants') } do
= _('Participants')
%span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count
%li.nav-item
= link_to '#tab-labels', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'labels') } do
= _('Labels')
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
= gl_tab_link_to '#tab-merge-requests', data: { endpoint: milestone_tab_path(milestone, 'merge_requests', show_project_name: show_project_name) } do
= _('Merge requests')
= gl_tab_counter_badge milestone.merge_requests_visible_to_user(current_user).size
= gl_tab_link_to '#tab-participants', data: { endpoint: milestone_tab_path(milestone, 'participants') } do
= _('Participants')
= gl_tab_counter_badge milestone.issue_participants_visible_by_user(current_user).count
= gl_tab_link_to '#tab-labels', data: { endpoint: milestone_tab_path(milestone, 'labels') } do
= _('Labels')
= gl_tab_counter_badge milestone.issue_labels_visible_by_user(current_user).count
.tab-content.milestone-content
.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'
type: development
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'
type: development
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)
end
end
end
Gitlab.ee do
if Gitlab::Runtime.sidekiq? && Gitlab::Geo.geo_database_configured?
Rails.configuration.geo_database['pool'] = Gitlab::Database.default_pool_size
Geo::TrackingBase.establish_connection(Rails.configuration.geo_database)
# The Geo::TrackingBase model does not yet use connects_to. So,
# this will not properly support geo: from config/databse.yml
# file yet. This is ACK of the current state and will be fixed.
Geo::TrackingBase.establish_connection(Gitlab::Database.geo_db_config_with_default_pool_size)
end
end

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}`."
end
rejected_config_names = configurations.map(&:name).to_set - Gitlab::Database::DATABASE_NAMES
rejected_config_names = configurations.map(&:name).to_set - Gitlab::Database.all_database_names
if rejected_config_names.any?
raise "ERROR: This installation of GitLab uses unsupported database names " \
"in 'config/database.yml': #{rejected_config_names.to_a.join(", ")}. The only supported ones are " \
"#{Gitlab::Database::DATABASE_NAMES.join(", ")}."
"#{Gitlab::Database.all_database_names.join(", ")}."
end
replicas_config_names = configurations.select(&:replica?).map(&:name)

View File

@ -145,7 +145,7 @@ Rails.application.routes.draw do
get 'acme-challenge/' => 'acme_challenges#show'
# UserCallouts
resources :user_callouts, only: [:create]
resources :user_callouts, controller: 'users/callouts', only: [:create] # remove after 14.6 2021-12-22 to handle mixed deployments
scope :ide, as: :ide, format: false do
get '/', to: 'ide#index'

View File

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

View File

@ -2,14 +2,13 @@
announcement_milestone: "14.5" # The milestone when this feature was first announced as deprecated.
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
body: | # Do not modify this line, instead modify the lines below.
Runner REST API will not return `paused` as a status in GitLab 15.0.
The GitLab Runner REST and GraphQL API endpoints will not return `paused` or `active` as a status in GitLab 15.0.
Paused runners' status will only relate to runner contact status, such as:
`online`, `offline`, or `not_connected`. Status `paused` will not appear when the runner is
not active.
A runner's status will only relate to runner contact status, such as:
`online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
When checking if a runner is `paused`, API users are advised to check the boolean attribute
`active` to be `false` instead.
`active` to be `false` instead. When checking if a runner is `active`, check if `active` is `true`.
stage: Verify
tiers: [Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344648

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;
CREATE TABLE lfs_object_states (
verification_started_at timestamp with time zone,
verification_retry_at timestamp with time zone,
verified_at timestamp with time zone,
lfs_object_id bigint NOT NULL,
verification_state smallint DEFAULT 0 NOT NULL,
verification_retry_count smallint,
verification_checksum bytea,
verification_failure text,
CONSTRAINT check_efe45a8ab3 CHECK ((char_length(verification_failure) <= 255))
);
CREATE SEQUENCE lfs_object_states_lfs_object_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE lfs_object_states_lfs_object_id_seq OWNED BY lfs_object_states.lfs_object_id;
CREATE TABLE lfs_objects (
id integer NOT NULL,
oid character varying NOT NULL,
@ -21795,6 +21816,8 @@ ALTER TABLE ONLY ldap_group_links ALTER COLUMN id SET DEFAULT nextval('ldap_grou
ALTER TABLE ONLY lfs_file_locks ALTER COLUMN id SET DEFAULT nextval('lfs_file_locks_id_seq'::regclass);
ALTER TABLE ONLY lfs_object_states ALTER COLUMN lfs_object_id SET DEFAULT nextval('lfs_object_states_lfs_object_id_seq'::regclass);
ALTER TABLE ONLY lfs_objects ALTER COLUMN id SET DEFAULT nextval('lfs_objects_id_seq'::regclass);
ALTER TABLE ONLY lfs_objects_projects ALTER COLUMN id SET DEFAULT nextval('lfs_objects_projects_id_seq'::regclass);
@ -23513,6 +23536,9 @@ ALTER TABLE ONLY ldap_group_links
ALTER TABLE ONLY lfs_file_locks
ADD CONSTRAINT lfs_file_locks_pkey PRIMARY KEY (id);
ALTER TABLE ONLY lfs_object_states
ADD CONSTRAINT lfs_object_states_pkey PRIMARY KEY (lfs_object_id);
ALTER TABLE ONLY lfs_objects
ADD CONSTRAINT lfs_objects_pkey PRIMARY KEY (id);
@ -26529,6 +26555,16 @@ CREATE UNIQUE INDEX index_lfs_file_locks_on_project_id_and_path ON lfs_file_lock
CREATE INDEX index_lfs_file_locks_on_user_id ON lfs_file_locks USING btree (user_id);
CREATE INDEX index_lfs_object_states_failed_verification ON lfs_object_states USING btree (verification_retry_at NULLS FIRST) WHERE (verification_state = 3);
CREATE INDEX index_lfs_object_states_needs_verification ON lfs_object_states USING btree (verification_state) WHERE ((verification_state = 0) OR (verification_state = 3));
CREATE INDEX index_lfs_object_states_on_lfs_object_id ON lfs_object_states USING btree (lfs_object_id);
CREATE INDEX index_lfs_object_states_on_verification_state ON lfs_object_states USING btree (verification_state);
CREATE INDEX index_lfs_object_states_pending_verification ON lfs_object_states USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0);
CREATE INDEX index_lfs_objects_on_file_store ON lfs_objects USING btree (file_store);
CREATE UNIQUE INDEX index_lfs_objects_on_oid ON lfs_objects USING btree (oid);
@ -30333,6 +30369,9 @@ ALTER TABLE ONLY description_versions
ALTER TABLE ONLY clusters_kubernetes_namespaces
ADD CONSTRAINT fk_rails_40cc7ccbc3 FOREIGN KEY (cluster_project_id) REFERENCES cluster_projects(id) ON DELETE SET NULL;
ALTER TABLE ONLY lfs_object_states
ADD CONSTRAINT fk_rails_4188448cd5 FOREIGN KEY (lfs_object_id) REFERENCES lfs_objects(id) ON DELETE CASCADE;
ALTER TABLE ONLY geo_node_namespace_links
ADD CONSTRAINT fk_rails_41ff5fb854 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;

View File

@ -37,7 +37,7 @@ verification methods:
| Git | Group wiki repository | Geo with Gitaly | _Not implemented_ |
| Blobs | User uploads _(file system)_ | Geo with API | _Not implemented_ |
| Blobs | User uploads _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
| Blobs | LFS objects _(file system)_ | Geo with API | _Not implemented_ |
| Blobs | LFS objects _(file system)_ | Geo with API | SHA256 checksum |
| Blobs | LFS objects _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
| Blobs | CI job artifacts _(file system)_ | Geo with API | _Not implemented_ |
| Blobs | CI job artifacts _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
@ -190,7 +190,7 @@ successfully, you must replicate their data using some other means.
|[Project wiki repository](../../../user/project/wiki/) | **Yes** (10.2) | **Yes** (10.7) | No | |
|[Group wiki repository](../../../user/project/wiki/group.md) | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | No | No | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. |
|[Uploads](../../uploads.md) | **Yes** (10.2) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817) | No | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. |
|[LFS objects](../../lfs/index.md) | **Yes** (10.2) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8922) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br><br>Behind feature flag `geo_lfs_object_replication`, enabled by default. |
|[LFS objects](../../lfs/index.md) | **Yes** (10.2) | **Yes**(14.6) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br /><br />Replication is behind the feature flag `geo_lfs_object_replication`, enabled by default. Verification is under development behind the feature flag `geo_lfs_object_verification` introduced in 14.6. |
|[Personal snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | No | |
|[Project snippets](../../../user/snippets.md) | **Yes** (10.2) | **Yes** (10.2) | No | |
|[CI job artifacts](../../../ci/pipelines/job_artifacts.md) | **Yes** (10.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923) | Via Object Storage provider if supported. Native Geo support (Beta). | Verified only manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. Job logs also verified on transfer. |

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_synced` | Gauge | 10.2 | Number of repositories synced on secondary | `url` |
| `geo_repositories_failed` | Gauge | 10.2 | Number of repositories failed to sync on secondary | `url` |
| `geo_lfs_objects` | Gauge | 10.2 | Total number of LFS objects available on primary | `url` |
| `geo_lfs_objects_synced` | Gauge | 10.2 | Number of LFS objects synced on secondary | `url` |
| `geo_lfs_objects_failed` | Gauge | 10.2 | Number of LFS objects failed to sync on secondary | `url` |
| `geo_lfs_objects` | Gauge | 10.2 | Number of LFS objects on primary | `url` |
| `geo_lfs_objects_checksummed` | Gauge | 14.6 | Number of LFS objects checksummed successfully on primary | `url` |
| `geo_lfs_objects_checksum_failed` | Gauge | 14.6 | Number of LFS objects failed to calculate the checksum on primary | `url` |
| `geo_lfs_objects_checksum_total` | Gauge | 14.6 | Number of LFS objects tried to checksum on primary | `url` |
| `geo_lfs_objects_synced` | Gauge | 10.2 | Number of syncable LFS objects synced on secondary | `url` |
| `geo_lfs_objects_failed` | Gauge | 10.2 | Number of syncable LFS objects failed to sync on secondary | `url` |
| `geo_lfs_objects_registry` | Gauge | 14.6 | Number of LFS objects in the registry | `url` |
| `geo_lfs_objects_verified` | Gauge | 14.6 | Number of LFS objects verified on secondary | `url` |
| `geo_lfs_objects_verification_failed` | Gauge | 14.6 | Number of LFS objects' verifications failed on secondary | `url` |
| `geo_lfs_objects_verification_total` | Gauge | 14.6 | Number of LFS objects' verifications tried on secondary | `url` |LFS objects failed to sync on secondary | `url` |
| `geo_attachments` | Gauge | 10.2 | Total number of file attachments available on primary | `url` |
| `geo_attachments_synced` | Gauge | 10.2 | Number of attachments synced on secondary | `url` |
| `geo_attachments_failed` | Gauge | 10.2 | Number of attachments failed to sync on secondary | `url` |
| `geo_last_event_id` | Gauge | 10.2 | Database ID of the latest event log entry on the primary | `url` |
| `geo_last_event_timestamp` | Gauge | 10.2 | UNIX timestamp of the latest event log entry on the primary | `url` |
| `geo_cursor_last_event_id` | Gauge | 10.2 | Last database ID of the event log processed by the secondary | `url` |
| `geo_cursor_last_event_timestamp` | Gauge | 10.2 | Last UNIX timestamp of the event log processed by the secondary | `url` |
| `geo_status_failed_total` | Counter | 10.2 | Number of times retrieving the status from the Geo Node failed | `url` |
| `geo_last_successful_status_check_timestamp` | Gauge | 10.2 | Last timestamp when the status was successfully updated | `url` |
| `geo_lfs_objects_synced_missing_on_primary` | Gauge | 10.7 | Number of LFS objects marked as synced due to the file missing on the primary | `url` |
| `geo_job_artifacts_synced_missing_on_primary` | Gauge | 10.7 | Number of job artifacts marked as synced due to the file missing on the primary | `url` |
| `geo_repositories_checksummed` | Gauge | 10.7 | Number of repositories checksummed on primary | `url` |
| `geo_repositories_checksum_failed` | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | `url` |

View File

@ -307,11 +307,18 @@ Example response:
"health_status": "Healthy",
"missing_oauth_application": false,
"db_replication_lag_seconds": null,
"lfs_objects_count": 0,
"lfs_objects_count": 5,
"lfs_objects_checksum_total_count": 5,
"lfs_objects_checksummed_count": 5,
"lfs_objects_checksum_failed_count": 0,
"lfs_objects_synced_count": null,
"lfs_objects_failed_count": null,
"lfs_objects_synced_missing_on_primary_count": 0,
"lfs_objects_registry_count": null,
"lfs_objects_verification_total_count": null,
"lfs_objects_verified_count": null,
"lfs_objects_verification_failed_count": null,
"lfs_objects_synced_in_percentage": "0.00%",
"lfs_objects_verified_in_percentage": "0.00%",
"job_artifacts_count": 2,
"job_artifacts_synced_count": null,
"job_artifacts_failed_count": null,
@ -468,11 +475,18 @@ Example response:
"health_status": "Healthy",
"missing_oauth_application": false,
"db_replication_lag_seconds": 0,
"lfs_objects_count": 0,
"lfs_objects_synced_count": 0,
"lfs_objects_failed_count": 0,
"lfs_objects_synced_missing_on_primary_count": 0,
"lfs_objects_count": 5,
"lfs_objects_checksum_total_count": 5,
"lfs_objects_checksummed_count": 5,
"lfs_objects_checksum_failed_count": 0,
"lfs_objects_synced_count": null,
"lfs_objects_failed_count": null,
"lfs_objects_registry_count": null,
"lfs_objects_verification_total_count": null,
"lfs_objects_verified_count": null,
"lfs_objects_verification_failed_count": null,
"lfs_objects_synced_in_percentage": "0.00%",
"lfs_objects_verified_in_percentage": "0.00%",
"job_artifacts_count": 2,
"job_artifacts_synced_count": 1,
"job_artifacts_failed_count": 1,
@ -633,11 +647,18 @@ Example response:
"health_status": "Healthy",
"missing_oauth_application": false,
"db_replication_lag_seconds": 0,
"lfs_objects_count": 0,
"lfs_objects_synced_count": 0,
"lfs_objects_failed_count": 0,
"lfs_objects_synced_missing_on_primary_count": 0,
"lfs_objects_count": 5,
"lfs_objects_checksum_total_count": 5,
"lfs_objects_checksummed_count": 5,
"lfs_objects_checksum_failed_count": 0,
"lfs_objects_synced_count": null,
"lfs_objects_failed_count": null,
"lfs_objects_registry_count": null,
"lfs_objects_verification_total_count": null,
"lfs_objects_verified_count": null,
"lfs_objects_verification_failed_count": null,
"lfs_objects_synced_in_percentage": "0.00%",
"lfs_objects_verified_in_percentage": "0.00%",
"job_artifacts_count": 2,
"job_artifacts_synced_count": 1,
"job_artifacts_failed_count": 1,

View File

@ -15975,7 +15975,8 @@ Values for sorting runners.
| Value | Description |
| ----- | ----------- |
| <a id="cirunnerstatusactive"></a>`ACTIVE` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` **{warning-solid}** | **Deprecated** in 14.6. This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period of no contact. |
| <a id="cirunnerstatusnever_contacted"></a>`NEVER_CONTACTED` | Runner that has never contacted this instance. Set legacyMode to null to utilize this value. Will replace NOT_CONNECTED starting in 15.0. |
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` **{warning-solid}** | **Deprecated** in 14.6. Use NEVER_CONTACTED instead. NEVER_CONTACTED will have a slightly different scope starting in 15.0, with STALE being returned instead after 3 months of no contact. |
| <a id="cirunnerstatusoffline"></a>`OFFLINE` **{warning-solid}** | **Deprecated** in 14.6. This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period offline. |
| <a id="cirunnerstatusonline"></a>`ONLINE` | Runner that contacted this instance within the last 2 hours. |
| <a id="cirunnerstatuspaused"></a>`PAUSED` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |

View File

@ -124,6 +124,15 @@ Long term service and support (LTSS) for SUSE Linux Enterprise Server (SLES) 12
Announced: 2021-11-22
### Deprecation of Runner status `not_connected` API value
The GitLab Runner REST and GraphQL [API](https://docs.gitlab.com/ee/api/runners.html#runners-api) endpoints
will return `never_contacted` instead of `not_connected` as the status values in 15.0.
Runners that have never contacted the GitLab instance will also return `stale` if created more than 3 months ago.
Announced: 2021-12-22
### Deprecation of bundler-audit Dependency Scanning tool
As of 14.6 bundler-audit is being deprecated from Dependency Scanning. It will continue to be in our CI/CD template while deprecated. We are removing bundler-audit from Dependency Scanning on May 22, 2022 in 15.0. After this removal Ruby scanning functionality will not be affected as it is still being covered by Gemnasium.
@ -200,14 +209,13 @@ Announced: 2021-11-22
### REST API Runner will not contain `paused`
Runner REST API will not return `paused` as a status in GitLab 15.0.
The GitLab Runner REST and GraphQL API endpoints will not return `paused` or `active` as a status in GitLab 15.0.
Paused runners' status will only relate to runner contact status, such as:
`online`, `offline`, or `not_connected`. Status `paused` will not appear when the runner is
not active.
A runner's status will only relate to runner contact status, such as:
`online`, `offline`, or `not_connected`. Status `paused` or `active` will no longer appear.
When checking if a runner is `paused`, API users are advised to check the boolean attribute
`active` to be `false` instead.
`active` to be `false` instead. When checking if a runner is `active`, check if `active` is `true`.
Announced: 2021-11-22

View File

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

View File

@ -42,11 +42,11 @@ module Banzai
def initialize(context)
@context = context
@renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer)
@renderer = Banzai::Renderer::CommonMark::HTML.new(options: render_options) if Feature.disabled?(:use_cmark_renderer, default_enabled: :yaml)
end
def render(text)
if Feature.enabled?(:use_cmark_renderer)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
CommonMarker.render_html(text, render_options, extensions)
else
doc = CommonMarker.render_doc(text, PARSE_OPTIONS, extensions)
@ -58,7 +58,7 @@ module Banzai
private
def extensions
if Feature.enabled?(:use_cmark_renderer)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
EXTENSIONS
else
EXTENSIONS + [
@ -72,7 +72,7 @@ module Banzai
end
def render_options_no_sourcepos
Feature.enabled?(:use_cmark_renderer) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) ? RENDER_OPTIONS_C : RENDER_OPTIONS_RUBY
end
def render_options_sourcepos

View File

@ -42,7 +42,7 @@ module Banzai
private
def lang_tag
if Feature.enabled?(:use_cmark_renderer)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Gitlab::Utils::Nokogiri.css_to_xpath('pre')
else
Gitlab::Utils::Nokogiri.css_to_xpath('code')

View File

@ -26,7 +26,7 @@ module Banzai
def lang_tag
@lang_tag ||=
if Feature.enabled?(:use_cmark_renderer)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
Gitlab::Utils::Nokogiri.css_to_xpath('pre[lang="plantuml"] > code').freeze
else
Gitlab::Utils::Nokogiri.css_to_xpath('pre > code[lang="plantuml"]').freeze

View File

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

View File

@ -70,7 +70,7 @@ module Banzai
private
def parse_lang_params(node)
node = node.parent if Feature.enabled?(:use_cmark_renderer)
node = node.parent if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
# Commonmarker's FULL_INFO_STRING render option works with the space delimiter.
# But the current behavior of GitLab's markdown renderer is different - it grabs everything as the single
@ -92,7 +92,7 @@ module Banzai
language, language_params = language.split(LANG_PARAMS_DELIMITER, 2)
if Feature.enabled?(:use_cmark_renderer)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
language_params = [node.attr('data-meta'), language_params].compact.join(' ')
end

View File

@ -7,7 +7,7 @@ module Gitlab
register_for 'gitlab-html-pipeline'
def format(node, lang, opts)
if Feature.enabled?(:use_cmark_renderer)
if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml)
%(<pre #{lang ? %[lang="#{lang}"] : ''}><code>#{node.content}</code></pre>)
else
%(<pre><code #{lang ? %[ lang="#{lang}"] : ''}>#{node.content}</code></pre>)

View File

@ -16,7 +16,11 @@ module Gitlab
def details_path
return unless can?(user, :read_pipeline, downstream_pipeline)
project_pipeline_path(downstream_project, downstream_pipeline)
if Feature.enabled?(:ci_retry_downstream_pipeline, subject.project, default_enabled: :yaml)
project_job_path(subject.project, subject)
else
project_pipeline_path(downstream_project, downstream_pipeline)
end
end
def has_action?

View File

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

View File

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

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
old_blob_lazy
preprocess_before_diff(diff) if Feature.enabled?(:jupyter_clean_diffs, repository.project, default_enabled: true)
diff.diff = Gitlab::Diff::CustomDiff.preprocess_before_diff(diff.new_path, old_blob_lazy, new_blob_lazy) || diff.diff if use_custom_diff?
end
def use_custom_diff?
strong_memoize(:_custom_diff_enabled) { Feature.enabled?(:jupyter_clean_diffs, repository.project, default_enabled: true) }
end
def position(position_marker, position_type: :text)
@ -450,33 +454,6 @@ module Gitlab
find_renderable_viewer_class(classes)
end
def preprocess_before_diff(diff)
return unless diff.new_path.ends_with? '.ipynb'
from = old_blob_lazy&.data
to = new_blob_lazy&.data
transformed_diff = IpynbDiff.diff(from, to,
diff_opts: { context: 5, include_diff_info: true },
transform_options: { cell_decorator: :percent },
raise_if_invalid_notebook: true)
new_diff = strip_diff_frontmatter(transformed_diff)
if new_diff
diff.diff = new_diff
new_blob_lazy.transformed_for_diff = true if new_blob_lazy
old_blob_lazy.transformed_for_diff = true if old_blob_lazy
end
Gitlab::AppLogger.info({ message: new_diff ? 'IPYNB_DIFF_GENERATED' : 'IPYNB_DIFF_NIL' })
rescue IpynbDiff::InvalidNotebookError => e
Gitlab::ErrorTracking.log_exception(e)
end
def strip_diff_frontmatter(diff_content)
diff_content.scan(/.*\n/)[2..-1]&.join('') if diff_content.present?
end
def alternate_viewer_class
return unless viewer.instance_of?(DiffViewer::Renamed)

View File

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

View File

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

View File

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

View File

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

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