...@@ -230,6 +230,7 @@ webpack-dev-server: ...@@ -230,6 +230,7 @@ webpack-dev-server:
- .default-tags - .default-tags
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-only
- .only-code-changes - .only-code-changes
dependencies: ["setup-test-env", "compile-assets", "compile-assets pull-cache"] dependencies: ["setup-test-env", "compile-assets", "compile-assets pull-cache"]
variables: variables:
... ...
......
...@@ -4,6 +4,7 @@ pages: ...@@ -4,6 +4,7 @@ pages:
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-only - .default-only
- .only-code-qa-changes
only: only:
refs: refs:
- master - master
... ...
......
...@@ -25,7 +25,9 @@ package-and-qa-manual: ...@@ -25,7 +25,9 @@ package-and-qa-manual:
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
package-and-qa-manual:master: package-and-qa-manual:master:
extends: .package-and-qa-base extends:
- .package-and-qa-base
- .only-code-qa-changes
only: only:
refs: refs:
- master@gitlab-org/gitlab-foss - master@gitlab-org/gitlab-foss
... ...
......
...@@ -6,6 +6,7 @@ cache gems: ...@@ -6,6 +6,7 @@ cache gems:
- .default-retry - .default-retry
- .default-cache - .default-cache
- .default-before_script - .default-before_script
- .only-code-qa-changes
stage: test stage: test
dependencies: ["setup-test-env"] dependencies: ["setup-test-env"]
needs: ["setup-test-env"] needs: ["setup-test-env"]
... ...
......
.tests-metadata-state: .tests-metadata-state:
extends: .default-only extends:
- .default-only
variables: variables:
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache" TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
before_script: before_script:
...@@ -31,7 +32,9 @@ retrieve-tests-metadata: ...@@ -31,7 +32,9 @@ retrieve-tests-metadata:
- '[[ ! -d "ee/" ]] || [[ -f $EE_KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${EE_KNAPSACK_RSPEC_SUITE_REPORT_PATH}' - '[[ ! -d "ee/" ]] || [[ -f $EE_KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${EE_KNAPSACK_RSPEC_SUITE_REPORT_PATH}'
update-tests-metadata: update-tests-metadata:
extends: .tests-metadata-state extends:
- .tests-metadata-state
- .only-code-changes
stage: post-test stage: post-test
cache: cache:
key: tests_metadata key: tests_metadata
... ...
......
1.62.0 1.64.0
...@@ -2,7 +2,7 @@ import Vue from 'vue'; ...@@ -2,7 +2,7 @@ import Vue from 'vue';
import Metrics from '~/monitoring/components/embed.vue'; import Metrics from '~/monitoring/components/embed.vue';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-ce/issues/64369. // TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-foss/issues/64369.
export default function renderMetrics(elements) { export default function renderMetrics(elements) {
if (!elements.length) { if (!elements.length) {
return; return;
... ...
......
/* eslint-disable func-names, no-var, object-shorthand, prefer-arrow-callback */ /* eslint-disable func-names, no-var, prefer-arrow-callback */
import $ from 'jquery'; import $ from 'jquery';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
...@@ -82,7 +82,7 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function(text, url, success) { ...@@ -82,7 +82,7 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function(text, url, success) {
}) })
.then(({ data }) => { .then(({ data }) => {
this.ajaxCache = { this.ajaxCache = {
text: text, text,
response: data, response: data,
}; };
success(data); success(data);
... ...
......
/* eslint-disable func-names, object-shorthand, prefer-arrow-callback */ /* eslint-disable func-names, prefer-arrow-callback */
import $ from 'jquery'; import $ from 'jquery';
import Dropzone from 'dropzone'; import Dropzone from 'dropzone';
...@@ -32,7 +32,7 @@ export default class BlobFileDropzone { ...@@ -32,7 +32,7 @@ export default class BlobFileDropzone {
url: form.attr('action'), url: form.attr('action'),
// Rails uses a hidden input field for PUT // Rails uses a hidden input field for PUT
// http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
method: method, method,
clickable: true, clickable: true,
uploadMultiple: false, uploadMultiple: false,
paramName: 'file', paramName: 'file',
...@@ -42,7 +42,7 @@ export default class BlobFileDropzone { ...@@ -42,7 +42,7 @@ export default class BlobFileDropzone {
addRemoveLinks: true, addRemoveLinks: true,
previewsContainer: '.dropzone-previews', previewsContainer: '.dropzone-previews',
headers: csrf.headers, headers: csrf.headers,
init: function() { init() {
this.on('addedfile', function() { this.on('addedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false); toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS); dropzoneMessage.addClass(HIDDEN_CLASS);
...@@ -69,7 +69,7 @@ export default class BlobFileDropzone { ...@@ -69,7 +69,7 @@ export default class BlobFileDropzone {
}); });
}, },
// Override behavior of adding error underneath preview // Override behavior of adding error underneath preview
error: function(file, errorMessage) { error(file, errorMessage) {
const stripped = $('<div/>') const stripped = $('<div/>')
.html(errorMessage) .html(errorMessage)
.text(); .text();
... ...
......
...@@ -50,6 +50,9 @@ export default Vue.extend({ ...@@ -50,6 +50,9 @@ export default Vue.extend({
}; };
}, },
computed: { computed: {
isLoggedIn() {
return Boolean(gon.current_user_id);
},
counterTooltip() { counterTooltip() {
const { issuesSize } = this.list; const { issuesSize } = this.list;
return `${n__('%d issue', '%d issues', issuesSize)}`; return `${n__('%d issue', '%d issues', issuesSize)}`;
...@@ -106,7 +109,11 @@ export default Vue.extend({ ...@@ -106,7 +109,11 @@ export default Vue.extend({
Sortable.create(this.$el.parentNode, sortableOptions); Sortable.create(this.$el.parentNode, sortableOptions);
}, },
created() { created() {
if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) { if (
this.list.isExpandable &&
AccessorUtilities.isLocalStorageAccessSafe() &&
!this.isLoggedIn
) {
const isCollapsed = localStorage.getItem(`${this.uniqueKey}.expanded`) === 'false'; const isCollapsed = localStorage.getItem(`${this.uniqueKey}.expanded`) === 'false';
this.list.isExpanded = !isCollapsed; this.list.isExpanded = !isCollapsed;
...@@ -120,10 +127,14 @@ export default Vue.extend({ ...@@ -120,10 +127,14 @@ export default Vue.extend({
if (this.list.isExpandable) { if (this.list.isExpandable) {
this.list.isExpanded = !this.list.isExpanded; this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe()) { if (AccessorUtilities.isLocalStorageAccessSafe() && !this.isLoggedIn) {
localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded); localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
} }
if (this.isLoggedIn) {
this.list.update();
}
// When expanding/collapsing, the tooltip on the caret button sometimes stays open. // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
// Close all tooltips manually to prevent dangling tooltips. // Close all tooltips manually to prevent dangling tooltips.
$('.tooltip').tooltip('hide'); $('.tooltip').tooltip('hide');
... ...
......
/* eslint-disable func-names, no-new, promise/catch-or-return */ /* eslint-disable func-names, no-new */
import $ from 'jquery'; import $ from 'jquery';
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import CreateLabelDropdown from '../../create_label'; import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store'; import boardsStore from '../stores/boards_store';
...@@ -26,18 +28,23 @@ $(document) ...@@ -26,18 +28,23 @@ $(document)
export default function initNewListDropdown() { export default function initNewListDropdown() {
$('.js-new-board-list').each(function() { $('.js-new-board-list').each(function() {
const $this = $(this); const $dropdownToggle = $(this);
const $dropdown = $dropdownToggle.closest('.dropdown');
new CreateLabelDropdown( new CreateLabelDropdown(
$this.closest('.dropdown').find('.dropdown-new-label'), $dropdown.find('.dropdown-new-label'),
$this.data('namespacePath'), $dropdownToggle.data('namespacePath'),
$this.data('projectPath'), $dropdownToggle.data('projectPath'),
); );
$this.glDropdown({ $dropdownToggle.glDropdown({
data(term, callback) { data(term, callback) {
axios.get($this.attr('data-list-labels-path')).then(({ data }) => { axios
callback(data); .get($dropdownToggle.attr('data-list-labels-path'))
}); .then(({ data }) => callback(data))
.catch(() => {
$dropdownToggle.data('bs.dropdown').hide();
flash(__('Error fetching labels.'));
});
}, },
renderRow(label) { renderRow(label) {
const active = boardsStore.findListByLabelId(label.id); const active = boardsStore.findListByLabelId(label.id);
... ...
......
...@@ -11,11 +11,6 @@ import boardsStore from '../stores/boards_store'; ...@@ -11,11 +11,6 @@ import boardsStore from '../stores/boards_store';
class ListIssue { class ListIssue {
constructor(obj, defaultAvatar) { constructor(obj, defaultAvatar) {
this.id = obj.id;
this.iid = obj.iid;
this.title = obj.title;
this.confidential = obj.confidential;
this.dueDate = obj.due_date;
this.subscribed = obj.subscribed; this.subscribed = obj.subscribed;
this.labels = []; this.labels = [];
this.assignees = []; this.assignees = [];
...@@ -25,6 +20,16 @@ class ListIssue { ...@@ -25,6 +20,16 @@ class ListIssue {
subscriptions: true, subscriptions: true,
}; };
this.isLoading = {}; this.isLoading = {};
this.refreshData(obj, defaultAvatar);
}
refreshData(obj, defaultAvatar) {
this.id = obj.id;
this.iid = obj.iid;
this.title = obj.title;
this.confidential = obj.confidential;
this.dueDate = obj.due_date;
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.referencePath = obj.reference_path; this.referencePath = obj.reference_path;
this.path = obj.real_path; this.path = obj.real_path;
...@@ -42,11 +47,13 @@ class ListIssue { ...@@ -42,11 +47,13 @@ class ListIssue {
this.milestone_id = obj.milestone.id; this.milestone_id = obj.milestone.id;
} }
obj.labels.forEach(label => { if (obj.labels) {
this.labels.push(new ListLabel(label)); this.labels = obj.labels.map(label => new ListLabel(label));
}); }
this.assignees = obj.assignees.map(a => new ListAssignee(a, defaultAvatar)); if (obj.assignees) {
this.assignees = obj.assignees.map(a => new ListAssignee(a, defaultAvatar));
}
} }
addLabel(label) { addLabel(label) {
... ...
......
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign */ /* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow */
import { __ } from '~/locale'; import { __ } from '~/locale';
import ListLabel from './label'; import ListLabel from './label';
...@@ -45,7 +45,7 @@ class List { ...@@ -45,7 +45,7 @@ class List {
const typeInfo = this.getTypeInfo(this.type); const typeInfo = this.getTypeInfo(this.type);
this.preset = Boolean(typeInfo.isPreset); this.preset = Boolean(typeInfo.isPreset);
this.isExpandable = Boolean(typeInfo.isExpandable); this.isExpandable = Boolean(typeInfo.isExpandable);
this.isExpanded = true; this.isExpanded = !obj.collapsed;
this.page = 1; this.page = 1;
this.loading = true; this.loading = true;
this.loadingMore = false; this.loadingMore = false;
...@@ -113,7 +113,8 @@ class List { ...@@ -113,7 +113,8 @@ class List {
} }
update() { update() {
gl.boardService.updateList(this.id, this.position).catch(() => { const collapsed = !this.isExpanded;
return gl.boardService.updateList(this.id, this.position, collapsed).catch(() => {
// TODO: handle request error // TODO: handle request error
}); });
} }
...@@ -259,12 +260,7 @@ class List { ...@@ -259,12 +260,7 @@ class List {
} }
onNewIssueResponse(issue, data) { onNewIssueResponse(issue, data) {
issue.id = data.id; issue.refreshData(data);
issue.iid = data.iid;
issue.project = data.project;
issue.path = data.real_path;
issue.referencePath = data.reference_path;
issue.assignableLabelsEndpoint = data.assignable_labels_endpoint;
if (this.issuesSize > 1) { if (this.issuesSize > 1) {
const moveBeforeId = this.issues[1].id; const moveBeforeId = this.issues[1].id;
... ...
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/** /**
* This file is intended to be deleted. * This file is intended to be deleted.
* The existing functions will removed one by one in favor of using the board store directly. * The existing functions will removed one by one in favor of using the board store directly.
* see https://gitlab.com/gitlab-org/gitlab-ce/issues/61621 * see https://gitlab.com/gitlab-org/gitlab-foss/issues/61621
*/ */
import boardsStore from '~/boards/stores/boards_store'; import boardsStore from '~/boards/stores/boards_store';
...@@ -32,8 +32,8 @@ export default class BoardService { ...@@ -32,8 +32,8 @@ export default class BoardService {
return boardsStore.createList(entityId, entityType); return boardsStore.createList(entityId, entityType);
} }
updateList(id, position) { updateList(id, position, collapsed) {
return boardsStore.updateList(id, position); return boardsStore.updateList(id, position, collapsed);
} }
destroyList(id) { destroyList(id) {
... ...
......
...@@ -82,7 +82,7 @@ const boardsStore = { ...@@ -82,7 +82,7 @@ const boardsStore = {
this.state.lists = _.sortBy(this.state.lists, 'position'); this.state.lists = _.sortBy(this.state.lists, 'position');
}) })
.catch(() => { .catch(() => {
// https://gitlab.com/gitlab-org/gitlab-ce/issues/30821 // https://gitlab.com/gitlab-org/gitlab-foss/issues/30821
}); });
this.removeBlankState(); this.removeBlankState();
}, },
...@@ -278,10 +278,11 @@ const boardsStore = { ...@@ -278,10 +278,11 @@ const boardsStore = {
}); });
}, },
updateList(id, position) { updateList(id, position, collapsed) {
return axios.put(`${this.state.endpoints.listsEndpoint}/${id}`, { return axios.put(`${this.state.endpoints.listsEndpoint}/${id}`, {
list: { list: {
position, position,
collapsed,
}, },
}); });
}, },
... ...
......
// this is just to make ee_else_ce happy and will be cleaned up in https://gitlab.com/gitlab-org/gitlab-ce/issues/59807 // this is just to make ee_else_ce happy and will be cleaned up in https://gitlab.com/gitlab-org/gitlab-foss/issues/59807
export default { export default {
initEESpecific() {}, initEESpecific() {},
... ...
......
...@@ -54,7 +54,7 @@ export default class VariableList { ...@@ -54,7 +54,7 @@ export default class VariableList {
environment_scope: { environment_scope: {
// We can't use a `.js-` class here because // We can't use a `.js-` class here because
// gl_dropdown replaces the <input> and doesn't copy over the class // gl_dropdown replaces the <input> and doesn't copy over the class
// See https://gitlab.com/gitlab-org/gitlab-ce/issues/42458 // See https://gitlab.com/gitlab-org/gitlab-foss/issues/42458
selector: `input[name="${this.formField}[variables_attributes][][environment_scope]"]`, selector: `input[name="${this.formField}[variables_attributes][][environment_scope]"]`,
default: '*', default: '*',
}, },
... ...
......
...@@ -111,15 +111,25 @@ export default class Clusters { ...@@ -111,15 +111,25 @@ export default class Clusters {
this.initApplications(clusterType); this.initApplications(clusterType);
this.initEnvironments(); this.initEnvironments();
if (clusterEnvironmentsPath) { if (clusterEnvironmentsPath && this.environments) {
this.fetchEnvironments(); this.store.toggleFetchEnvironments(true);
this.initPolling(
'fetchClusterEnvironments',
data => this.handleClusterEnvironmentsSuccess(data),
() => this.handleEnvironmentsPollError(),
);
} }
this.updateContainer(null, this.store.state.status, this.store.state.statusReason); this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
this.addListeners(); this.addListeners();
if (statusPath && !this.environments) { if (statusPath && !this.environments) {
this.initPolling(); this.initPolling(
'fetchClusterStatus',
data => this.handleClusterStatusSuccess(data),
() => this.handlePollError(),
);
} }
} }
...@@ -179,16 +189,9 @@ export default class Clusters { ...@@ -179,16 +189,9 @@ export default class Clusters {
}); });
} }
fetchEnvironments() { handleClusterEnvironmentsSuccess(data) {
this.store.toggleFetchEnvironments(true); this.store.toggleFetchEnvironments(false);
this.store.updateEnvironments(data.data);
this.service
.fetchClusterEnvironments()
.then(data => {
this.store.toggleFetchEnvironments(false);
this.store.updateEnvironments(data.data);
})
.catch(() => Clusters.handleError());
} }
static initDismissableCallout() { static initDismissableCallout() {
...@@ -224,21 +227,16 @@ export default class Clusters { ...@@ -224,21 +227,16 @@ export default class Clusters {
eventHub.$off('uninstallApplication'); eventHub.$off('uninstallApplication');
} }
initPolling() { initPolling(method, successCallback, errorCallback) {
this.poll = new Poll({ this.poll = new Poll({
resource: this.service, resource: this.service,
method: 'fetchData', method,
successCallback: data => this.handleSuccess(data), successCallback,
errorCallback: () => Clusters.handleError(), errorCallback,
}); });
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
this.poll.makeRequest(); this.poll.makeRequest();
} else {
this.service
.fetchData()
.then(data => this.handleSuccess(data))
.catch(() => Clusters.handleError());
} }
Visibility.change(() => { Visibility.change(() => {
...@@ -250,11 +248,21 @@ export default class Clusters { ...@@ -250,11 +248,21 @@ export default class Clusters {
}); });
} }
handlePollError() {
this.constructor.handleError();
}
handleEnvironmentsPollError() {
this.store.toggleFetchEnvironments(false);
this.handlePollError();
}
static handleError() { static handleError() {
Flash(s__('ClusterIntegration|Something went wrong on our end.')); Flash(s__('ClusterIntegration|Something went wrong on our end.'));
} }
handleSuccess(data) { handleClusterStatusSuccess(data) {
const prevStatus = this.store.state.status; const prevStatus = this.store.state.status;
const prevApplicationMap = Object.assign({}, this.store.state.applications); const prevApplicationMap = Object.assign({}, this.store.state.applications);
... ...
......
...@@ -17,7 +17,7 @@ export default class ClusterService { ...@@ -17,7 +17,7 @@ export default class ClusterService {
}; };
} }
fetchData() { fetchClusterStatus() {
return axios.get(this.options.endpoint); return axios.get(this.options.endpoint);
} }
... ...
......
...@@ -218,6 +218,7 @@ export default class ClusterStore { ...@@ -218,6 +218,7 @@ export default class ClusterStore {
environmentPath: environment.environment_path, environmentPath: environment.environment_path,
lastDeployment: environment.last_deployment, lastDeployment: environment.last_deployment,
rolloutStatus: { rolloutStatus: {
status: environment.rollout_status ? environment.rollout_status.status : null,
instances: environment.rollout_status ? environment.rollout_status.instances : [], instances: environment.rollout_status ? environment.rollout_status.instances : [],
}, },
updatedAt: environment.updated_at, updatedAt: environment.updated_at,
... ...
......