Commit b1d7b012 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent aabf412b
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import updateDescription from '../utils/update_description';
export default class Store { export default class Store {
constructor(initialState) { constructor(initialState) {
...@@ -19,8 +21,15 @@ export default class Store { ...@@ -19,8 +21,15 @@ export default class Store {
} }
Object.assign(this.state, convertObjectPropsToCamelCase(data)); Object.assign(this.state, convertObjectPropsToCamelCase(data));
// find if there is an open details node inside of the issue description.
const descriptionSection = document.body.querySelector(
'.detail-page-description.content-block',
);
const details =
!_.isNull(descriptionSection) && descriptionSection.getElementsByTagName('details');
this.state.descriptionHtml = updateDescription(data.description, details);
this.state.titleHtml = data.title; this.state.titleHtml = data.title;
this.state.descriptionHtml = data.description;
this.state.lock_version = data.lock_version; this.state.lock_version = data.lock_version;
} }
......
import _ from 'underscore';
/**
* Function that replaces the open attribute for the <details> element.
*
* @param {String} descriptionHtml - The html string passed back from the server as a result of polling
* @param {Array} details - All detail nodes inside of the issue description.
*/
const updateDescription = (descriptionHtml = '', details) => {
let detailNodes = details;
if (_.isEmpty(details)) {
detailNodes = [];
}
const placeholder = document.createElement('div');
placeholder.innerHTML = descriptionHtml;
const newDetails = placeholder.getElementsByTagName('details');
if (newDetails.length !== detailNodes.length) {
return descriptionHtml;
}
Array.from(newDetails).forEach((el, i) => {
/*
* <details> has an open attribute that can have a value, "", "true", "false"
* and will show the dropdown, which is why we are setting the attribute
* explicitly to true.
*/
if (detailNodes[i].open) el.setAttribute('open', true);
});
return placeholder.innerHTML;
};
export default updateDescription;
...@@ -122,7 +122,7 @@ export default { ...@@ -122,7 +122,7 @@ export default {
}, },
}, },
series: this.scatterSeries, series: this.scatterSeries,
dataZoom: this.dataZoomConfig, dataZoom: [this.dataZoomConfig],
}; };
}, },
dataZoomConfig() { dataZoomConfig() {
......
...@@ -104,7 +104,7 @@ export default { ...@@ -104,7 +104,7 @@ export default {
v-gl-tooltip v-gl-tooltip
:title=" :title="
__( __(
'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more on the documentation for Pipelines for Merged Results.', 'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.',
) )
" "
class="js-pipeline-url-detached badge badge-info" class="js-pipeline-url-detached badge badge-info"
......
...@@ -64,12 +64,16 @@ module Boards ...@@ -64,12 +64,16 @@ module Boards
%i[label_id] %i[label_id]
end end
def list_update_attrs
%i[collapsed position]
end
def create_list_params def create_list_params
params.require(:list).permit(list_creation_attrs) params.require(:list).permit(list_creation_attrs)
end end
def update_list_params def update_list_params
params.require(:list).permit(:collapsed, :position) params.require(:list).permit(list_update_attrs)
end end
def serialize_as_json(resource) def serialize_as_json(resource)
......
# frozen_string_literal: true # frozen_string_literal: true
# This file should be identical in GitLab Community Edition and Enterprise Edition
class Projects::GitHttpClientController < Projects::ApplicationController class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper include KerberosSpnegoHelper
......
...@@ -43,7 +43,11 @@ module Boards ...@@ -43,7 +43,11 @@ module Boards
end end
def create_list(board, type, target, position) def create_list(board, type, target, position)
board.lists.create(type => target, list_type: type, position: position) board.lists.create(create_list_attributes(type, target, position))
end
def create_list_attributes(type, target, position)
{ type => target, list_type: type, position: position }
end end
end end
end end
......
...@@ -4,16 +4,22 @@ module Boards ...@@ -4,16 +4,22 @@ module Boards
module Lists module Lists
class UpdateService < Boards::BaseService class UpdateService < Boards::BaseService
def execute(list) def execute(list)
update_preferences_result = update_preferences(list) if can_read?(list) if execute_by_params(list)
update_position_result = update_position(list) if can_admin?(list)
if update_preferences_result || update_position_result
success(list: list) success(list: list)
else else
error(list.errors.messages, 422) error(list.errors.messages, 422)
end end
end end
private
def execute_by_params(list)
update_preferences_result = update_preferences(list) if can_read?(list)
update_position_result = update_position(list) if can_admin?(list)
update_preferences_result || update_position_result
end
def update_preferences(list) def update_preferences(list)
return unless preferences? return unless preferences?
...@@ -50,3 +56,5 @@ module Boards ...@@ -50,3 +56,5 @@ module Boards
end end
end end
end end
Boards::Lists::UpdateService.prepend_if_ee('EE::Boards::Lists::UpdateService')
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
} } } }
Auto DevOps Auto DevOps
- if @pipeline.detached_merge_request_pipeline? - if @pipeline.detached_merge_request_pipeline?
%span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more on the documentation for Pipelines for Merged Results.') } %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.') }
detached detached
- if @pipeline.stuck? - if @pipeline.stuck?
%span.js-pipeline-url-stuck.badge.badge-warning %span.js-pipeline-url-stuck.badge.badge-warning
......
---
title: When user toggles task list item, keep details open until user closes the details
manually
merge_request: 16153
author:
type: fixed
---
title: Handle wiki and graphql attachments in gitlab-workhorse
merge_request: 17690
author:
type: performance
---
title: Better job naming for Docker.gitlab-ci.yml
merge_request: 17218
author: luca.orlandi@gmail.com
type: other
# frozen_string_literal: true
class AddMaxIssueCountToList < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default :lists, :max_issue_count, :integer, default: 0
end
def down
remove_column :lists, :max_issue_count
end
end
...@@ -2014,6 +2014,7 @@ ActiveRecord::Schema.define(version: 2019_09_19_162036) do ...@@ -2014,6 +2014,7 @@ ActiveRecord::Schema.define(version: 2019_09_19_162036) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "user_id" t.integer "user_id"
t.integer "milestone_id" t.integer "milestone_id"
t.integer "max_issue_count", default: 0, null: false
t.index ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true t.index ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true
t.index ["label_id"], name: "index_lists_on_label_id" t.index ["label_id"], name: "index_lists_on_label_id"
t.index ["list_type"], name: "index_lists_on_list_type" t.index ["list_type"], name: "index_lists_on_list_type"
......
...@@ -48,7 +48,8 @@ Example response: ...@@ -48,7 +48,8 @@ Example response:
"color" : "#F0AD4E", "color" : "#F0AD4E",
"description" : null "description" : null
}, },
"position" : 1 "position" : 1,
"max_issue_count": 0
}, },
{ {
"id" : 2, "id" : 2,
...@@ -57,7 +58,8 @@ Example response: ...@@ -57,7 +58,8 @@ Example response:
"color" : "#FF0000", "color" : "#FF0000",
"description" : null "description" : null
}, },
"position" : 2 "position" : 2,
"max_issue_count": 0
}, },
{ {
"id" : 3, "id" : 3,
...@@ -66,7 +68,8 @@ Example response: ...@@ -66,7 +68,8 @@ Example response:
"color" : "#FF5F00", "color" : "#FF5F00",
"description" : null "description" : null
}, },
"position" : 3 "position" : 3,
"max_issue_count": 0
} }
] ]
} }
...@@ -117,7 +120,8 @@ Example response: ...@@ -117,7 +120,8 @@ Example response:
"color" : "#F0AD4E", "color" : "#F0AD4E",
"description" : null "description" : null
}, },
"position" : 1 "position" : 1,
"max_issue_count": 0
}, },
{ {
"id" : 2, "id" : 2,
...@@ -126,7 +130,8 @@ Example response: ...@@ -126,7 +130,8 @@ Example response:
"color" : "#FF0000", "color" : "#FF0000",
"description" : null "description" : null
}, },
"position" : 2 "position" : 2,
"max_issue_count": 0
}, },
{ {
"id" : 3, "id" : 3,
...@@ -135,7 +140,8 @@ Example response: ...@@ -135,7 +140,8 @@ Example response:
"color" : "#FF5F00", "color" : "#FF5F00",
"description" : null "description" : null
}, },
"position" : 3 "position" : 3,
"max_issue_count": 0
} }
] ]
} }
...@@ -185,7 +191,8 @@ Example response: ...@@ -185,7 +191,8 @@ Example response:
"color" : "#F0AD4E", "color" : "#F0AD4E",
"description" : null "description" : null
}, },
"position" : 1 "position" : 1,
"max_issue_count": 0
}, },
{ {
"id" : 2, "id" : 2,
...@@ -194,7 +201,8 @@ Example response: ...@@ -194,7 +201,8 @@ Example response:
"color" : "#FF0000", "color" : "#FF0000",
"description" : null "description" : null
}, },
"position" : 2 "position" : 2,
"max_issue_count": 0
}, },
{ {
"id" : 3, "id" : 3,
...@@ -203,7 +211,8 @@ Example response: ...@@ -203,7 +211,8 @@ Example response:
"color" : "#FF5F00", "color" : "#FF5F00",
"description" : null "description" : null
}, },
"position" : 3 "position" : 3,
"max_issue_count": 0
} }
] ]
} }
...@@ -336,7 +345,8 @@ Example response: ...@@ -336,7 +345,8 @@ Example response:
"color" : "#F0AD4E", "color" : "#F0AD4E",
"description" : null "description" : null
}, },
"position" : 1 "position" : 1,
"max_issue_count": 0
}, },
{ {
"id" : 2, "id" : 2,
...@@ -345,7 +355,8 @@ Example response: ...@@ -345,7 +355,8 @@ Example response:
"color" : "#FF0000", "color" : "#FF0000",
"description" : null "description" : null
}, },
"position" : 2 "position" : 2,
"max_issue_count": 0
}, },
{ {
"id" : 3, "id" : 3,
...@@ -354,7 +365,8 @@ Example response: ...@@ -354,7 +365,8 @@ Example response:
"color" : "#FF5F00", "color" : "#FF5F00",
"description" : null "description" : null
}, },
"position" : 3 "position" : 3,
"max_issue_count": 0
} }
] ]
``` ```
...@@ -387,7 +399,8 @@ Example response: ...@@ -387,7 +399,8 @@ Example response:
"color" : "#F0AD4E", "color" : "#F0AD4E",
"description" : null "description" : null
}, },
"position" : 1 "position" : 1,
"max_issue_count": 0
} }
``` ```
...@@ -427,7 +440,8 @@ Example response: ...@@ -427,7 +440,8 @@ Example response:
"color" : "#F0AD4E", "color" : "#F0AD4E",
"description" : null "description" : null
}, },
"position" : 1 "position" : 1,
"max_issue_count": 0
} }
``` ```
...@@ -460,7 +474,8 @@ Example response: ...@@ -460,7 +474,8 @@ Example response:
"color" : "#F0AD4E", "color" : "#F0AD4E",
"description" : null "description" : null
}, },
"position" : 1 "position" : 1,
"max_issue_count": 0
} }
``` ```
......
build-master: docker-build-master:
# Official docker image. # Official docker image.
image: docker:latest image: docker:latest
stage: build stage: build
...@@ -12,7 +12,7 @@ build-master: ...@@ -12,7 +12,7 @@ build-master:
only: only:
- master - master
build: docker-build:
# Official docker image. # Official docker image.
image: docker:latest image: docker:latest
stage: build stage: build
......
...@@ -11176,7 +11176,7 @@ msgstr "" ...@@ -11176,7 +11176,7 @@ msgstr ""
msgid "Pipelines for last year" msgid "Pipelines for last year"
msgstr "" msgstr ""
msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more on the documentation for Pipelines for Merged Results." msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results."
msgstr "" msgstr ""
msgid "Pipelines settings for '%{project_name}' were successfully updated." msgid "Pipelines settings for '%{project_name}' were successfully updated."
......
...@@ -35,7 +35,8 @@ ...@@ -35,7 +35,8 @@
} }
}, },
"title": { "type": "string" }, "title": { "type": "string" },
"position": { "type": ["integer", "null"] } "position": { "type": ["integer", "null"] },
"max_issue_count": { "type": "integer" }
}, },
"additionalProperties": true "additionalProperties": true
} }
...@@ -76,7 +76,8 @@ ...@@ -76,7 +76,8 @@
"name": { "type": "string" } "name": { "type": "string" }
} }
}, },
"position": { "type": ["integer", "null"] } "position": { "type": ["integer", "null"] },
"max_issue_count": { "type": "integer" }
}, },
"additionalProperties": false "additionalProperties": false
} }
......
import Store from '~/issue_show/stores';
import updateDescription from '~/issue_show/utils/update_description';
jest.mock('~/issue_show/utils/update_description');
describe('Store', () => {
let store;
beforeEach(() => {
store = new Store({
descriptionHtml: '<p>This is a description</p>',
});
});
describe('updateState', () => {
beforeEach(() => {
document.body.innerHTML = `
<div class="detail-page-description content-block">
<details open>
<summary>One</summary>
</details>
<details>
<summary>Two</summary>
</details>
</div>
`;
});
afterEach(() => {
document.getElementsByTagName('html')[0].innerHTML = '';
});
it('calls updateDetailsState', () => {
store.updateState({ description: '' });
expect(updateDescription).toHaveBeenCalledTimes(1);
});
});
});
import updateDescription from '~/issue_show/utils/update_description';
describe('updateDescription', () => {
it('returns the correct value to be set as descriptionHtml', () => {
const actual = updateDescription(
'<details><summary>One</summary></details><details><summary>Two</summary></details>',
[{ open: true }, { open: false }], // mocking NodeList from the dom.
);
expect(actual).toEqual(
'<details open="true"><summary>One</summary></details><details><summary>Two</summary></details>',
);
});
describe('when description details returned from api is different then whats currently on the dom', () => {
it('returns the description from the api', () => {
const dataDescription = '<details><summary>One</summary></details>';
const actual = updateDescription(dataDescription, []);
expect(actual).toEqual(dataDescription);
});
});
});
...@@ -23,6 +23,14 @@ describe('Issuable output', () => { ...@@ -23,6 +23,14 @@ describe('Issuable output', () => {
beforeEach(done => { beforeEach(done => {
setFixtures(` setFixtures(`
<div> <div>
<div class="detail-page-description content-block">
<details open>
<summary>One</summary>
</details>
<details>
<summary>Two</summary>
</details>
</div>
<div class="flash-container"></div> <div class="flash-container"></div>
<span id="task_status"></span> <span id="task_status"></span>
</div> </div>
......
...@@ -140,6 +140,16 @@ describe('Time series component', () => { ...@@ -140,6 +140,16 @@ describe('Time series component', () => {
expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
}); });
}); });
it('contains an svg object within an array to properly render icon', () => {
timeSeriesChart.vm.$nextTick(() => {
expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([
{
handleIcon: `path://${mockSvgPathContent}`,
},
]);
});
});
}); });
describe('onResize', () => { describe('onResize', () => {
......
...@@ -717,6 +717,7 @@ List: ...@@ -717,6 +717,7 @@ List:
- updated_at - updated_at
- milestone_id - milestone_id
- user_id - user_id
- max_issue_count
ExternalPullRequest: ExternalPullRequest:
- id - id
- created_at - created_at
......
# frozen_string_literal: true # frozen_string_literal: true
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
RSpec.shared_examples 'slack or mattermost notifications' do |service_name| RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:chat_service) { described_class.new } let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' } let(:webhook_url) { 'https://example.gitlab.com/' }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment