Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-16 21:09:24 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-16 21:09:24 +0300
commit1eec6b22b26d09ce6927adf66f98d755a6339815 (patch)
treeb1bb8bbdc0d49136bfd176a1e64d3bd9f2969396 /app
parentb4e854a900ba9bcbfc3476f88317c59ea048daaf (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clone_panel.js5
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue4
-rw-r--r--app/assets/javascripts/ide/constants.js4
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js4
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js150
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js15
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js10
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js88
-rw-r--r--app/assets/javascripts/import_entities/import_groups/index.js2
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue10
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue4
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js12
-rw-r--r--app/assets/javascripts/merge_request_tabs.js19
-rw-r--r--app/assets/javascripts/pages/import/bulk_imports/status/index.js (renamed from app/assets/javascripts/pages/import/bulk_imports/index.js)0
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/controllers/import/bulk_imports_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/repositories/git_http_controller.rb2
-rw-r--r--app/finders/ci/jobs_finder.rb3
-rw-r--r--app/finders/deployments_finder.rb25
-rw-r--r--app/graphql/mutations/merge_requests/update.rb11
-rw-r--r--app/graphql/types/merge_request_state_event_enum.rb16
-rw-r--r--app/helpers/labels_helper.rb42
-rw-r--r--app/models/clusters/agent_token.rb1
-rw-r--r--app/models/commit_status.rb24
-rw-r--r--app/models/deployment.rb8
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/services/bulk_import_service.rb2
-rw-r--r--app/services/merge_requests/reload_merge_head_diff_service.rb2
-rw-r--r--app/views/import/bulk_imports/status.html.haml1
-rw-r--r--app/views/projects/buttons/_clone.html.haml17
-rw-r--r--app/views/projects/buttons/_xcode_link.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml5
-rw-r--r--app/views/shared/notifications/_new_button.html.haml6
-rw-r--r--app/workers/pages_transfer_worker.rb2
37 files changed, 269 insertions, 246 deletions
diff --git a/app/assets/javascripts/clone_panel.js b/app/assets/javascripts/clone_panel.js
index 00bf54e1478..c9fae8f17a4 100644
--- a/app/assets/javascripts/clone_panel.js
+++ b/app/assets/javascripts/clone_panel.js
@@ -18,6 +18,11 @@ export default function initClonePanel() {
e.preventDefault();
const $this = $(e.currentTarget);
const url = $this.attr('href');
+ if (url && (url.startsWith('vscode://') || url.startsWith('xcode://'))) {
+ // Clone with "..." should open like a normal link
+ return;
+ }
+ e.preventDefault();
const cloneType = $this.data('cloneType');
$('.is-active', $cloneOptions).removeClass('is-active');
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index bd8d2d6b8f2..6b1e2bfb34e 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -1,7 +1,6 @@
<script>
import { GlTooltipDirective, GlLink, GlButton, GlSprintf } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
-import { polyfillSticky } from '~/lib/utils/sticky';
import { __ } from '~/locale';
import { CENTERED_LIMITED_CONTAINER_CLASSES, EVT_EXPAND_ALL_FILES } from '../constants';
import eventHub from '../event_hub';
@@ -61,9 +60,6 @@ export default {
created() {
this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
},
- mounted() {
- polyfillSticky(this.$el);
- },
methods: {
...mapActions('diffs', ['setInlineDiffViewType', 'setParallelDiffViewType', 'setShowTreeList']),
expandAllFiles() {
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 6bd74b143e2..ed6b750480b 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -107,3 +107,7 @@ export const SIDE_RIGHT = 'right';
// Live Preview feature
export const LIVE_PREVIEW_DEBOUNCE = 2000;
+
+// This is the maximum number of files to auto open when opening the Web IDE
+// from a Merge Request
+export const MAX_MR_FILES_AUTO_OPEN = 10;
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index aa2e3d32b59..d1e40920ebc 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -120,10 +120,6 @@ export const getFileData = (
});
};
-export const setFileMrChange = ({ commit }, { file, mrChange }) => {
- commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange });
-};
-
export const getRawFileData = ({ state, commit, dispatch, getters }, { path }) => {
const file = state.entries[path];
const stagedFile = state.stagedFiles.find((f) => f.path === path);
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 8dcc420f156..753f6b9cd47 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -1,6 +1,6 @@
import { deprecatedCreateFlash as flash } from '~/flash';
import { __ } from '~/locale';
-import { leftSidebarViews, PERMISSION_READ_MR } from '../../constants';
+import { leftSidebarViews, PERMISSION_READ_MR, MAX_MR_FILES_AUTO_OPEN } from '../../constants';
import service from '../../services';
import * as types from '../mutation_types';
@@ -147,70 +147,96 @@ export const getMergeRequestVersions = (
}
});
-export const openMergeRequest = (
- { dispatch, state, getters },
- { projectId, targetProjectId, mergeRequestId } = {},
-) =>
- dispatch('getMergeRequestData', {
- projectId,
- targetProjectId,
- mergeRequestId,
- })
- .then((mr) => {
- dispatch('setCurrentBranchId', mr.source_branch);
-
- return dispatch('getBranchData', {
- projectId,
- branchId: mr.source_branch,
- }).then(() => {
- const branch = getters.findBranch(projectId, mr.source_branch);
-
- return dispatch('getFiles', {
- projectId,
- branchId: mr.source_branch,
- ref: branch.commit.id,
+export const openMergeRequestChanges = async ({ dispatch, getters, state, commit }, changes) => {
+ const entryChanges = changes
+ .map((change) => ({ entry: state.entries[change.new_path], change }))
+ .filter((x) => x.entry);
+
+ const pathsToOpen = entryChanges
+ .slice(0, MAX_MR_FILES_AUTO_OPEN)
+ .map(({ change }) => change.new_path);
+
+ // If there are no changes with entries, do nothing.
+ if (!entryChanges.length) {
+ return;
+ }
+
+ dispatch('updateActivityBarView', leftSidebarViews.review.name);
+
+ entryChanges.forEach(({ change, entry }) => {
+ commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file: entry, mrChange: change });
+ });
+
+ // Open paths in order as they appear in MR changes
+ pathsToOpen.forEach((path) => {
+ commit(types.TOGGLE_FILE_OPEN, path);
+ });
+
+ // Activate first path.
+ // We don't `getFileData` here since the editor component kicks that off. Otherwise, we'd fetch twice.
+ const [firstPath, ...remainingPaths] = pathsToOpen;
+ await dispatch('router/push', getters.getUrlForPath(firstPath));
+ await dispatch('setFileActive', firstPath);
+
+ // Lastly, eagerly fetch the remaining paths for improved user experience.
+ await Promise.all(
+ remainingPaths.map(async (path) => {
+ try {
+ await dispatch('getFileData', {
+ path,
+ makeFileActive: false,
});
- });
- })
- .then(() =>
- dispatch('getMergeRequestVersions', {
- projectId,
- targetProjectId,
- mergeRequestId,
- }),
- )
- .then(() =>
- dispatch('getMergeRequestChanges', {
- projectId,
- targetProjectId,
- mergeRequestId,
- }),
- )
- .then((mrChanges) => {
- if (mrChanges.changes.length) {
- dispatch('updateActivityBarView', leftSidebarViews.review.name);
+ await dispatch('getRawFileData', { path });
+ } catch (e) {
+ // If one of the file fetches fails, we dont want to blow up the rest of them.
+ // eslint-disable-next-line no-console
+ console.error('[gitlab] An unexpected error occurred fetching MR file data', e);
}
+ }),
+ );
+};
- mrChanges.changes.forEach((change, ind) => {
- const changeTreeEntry = state.entries[change.new_path];
+export const openMergeRequest = async (
+ { dispatch, getters },
+ { projectId, targetProjectId, mergeRequestId } = {},
+) => {
+ try {
+ const mr = await dispatch('getMergeRequestData', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ });
- if (changeTreeEntry) {
- dispatch('setFileMrChange', {
- file: changeTreeEntry,
- mrChange: change,
- });
+ dispatch('setCurrentBranchId', mr.source_branch);
- if (ind < 10) {
- dispatch('getFileData', {
- path: change.new_path,
- makeFileActive: ind === 0,
- openFile: true,
- });
- }
- }
- });
- })
- .catch((e) => {
- flash(__('Error while loading the merge request. Please try again.'));
- throw e;
+ await dispatch('getBranchData', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+
+ const branch = getters.findBranch(projectId, mr.source_branch);
+
+ await dispatch('getFiles', {
+ projectId,
+ branchId: mr.source_branch,
+ ref: branch.commit.id,
});
+
+ await dispatch('getMergeRequestVersions', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ });
+
+ const { changes } = await dispatch('getMergeRequestChanges', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ });
+
+ await dispatch('openMergeRequestChanges', changes);
+ } catch (e) {
+ flash(__('Error while loading the merge request. Please try again.'));
+ throw e;
+ }
+};
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
index 651fc907611..8110934efc4 100644
--- a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js
@@ -25,6 +25,14 @@ export function createResolvers({ endpoints }) {
data: { availableNamespaces },
} = await client.query({ query: availableNamespacesQuery });
+ if (!statusPoller) {
+ statusPoller = new StatusPoller({
+ client,
+ pollPath: endpoints.jobs,
+ });
+ statusPoller.startPolling();
+ }
+
return axios
.get(endpoints.status, {
params: {
@@ -83,7 +91,7 @@ export function createResolvers({ endpoints }) {
const group = groupManager.findById(sourceGroupId);
groupManager.setImportStatus(group, STATUSES.SCHEDULING);
try {
- await axios.post(endpoints.createBulkImport, {
+ const response = await axios.post(endpoints.createBulkImport, {
bulk_import: [
{
source_type: 'group_entity',
@@ -94,10 +102,7 @@ export function createResolvers({ endpoints }) {
],
});
groupManager.setImportStatus(group, STATUSES.STARTED);
- if (!statusPoller) {
- statusPoller = new StatusPoller({ client, interval: 3000 });
- statusPoller.startPolling();
- }
+ SourceGroupsManager.attachImportId(group, response.data.id);
} catch (e) {
createFlash({
message: s__('BulkImport|Importing the group failed'),
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js b/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
index 047b04fe7d6..261e30edbbb 100644
--- a/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
@@ -14,6 +14,12 @@ function generateGroupId(id) {
}
export class SourceGroupsManager {
+ static importMap = new Map();
+
+ static attachImportId(group, importId) {
+ SourceGroupsManager.importMap.set(importId, group.id);
+ }
+
constructor({ client }) {
this.client = client;
}
@@ -36,6 +42,10 @@ export class SourceGroupsManager {
this.update(group, fn);
}
+ findByImportId(importId) {
+ return this.findById(SourceGroupsManager.importMap.get(importId));
+ }
+
setImportStatus(group, status) {
this.update(group, (sourceGroup) => {
// eslint-disable-next-line no-param-reassign
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js b/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js
index 960126cfa6d..63cd6b48fc4 100644
--- a/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js
@@ -1,71 +1,47 @@
-import gql from 'graphql-tag';
+import Visibility from 'visibilityjs';
import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import Poll from '~/lib/utils/poll';
import { s__ } from '~/locale';
-import { STATUSES } from '../../../constants';
-import bulkImportSourceGroupsQuery from '../queries/bulk_import_source_groups.query.graphql';
import { SourceGroupsManager } from './source_groups_manager';
-const groupId = (i) => `group${i}`;
-
-function generateGroupsQuery(groups) {
- return gql`{
- ${groups
- .map(
- (g, idx) =>
- `${groupId(idx)}: group(fullPath: "${g.import_target.target_namespace}/${
- g.import_target.new_name
- }") { id }`,
- )
- .join('\n')}
- }`;
-}
-
export class StatusPoller {
- constructor({ client, interval }) {
+ constructor({ client, pollPath }) {
this.client = client;
- this.interval = interval;
- this.timeoutId = null;
- this.groupManager = new SourceGroupsManager({ client });
- }
- startPolling() {
- if (this.timeoutId) {
- return;
- }
+ this.eTagPoll = new Poll({
+ resource: {
+ fetchJobs: () => axios.get(pollPath),
+ },
+ method: 'fetchJobs',
+ successCallback: ({ data }) => this.updateImportsStatuses(data),
+ errorCallback: () =>
+ createFlash({
+ message: s__('BulkImport|Update of import statuses with realtime changes failed'),
+ }),
+ });
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ this.eTagPoll.restart();
+ } else {
+ this.eTagPoll.stop();
+ }
+ });
- this.checkPendingImports();
+ this.groupManager = new SourceGroupsManager({ client });
}
- stopPolling() {
- clearTimeout(this.timeoutId);
- this.timeoutId = null;
+ startPolling() {
+ this.eTagPoll.makeRequest();
}
- async checkPendingImports() {
- try {
- const { bulkImportSourceGroups } = this.client.readQuery({
- query: bulkImportSourceGroupsQuery,
- });
-
- const groupsInProgress = bulkImportSourceGroups.nodes.filter(
- (g) => g.status === STATUSES.STARTED,
- );
- if (groupsInProgress.length) {
- const { data: results } = await this.client.query({
- query: generateGroupsQuery(groupsInProgress),
- fetchPolicy: 'no-cache',
- });
- const completedGroups = groupsInProgress.filter((_, idx) => Boolean(results[groupId(idx)]));
- completedGroups.forEach((group) => {
- this.groupManager.setImportStatus(group, STATUSES.FINISHED);
- });
+ async updateImportsStatuses(importStatuses) {
+ importStatuses.forEach(({ id, status_name: statusName }) => {
+ const group = this.groupManager.findByImportId(id);
+ if (group.id) {
+ this.groupManager.setImportStatus(group, statusName);
}
- } catch (e) {
- createFlash({
- message: s__('BulkImport|Update of import statuses with realtime changes failed'),
- });
- } finally {
- this.timeoutId = setTimeout(() => this.checkPendingImports(), this.interval);
- }
+ });
}
}
diff --git a/app/assets/javascripts/import_entities/import_groups/index.js b/app/assets/javascripts/import_entities/import_groups/index.js
index cd646befaaa..cd837a840e4 100644
--- a/app/assets/javascripts/import_entities/import_groups/index.js
+++ b/app/assets/javascripts/import_entities/import_groups/index.js
@@ -14,6 +14,7 @@ export function mountImportGroupsApp(mountElement) {
statusPath,
availableNamespacesPath,
createBulkImportPath,
+ jobsPath,
sourceUrl,
} = mountElement.dataset;
const apolloProvider = new VueApollo({
@@ -22,6 +23,7 @@ export function mountImportGroupsApp(mountElement) {
status: statusPath,
availableNamespaces: availableNamespacesPath,
createBulkImport: createBulkImportPath,
+ jobs: jobsPath,
},
}),
});
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 7107380b146..91ab68d5f39 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -4,7 +4,6 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { throttle, isEmpty } from 'lodash';
import { mapGetters, mapState, mapActions } from 'vuex';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
-import { polyfillSticky } from '~/lib/utils/sticky';
import { sprintf } from '~/locale';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import delayedJobMixin from '../mixins/delayed_job_mixin';
@@ -135,14 +134,6 @@ export default {
this.fetchJobsForStage(defaultStage);
}
}
-
- if (newVal.archived) {
- this.$nextTick(() => {
- if (this.$refs.sticky) {
- polyfillSticky(this.$refs.sticky);
- }
- });
- }
},
},
created() {
@@ -265,7 +256,6 @@ export default {
<div
v-if="job.archived"
- ref="sticky"
class="gl-mt-3 archived-job"
:class="{ 'sticky-top border-bottom-0': hasTrace }"
data-testid="archived-job"
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 2453fea9e58..fbdbfddff56 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -2,7 +2,6 @@
/* eslint-disable vue/no-v-html */
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils';
-import { polyfillSticky } from '~/lib/utils/sticky';
import { __, sprintf } from '~/locale';
import scrollDown from '../svg/scroll_down.svg';
@@ -54,9 +53,6 @@ export default {
});
},
},
- mounted() {
- polyfillSticky(this.$el);
- },
methods: {
handleScrollToTop() {
this.$emit('scrollJobLogTop');
diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js
index 6bb7f09b886..a6d53358cb8 100644
--- a/app/assets/javascripts/lib/utils/sticky.js
+++ b/app/assets/javascripts/lib/utils/sticky.js
@@ -1,5 +1,3 @@
-import StickyFill from 'stickyfilljs';
-
export const createPlaceholder = () => {
const placeholder = document.createElement('div');
placeholder.classList.add('sticky-placeholder');
@@ -60,13 +58,3 @@ export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => {
},
);
};
-
-/**
- * Polyfill the `position: sticky` behavior.
- *
- * - If the current environment supports `position: sticky`, do nothing.
- * - Can receive an iterable element list (NodeList, jQuery collection, etc.) or single HTMLElement.
- */
-export const polyfillSticky = (el) => {
- StickyFill.add(el);
-};
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 6aa45ecc7a0..251f1e0515a 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -19,7 +19,6 @@ import {
} from './lib/utils/common_utils';
import { localTimeAgo } from './lib/utils/datetime_utility';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
-import { polyfillSticky } from './lib/utils/sticky';
import { getLocationHash } from './lib/utils/url_utility';
import { __ } from './locale';
import Notes from './notes';
@@ -123,7 +122,6 @@ export default class MergeRequestTabs {
) {
this.mergeRequestTabs.querySelector(`a[data-action='${action}']`).click();
}
- this.initAffix();
}
bindEvents() {
@@ -509,21 +507,4 @@ export default class MergeRequestTabs {
}
}, 0);
}
-
- initAffix() {
- const $tabs = $('.js-tabs-affix');
-
- // Screen space on small screens is usually very sparse
- // So we dont affix the tabs on these
- if (bp.getBreakpointSize() === 'xs' || !$tabs.length) return;
-
- /**
- If the browser does not support position sticky, it returns the position as static.
- If the browser does support sticky, then we allow the browser to handle it, if not
- then we default back to Bootstraps affix
- */
- if ($tabs.css('position') !== 'static') return;
-
- polyfillSticky($tabs);
- }
}
diff --git a/app/assets/javascripts/pages/import/bulk_imports/index.js b/app/assets/javascripts/pages/import/bulk_imports/status/index.js
index 37ac1a98466..37ac1a98466 100644
--- a/app/assets/javascripts/pages/import/bulk_imports/index.js
+++ b/app/assets/javascripts/pages/import/bulk_imports/status/index.js
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 1383f224979..f56d8f2c2a9 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -497,7 +497,7 @@
li {
a,
button,
- .dropdown-item {
+ .dropdown-item:not(.open-with-link) {
padding: 8px 40px;
position: relative;
diff --git a/app/controllers/import/bulk_imports_controller.rb b/app/controllers/import/bulk_imports_controller.rb
index 61eb9a27560..ef32ba4d119 100644
--- a/app/controllers/import/bulk_imports_controller.rb
+++ b/app/controllers/import/bulk_imports_controller.rb
@@ -37,9 +37,8 @@ class Import::BulkImportsController < ApplicationController
end
def create
- BulkImportService.new(current_user, create_params, credentials).execute
-
- render json: :ok
+ result = BulkImportService.new(current_user, create_params, credentials).execute
+ render json: result.to_json(only: [:id])
end
def realtime_changes
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index c30fc0f5a73..c9e9a34ad88 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -36,7 +36,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:merge_request_widget_graphql, @project)
push_frontend_feature_flag(:drag_comment_selection, @project, default_enabled: true)
push_frontend_feature_flag(:unified_diff_components, @project, default_enabled: true)
- push_frontend_feature_flag(:default_merge_ref_for_diffs, @project)
+ push_frontend_feature_flag(:default_merge_ref_for_diffs, @project, default_enabled: :yaml)
push_frontend_feature_flag(:core_security_mr_widget_counts, @project)
push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true)
push_frontend_feature_flag(:diffs_gradual_load, @project, default_enabled: true)
@@ -502,7 +502,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
params = request.query_parameters
params[:view] = "inline"
- if Feature.enabled?(:default_merge_ref_for_diffs, project)
+ if Feature.enabled?(:default_merge_ref_for_diffs, project, default_enabled: :yaml)
params = params.merge(diff_head: true)
end
diff --git a/app/controllers/repositories/git_http_controller.rb b/app/controllers/repositories/git_http_controller.rb
index 3cf0a23b7f6..9ad700404ff 100644
--- a/app/controllers/repositories/git_http_controller.rb
+++ b/app/controllers/repositories/git_http_controller.rb
@@ -78,6 +78,8 @@ module Repositories
def update_fetch_statistics
return unless project
return if Gitlab::Database.read_only?
+ return if Feature.enabled?(:disable_git_http_fetch_writes)
+
return unless repo_type.project?
OnboardingProgressService.new(project.namespace).execute(action: :git_read)
diff --git a/app/finders/ci/jobs_finder.rb b/app/finders/ci/jobs_finder.rb
index 4ade3e6f031..4408c9cdb6d 100644
--- a/app/finders/ci/jobs_finder.rb
+++ b/app/finders/ci/jobs_finder.rb
@@ -45,7 +45,8 @@ module Ci
return unless pipeline
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :read_build, pipeline)
- jobs_by_type(pipeline, type).latest
+ jobs_scope = jobs_by_type(pipeline, type)
+ params[:include_retried] ? jobs_scope : jobs_scope.latest
end
def filter_by_scope(builds)
diff --git a/app/finders/deployments_finder.rb b/app/finders/deployments_finder.rb
index bdcf7da3bea..89a28d9dfb8 100644
--- a/app/finders/deployments_finder.rb
+++ b/app/finders/deployments_finder.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# WARNING: This finder does not check permissions!
+#
# Arguments:
# params:
# project: Project model - Find deployments for this project
@@ -27,11 +29,13 @@ class DeploymentsFinder
def execute
items = init_collection
items = by_updated_at(items)
+ items = by_finished_at(items)
items = by_environment(items)
items = by_status(items)
items = preload_associations(items)
- items = by_finished_between(items)
- sort(items)
+ items = sort(items)
+
+ items
end
private
@@ -44,11 +48,9 @@ class DeploymentsFinder
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def sort(items)
- items.order(sort_params)
+ items.order(sort_params) # rubocop: disable CodeReuse/ActiveRecord
end
- # rubocop: enable CodeReuse/ActiveRecord
def by_updated_at(items)
items = items.updated_before(params[:updated_before]) if params[:updated_before].present?
@@ -57,6 +59,13 @@ class DeploymentsFinder
items
end
+ def by_finished_at(items)
+ items = items.finished_before(params[:finished_before]) if params[:finished_before].present?
+ items = items.finished_after(params[:finished_after]) if params[:finished_after].present?
+
+ items
+ end
+
def by_environment(items)
if params[:environment].present?
items.for_environment_name(params[:environment])
@@ -65,12 +74,6 @@ class DeploymentsFinder
end
end
- def by_finished_between(items)
- items = items.finished_between(params[:finished_after], params[:finished_before].presence) if params[:finished_after].present?
-
- items
- end
-
def by_status(items)
return items unless params[:status].present?
diff --git a/app/graphql/mutations/merge_requests/update.rb b/app/graphql/mutations/merge_requests/update.rb
index 4721ebab41b..6a94d2f37b2 100644
--- a/app/graphql/mutations/merge_requests/update.rb
+++ b/app/graphql/mutations/merge_requests/update.rb
@@ -19,9 +19,14 @@ module Mutations
required: false,
description: copy_field_description(Types::MergeRequestType, :description)
- def resolve(args)
- merge_request = authorized_find!(**args.slice(:project_path, :iid))
- attributes = args.slice(:title, :description, :target_branch).compact
+ argument :state, ::Types::MergeRequestStateEventEnum,
+ required: false,
+ as: :state_event,
+ description: 'The action to perform to change the state.'
+
+ def resolve(project_path:, iid:, **args)
+ merge_request = authorized_find!(project_path: project_path, iid: iid)
+ attributes = args.compact
::MergeRequests::UpdateService
.new(merge_request.project, current_user, attributes)
diff --git a/app/graphql/types/merge_request_state_event_enum.rb b/app/graphql/types/merge_request_state_event_enum.rb
new file mode 100644
index 00000000000..ebb8b9638db
--- /dev/null
+++ b/app/graphql/types/merge_request_state_event_enum.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ class MergeRequestStateEventEnum < BaseEnum
+ graphql_name 'MergeRequestNewState'
+ description 'New state to apply to a merge request.'
+
+ value 'OPEN',
+ value: 'reopen',
+ description: 'Open the merge request if it is closed.'
+
+ value 'CLOSED',
+ value: 'close',
+ description: 'Close the merge request if it is open.'
+ end
+end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 312d535a92c..cfc4075100b 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -80,27 +80,27 @@ module LabelsHelper
def suggested_colors
{
- '#0033CC' => s_('SuggestedColors|UA blue'),
- '#428BCA' => s_('SuggestedColors|Moderate blue'),
- '#44AD8E' => s_('SuggestedColors|Lime green'),
- '#A8D695' => s_('SuggestedColors|Feijoa'),
- '#5CB85C' => s_('SuggestedColors|Slightly desaturated green'),
- '#69D100' => s_('SuggestedColors|Bright green'),
- '#004E00' => s_('SuggestedColors|Very dark lime green'),
- '#34495E' => s_('SuggestedColors|Very dark desaturated blue'),
- '#7F8C8D' => s_('SuggestedColors|Dark grayish cyan'),
- '#A295D6' => s_('SuggestedColors|Slightly desaturated blue'),
- '#5843AD' => s_('SuggestedColors|Dark moderate blue'),
- '#8E44AD' => s_('SuggestedColors|Dark moderate violet'),
- '#FFECDB' => s_('SuggestedColors|Very pale orange'),
- '#AD4363' => s_('SuggestedColors|Dark moderate pink'),
- '#D10069' => s_('SuggestedColors|Strong pink'),
- '#CC0033' => s_('SuggestedColors|Strong red'),
- '#FF0000' => s_('SuggestedColors|Pure red'),
- '#D9534F' => s_('SuggestedColors|Soft red'),
- '#D1D100' => s_('SuggestedColors|Strong yellow'),
- '#F0AD4E' => s_('SuggestedColors|Soft orange'),
- '#AD8D43' => s_('SuggestedColors|Dark moderate orange')
+ '#009966' => s_('SuggestedColors|Green-cyan'),
+ '#8fbc8f' => s_('SuggestedColors|Dark sea green'),
+ '#3cb371' => s_('SuggestedColors|Medium sea green'),
+ '#00b140' => s_('SuggestedColors|Green screen'),
+ '#013220' => s_('SuggestedColors|Dark green'),
+ '#6699cc' => s_('SuggestedColors|Blue-gray'),
+ '#0000ff' => s_('SuggestedColors|Blue'),
+ '#e6e6fa' => s_('SuggestedColors|Lavendar'),
+ '#9400d3' => s_('SuggestedColors|Dark violet'),
+ '#330066' => s_('SuggestedColors|Deep violet'),
+ '#808080' => s_('SuggestedColors|Gray'),
+ '#36454f' => s_('SuggestedColors|Charcoal grey'),
+ '#f7e7ce' => s_('SuggestedColors|Champagne'),
+ '#c21e56' => s_('SuggestedColors|Rose red'),
+ '#cc338b' => s_('SuggestedColors|Magenta-pink'),
+ '#dc143c' => s_('SuggestedColors|Crimson'),
+ '#ff0000' => s_('SuggestedColors|Red'),
+ '#cd5b45' => s_('SuggestedColors|Dark coral'),
+ '#eee600' => s_('SuggestedColors|Titanium yellow'),
+ '#ed9121' => s_('SuggestedColors|Carrot orange'),
+ '#c39953' => s_('SuggestedColors|Aztec Gold')
}
end
diff --git a/app/models/clusters/agent_token.rb b/app/models/clusters/agent_token.rb
index 5c9561ffa98..b260822f784 100644
--- a/app/models/clusters/agent_token.rb
+++ b/app/models/clusters/agent_token.rb
@@ -8,6 +8,7 @@ module Clusters
self.table_name = 'cluster_agent_tokens'
belongs_to :agent, class_name: 'Clusters::Agent'
+ belongs_to :created_by_user, class_name: 'User', optional: true
before_save :ensure_token
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 2f0fd0af63b..ea2f425c5f6 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -209,14 +209,26 @@ class CommitStatus < ApplicationRecord
end
def group_name
- # 'rspec:linux: 1/10' => 'rspec:linux'
- common_name = name.to_s.gsub(%r{\b\d+[\s:\/\\]+\d+\s*}, '')
+ simplified_commit_status_group_name_feature_flag = Gitlab::SafeRequestStore.fetch("project:#{project_id}:simplified_commit_status_group_name") do
+ Feature.enabled?(:simplified_commit_status_group_name, project, default_enabled: false)
+ end
+
+ if simplified_commit_status_group_name_feature_flag
+ # Only remove one or more [...] "X/Y" "X Y" from the end of build names.
+ # More about the regular expression logic: https://docs.gitlab.com/ee/ci/jobs/#group-jobs-in-a-pipeline
- # 'rspec:linux: [aws, max memory]' => 'rspec:linux', 'rspec:linux: [aws]' => 'rspec:linux'
- common_name.gsub!(%r{: \[.*\]\s*\z}, '')
+ name.to_s.sub(%r{([\b\s:]+((\[.*\])|(\d+[\s:\/\\]+\d+)))+\s*\z}, '').strip
+ else
+ # Prior implementation, remove [...] "X/Y" "X Y" from the beginning and middle of build names
+ # 'rspec:linux: 1/10' => 'rspec:linux'
+ common_name = name.to_s.gsub(%r{\b\d+[\s:\/\\]+\d+\s*}, '')
- common_name.strip!
- common_name
+ # 'rspec:linux: [aws, max memory]' => 'rspec:linux', 'rspec:linux: [aws]' => 'rspec:linux'
+ common_name.gsub!(%r{: \[.*\]\s*\z}, '')
+
+ common_name.strip!
+ common_name
+ end
end
def failed_but_allowed?
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 7bcf7c702f6..f000e474605 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -38,6 +38,7 @@ class Deployment < ApplicationRecord
scope :for_status, -> (status) { where(status: status) }
scope :for_project, -> (project_id) { where(project_id: project_id) }
+ scope :for_projects, -> (projects) { where(project: projects) }
scope :visible, -> { where(status: %i[running success failed canceled]) }
scope :stoppable, -> { where.not(on_stop: nil).where.not(deployable_id: nil).success }
@@ -45,11 +46,8 @@ class Deployment < ApplicationRecord
scope :older_than, -> (deployment) { where('deployments.id < ?', deployment.id) }
scope :with_deployable, -> { joins('INNER JOIN ci_builds ON ci_builds.id = deployments.deployable_id').preload(:deployable) }
- scope :finished_between, -> (start_date, end_date = nil) do
- selected = where('deployments.finished_at >= ?', start_date)
- selected = selected.where('deployments.finished_at < ?', end_date) if end_date
- selected
- end
+ scope :finished_after, ->(date) { where('finished_at >= ?', date) }
+ scope :finished_before, ->(date) { where('finished_at < ?', date) }
FINISHED_STATUSES = %i[success failed canceled].freeze
diff --git a/app/models/label.rb b/app/models/label.rb
index 54129c7c7f3..7a31b095cfc 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -12,7 +12,7 @@ class Label < ApplicationRecord
cache_markdown_field :description, pipeline: :single_line
- DEFAULT_COLOR = '#428BCA'
+ DEFAULT_COLOR = '#6699cc'
default_value_for :color, DEFAULT_COLOR
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5fad876d3fb..1374e8a814a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -921,6 +921,10 @@ class MergeRequest < ApplicationRecord
closed? && !source_project_missing? && source_branch_exists?
end
+ def can_be_closed?
+ opened?
+ end
+
def ensure_merge_request_diff
merge_request_diff.persisted? || create_merge_request_diff
end
diff --git a/app/services/bulk_import_service.rb b/app/services/bulk_import_service.rb
index bebf9153ce7..29439a79afe 100644
--- a/app/services/bulk_import_service.rb
+++ b/app/services/bulk_import_service.rb
@@ -38,6 +38,8 @@ class BulkImportService
bulk_import = create_bulk_import
BulkImportWorker.perform_async(bulk_import.id)
+
+ bulk_import
end
private
diff --git a/app/services/merge_requests/reload_merge_head_diff_service.rb b/app/services/merge_requests/reload_merge_head_diff_service.rb
index 66fcb5c022b..f02a9bd3139 100644
--- a/app/services/merge_requests/reload_merge_head_diff_service.rb
+++ b/app/services/merge_requests/reload_merge_head_diff_service.rb
@@ -24,7 +24,7 @@ module MergeRequests
attr_reader :merge_request
def enabled?
- Feature.enabled?(:default_merge_ref_for_diffs, merge_request.project)
+ Feature.enabled?(:default_merge_ref_for_diffs, merge_request.project, default_enabled: :yaml)
end
def recreate_merge_head_diff
diff --git a/app/views/import/bulk_imports/status.html.haml b/app/views/import/bulk_imports/status.html.haml
index 9ae71eabc8e..778bc1ef1a4 100644
--- a/app/views/import/bulk_imports/status.html.haml
+++ b/app/views/import/bulk_imports/status.html.haml
@@ -8,4 +8,5 @@
#import-groups-mount-element{ data: { status_path: status_import_bulk_imports_path(format: :json),
available_namespaces_path: import_available_namespaces_path(format: :json),
create_bulk_import_path: import_bulk_imports_path(format: :json),
+ jobs_path: realtime_changes_import_bulk_imports_path(format: :json),
source_url: @source_url } }
diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml
index 938dfc69500..0ec47744fc9 100644
--- a/app/views/projects/buttons/_clone.html.haml
+++ b/app/views/projects/buttons/_clone.html.haml
@@ -6,9 +6,9 @@
%span.gl-mr-2.js-clone-dropdown-label
= _('Clone')
= sprite_icon("chevron-down", css_class: "icon")
- %ul.p-3.dropdown-menu.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options{ class: dropdown_class }
+ %ul.dropdown-menu.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options{ class: dropdown_class }
- if ssh_enabled?
- %li
+ %li{ class: 'gl-px-4!' }
%label.label-bold
= _('Clone with SSH')
.input-group
@@ -17,7 +17,7 @@
= clipboard_button(target: '#ssh_project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
= render_if_exists 'projects/buttons/geo'
- if http_enabled?
- %li.pt-2
+ %li.pt-2{ class: 'gl-px-4!' }
%label.label-bold
= _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase }
.input-group
@@ -25,4 +25,15 @@
.input-group-append
= clipboard_button(target: '#http_project_clone', title: _("Copy URL"), class: "input-group-text btn-default btn-clipboard")
= render_if_exists 'projects/buttons/geo'
+ %li.divider.mt-2
+ %li.pt-2.gl-new-dropdown-item
+ %label.label-bold{ class: 'gl-px-4!' }
+ = _('Open in your IDE')
+ %a.dropdown-item.open-with-link{ href: 'vscode://vscode.git/clone?url=' + CGI.escape(project.http_url_to_repo) }
+ .gl-new-dropdown-item-text-wrapper
+ = _('Visual Studio Code')
+ - if show_xcode_link?(@project)
+ %a.dropdown-item.open-with-link{ href: xcode_uri_to_repo(@project) }
+ .gl-new-dropdown-item-text-wrapper
+ = _("Xcode")
= render_if_exists 'projects/buttons/kerberos_clone_field'
diff --git a/app/views/projects/buttons/_xcode_link.html.haml b/app/views/projects/buttons/_xcode_link.html.haml
deleted file mode 100644
index e0f47f1ca3d..00000000000
--- a/app/views/projects/buttons/_xcode_link.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%a.gl-button.btn.btn-default{ href: xcode_uri_to_repo(@project) }
- = _("Open in Xcode")
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 2a502f6e613..453a34d1e7a 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -78,7 +78,7 @@
- if mr_action === "diffs"
- add_page_startup_api_call @endpoint_metadata_url
- params = request.query_parameters
- - if Feature.enabled?(:default_merge_ref_for_diffs, @project)
+ - if Feature.enabled?(:default_merge_ref_for_diffs, @project, default_enabled: :yaml)
- params = params.merge(diff_head: true)
= render "projects/merge_requests/tabs/pane", name: "diffs", id: "js-diffs-app", class: "diffs", data: { "is-locked": @merge_request.discussion_locked?,
endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', params),
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index cd6e85d60ed..6d33fbb535e 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -11,11 +11,6 @@
= render 'projects/find_file_link'
= render 'shared/web_ide_button', blob: nil
-
- - if show_xcode_link?(@project)
- .project-action-button.project-xcode<
- = render "projects/buttons/xcode_link"
-
= render 'projects/buttons/download', project: @project, ref: @ref
.project-clone-holder.d-none.d-md-inline-block>
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index 14f4b04ef78..4b008601783 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -17,14 +17,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.gl-button.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
- %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
+ %button.btn.gl-button.btn-default.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
= sprite_icon("chevron-down", css_class: "icon mr-0")
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.gl-button.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
= sprite_icon("chevron-down", css_class: "icon")
diff --git a/app/workers/pages_transfer_worker.rb b/app/workers/pages_transfer_worker.rb
index f78564cc69d..5d395c9e38a 100644
--- a/app/workers/pages_transfer_worker.rb
+++ b/app/workers/pages_transfer_worker.rb
@@ -9,7 +9,7 @@ class PagesTransferWorker # rubocop:disable Scalability/IdempotentWorker
loggable_arguments 0, 1
def perform(method, args)
- return unless Gitlab::PagesTransfer::Async::METHODS.include?(method)
+ return unless Gitlab::PagesTransfer::METHODS.include?(method)
result = Gitlab::PagesTransfer.new.public_send(method, *args) # rubocop:disable GitlabSecurity/PublicSend