mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-08-01 16:04:19 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
@ -2,6 +2,7 @@ import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import errorTrackingStore from '~/error_tracking/store';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
|
||||
import IssueApp from './components/app.vue';
|
||||
import HeaderActions from './components/header_actions.vue';
|
||||
import IncidentTabs from './components/incidents/incident_tabs.vue';
|
||||
@ -73,6 +74,10 @@ export function initIssueApp(issueData, store) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (gon?.features?.fixCommentScroll) {
|
||||
scrollToTargetOnResize();
|
||||
}
|
||||
|
||||
bootstrapApollo({ ...issueState, issueType: el.dataset.issueType });
|
||||
|
||||
const { canCreateIncident, ...issueProps } = issueData;
|
||||
|
@ -181,6 +181,7 @@ export const contentTop = () => {
|
||||
},
|
||||
() => getOuterHeight('.merge-request-tabs'),
|
||||
() => getOuterHeight('.js-diff-files-changed'),
|
||||
() => getOuterHeight('.issue-sticky-header.gl-fixed'),
|
||||
({ desktop }) => {
|
||||
const diffsTabIsActive = window.mrTabs?.currentAction === 'diffs';
|
||||
let size;
|
||||
|
58
app/assets/javascripts/lib/utils/resize_observer.js
Normal file
58
app/assets/javascripts/lib/utils/resize_observer.js
Normal file
@ -0,0 +1,58 @@
|
||||
import { contentTop } from './common_utils';
|
||||
|
||||
const interactionEvents = ['mousedown', 'touchstart', 'keydown', 'wheel'];
|
||||
|
||||
export function createResizeObserver() {
|
||||
return new ResizeObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
entry.target.dispatchEvent(new CustomEvent(`ResizeUpdate`, { detail: { entry } }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// watches for change in size of a container element (e.g. for lazy-loaded images)
|
||||
// and scroll the target element to the top of the content area
|
||||
// stop watching after any user input. So if user opens sidebar or manually
|
||||
// scrolls the page we don't hijack their scroll position
|
||||
export function scrollToTargetOnResize({
|
||||
target = window.location.hash,
|
||||
container = '#content-body',
|
||||
} = {}) {
|
||||
if (!target) return null;
|
||||
|
||||
const ro = createResizeObserver();
|
||||
const containerEl = document.querySelector(container);
|
||||
let interactionListenersAdded = false;
|
||||
|
||||
function keepTargetAtTop() {
|
||||
const anchorEl = document.querySelector(target);
|
||||
|
||||
if (!anchorEl) return;
|
||||
|
||||
const anchorTop = anchorEl.getBoundingClientRect().top + window.scrollY;
|
||||
const top = anchorTop - contentTop();
|
||||
document.documentElement.scrollTo({
|
||||
top,
|
||||
});
|
||||
|
||||
if (!interactionListenersAdded) {
|
||||
interactionEvents.forEach((event) =>
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
document.addEventListener(event, removeListeners),
|
||||
);
|
||||
interactionListenersAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
function removeListeners() {
|
||||
interactionEvents.forEach((event) => document.removeEventListener(event, removeListeners));
|
||||
|
||||
ro.unobserve(containerEl);
|
||||
containerEl.removeEventListener('ResizeUpdate', keepTargetAtTop);
|
||||
}
|
||||
|
||||
containerEl.addEventListener('ResizeUpdate', keepTargetAtTop);
|
||||
|
||||
ro.observe(containerEl);
|
||||
return ro;
|
||||
}
|
@ -53,6 +53,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||
push_frontend_feature_flag(:confidential_notes, project&.group, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:fix_comment_scroll, @project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
|
||||
|
@ -41,9 +41,7 @@ module Clusters
|
||||
end
|
||||
|
||||
def prepare_uninstall
|
||||
::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350180') do
|
||||
runner&.update!(active: false)
|
||||
end
|
||||
# No op, see https://gitlab.com/gitlab-org/gitlab/-/issues/350180.
|
||||
end
|
||||
|
||||
def post_uninstall
|
||||
|
8
config/feature_flags/development/fix_comment_scroll.yml
Normal file
8
config/feature_flags/development/fix_comment_scroll.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
name: fix_comment_scroll
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76340
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349638
|
||||
milestone: '14.7'
|
||||
type: development
|
||||
group: group::project management
|
||||
default_enabled: false
|
@ -35,7 +35,7 @@ sudo -u git -H bundle exec rails console -e production
|
||||
|
||||
**For Kubernetes deployments**
|
||||
|
||||
The console is in the task-runner pod. Refer to our [Kubernetes cheat sheet](../troubleshooting/kubernetes_cheat_sheet.md#gitlab-specific-kubernetes-information) for details.
|
||||
The console is in the toolbox pod. Refer to our [Kubernetes cheat sheet](../troubleshooting/kubernetes_cheat_sheet.md#gitlab-specific-kubernetes-information) for details.
|
||||
|
||||
To exit the console, type: `quit`.
|
||||
|
||||
|
@ -147,10 +147,10 @@ Secrets such as credential files, SSH private keys, and other files containing s
|
||||
GitLab enables you to turn on a predefined denylist of files which can't be
|
||||
pushed to a repository. The list stops those commits from reaching the remote repository.
|
||||
|
||||
By selecting the checkbox *Prevent committing secrets to Git*, GitLab prevents
|
||||
By selecting the checkbox *Prevent pushing secret files*, GitLab prevents
|
||||
pushes to the repository when a file matches a regular expression as read from
|
||||
[`files_denylist.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/checks/files_denylist.yml) (make sure you are at the right branch
|
||||
as your GitLab version when viewing this file).
|
||||
as your GitLab version when viewing this file). This checkbox is able to be set globally via **Admin Area > Push Rules**, or per-project via **Settings > Repository > Push Rules**.
|
||||
|
||||
NOTE:
|
||||
Files already committed aren't restricted by this push rule.
|
||||
|
33
spec/features/issues/user_scrolls_to_deeplinked_note_spec.rb
Normal file
33
spec/features/issues/user_scrolls_to_deeplinked_note_spec.rb
Normal file
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User scrolls to deep-linked note' do
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:comment_1) { create(:note_on_issue, noteable: issue, project: project, note: 'written first') }
|
||||
let_it_be(:comments) { create_list(:note_on_issue, 20, noteable: issue, project: project, note: 'spacer note') }
|
||||
|
||||
context 'on issue page', :js do
|
||||
it 'on comment' do
|
||||
visit project_issue_path(project, issue, anchor: "note_#{comment_1.id}")
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(first_comment).to have_content(comment_1.note)
|
||||
|
||||
bottom_of_title = find('.issue-sticky-header.gl-fixed').evaluate_script("this.getBoundingClientRect().bottom;")
|
||||
top = first_comment.evaluate_script("this.getBoundingClientRect().top;")
|
||||
|
||||
expect(top).to be_within(1).of(bottom_of_title)
|
||||
end
|
||||
end
|
||||
|
||||
def all_comments
|
||||
all('.timeline > .note.timeline-entry')
|
||||
end
|
||||
|
||||
def first_comment
|
||||
all_comments.first
|
||||
end
|
||||
end
|
68
spec/frontend/lib/utils/resize_observer_spec.js
Normal file
68
spec/frontend/lib/utils/resize_observer_spec.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { contentTop } from '~/lib/utils/common_utils';
|
||||
import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
|
||||
|
||||
jest.mock('~/lib/utils/common_utils');
|
||||
|
||||
function mockStickyHeaderSize(val) {
|
||||
contentTop.mockReturnValue(val);
|
||||
}
|
||||
|
||||
describe('ResizeObserver Utility', () => {
|
||||
let observer;
|
||||
const triggerResize = () => {
|
||||
const entry = document.querySelector('#content-body');
|
||||
entry.dispatchEvent(new CustomEvent(`ResizeUpdate`, { detail: { entry } }));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockStickyHeaderSize(90);
|
||||
|
||||
jest.spyOn(document.documentElement, 'scrollTo');
|
||||
|
||||
setFixtures(`<div id="content-body"><div class="target">element to scroll to</div></div>`);
|
||||
|
||||
const target = document.querySelector('.target');
|
||||
|
||||
jest.spyOn(target, 'getBoundingClientRect').mockReturnValue({ top: 200 });
|
||||
|
||||
observer = scrollToTargetOnResize({
|
||||
target: '.target',
|
||||
container: '#content-body',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
contentTop.mockReset();
|
||||
});
|
||||
|
||||
describe('Observer behavior', () => {
|
||||
it('returns null for empty target', () => {
|
||||
observer = scrollToTargetOnResize({
|
||||
target: '',
|
||||
container: '#content-body',
|
||||
});
|
||||
|
||||
expect(observer).toBe(null);
|
||||
});
|
||||
|
||||
it('returns ResizeObserver instance', () => {
|
||||
expect(observer).toBeInstanceOf(ResizeObserver);
|
||||
});
|
||||
|
||||
it('scrolls body so anchor is just below sticky header (contentTop)', () => {
|
||||
triggerResize();
|
||||
|
||||
expect(document.documentElement.scrollTo).toHaveBeenCalledWith({ top: 110 });
|
||||
});
|
||||
|
||||
const interactionEvents = ['mousedown', 'touchstart', 'keydown', 'wheel'];
|
||||
it.each(interactionEvents)('does not hijack scroll after user input from %s', (eventType) => {
|
||||
const event = new Event(eventType);
|
||||
document.dispatchEvent(event);
|
||||
|
||||
triggerResize();
|
||||
|
||||
expect(document.documentElement.scrollTo).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
@ -101,19 +101,6 @@ RSpec.describe Clusters::Applications::Runner do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#prepare_uninstall' do
|
||||
it 'pauses associated runner' do
|
||||
active_runner = create(:ci_runner, contacted_at: 1.second.ago)
|
||||
|
||||
expect(active_runner.active).to be_truthy
|
||||
|
||||
application_runner = create(:clusters_applications_runner, :scheduled, runner: active_runner)
|
||||
application_runner.prepare_uninstall
|
||||
|
||||
expect(active_runner.active).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#make_uninstalling!' do
|
||||
subject { create(:clusters_applications_runner, :scheduled, runner: ci_runner) }
|
||||
|
||||
|
Reference in New Issue
Block a user