Merge branch 'release/4.1' into dev

pull/2939/head
Jonas Heinrich 10 years ago
commit 3039d4aaea
  1. 3
      app/assets/stylesheets/content/_in_place_editing.sass
  2. 4
      frontend/app/services/timezone-service.js
  3. 1
      frontend/app/templates/components/inplace_editor/display/date.html
  4. 1
      frontend/app/templates/components/inplace_editor/editable/date.html
  5. 3
      frontend/app/templates/work_packages/inplace_editor/custom/display/spent_time.html
  6. 1
      frontend/app/templates/work_packages/inplace_editor/custom/editable/date.html
  7. 7
      frontend/app/ui_components/date/date-directive.js
  8. 7
      frontend/app/work_packages/directives/inplace_editor/custom/display/inplace-display-spent-time-directive.js
  9. 3
      frontend/app/work_packages/directives/inplace_editor/custom/editable/index.js
  10. 164
      frontend/app/work_packages/directives/inplace_editor/custom/editable/inplace-editor-date-directive.js
  11. 5
      frontend/app/work_packages/directives/work-packages-table-directive.js
  12. 18
      frontend/app/work_packages/services/work-package-field-service.js
  13. 4
      frontend/tests/integration/mocks/work-packages/819.json
  14. 120
      frontend/tests/integration/specs/work-packages/details-pane/custom-fields/date-editable-spec.js
  15. 24
      frontend/tests/integration/specs/work-packages/work-packages-spec.js
  16. 3
      spec/lib/api/v3/work_packages/work_package_schema_representer_spec.rb

@ -47,6 +47,8 @@ $inplace-edit--color--very-dark: #cacaca
cursor: wait!important
inplace-edit--write
.inplace-edit--write-value
.inplace-edit--date
margin: 0
.date-range-picker
display: table
& > *
@ -55,6 +57,7 @@ $inplace-edit--color--very-dark: #cacaca
& > span
padding-right: 5px
padding-left: 5px
.ui-datepicker-current,
.ui-datepicker-close
display: none

@ -44,6 +44,10 @@ module.exports = function(ConfigurationService, I18n) {
return d;
},
parseISODate: function(date) {
return TimezoneService.parseDate(date, 'YYYY-MM-DD');
},
formattedDate: function(date) {
var format = ConfigurationService.dateFormatPresent() ? ConfigurationService.dateFormat() : 'L';
return TimezoneService.parseDate(date).format(format);

@ -0,0 +1 @@
<op-date date-value="displayPaneController.getReadValue()" no-date-text="displayPaneController.placeholder"></op-date>

@ -0,0 +1 @@
<inplace-editor-date></inplace-editor-date>

@ -1,4 +1,6 @@
<div class="spent-time-wrapper">
<span ng-if="fieldController.isEmpty()">{{ displayPaneController.placeholder }}</span>
<span ng-if="!fieldController.isEmpty()">
<span ng-if="customEditorController.isLinkViewable()">
<a href="{{ customEditorController.getPath() }}">
{{ displayPaneController.getReadValue() }}
@ -7,4 +9,5 @@
<span ng-if="!customEditorController.isLinkViewable()">
{{ displayPaneController.getReadValue() }}
</span>
</span>
</div>

@ -0,0 +1 @@
<input class="focus-input inplace-edit--date" type="text" />

@ -39,15 +39,16 @@ module.exports = function(TimezoneService) {
} else {
scope.date = scope.noDateText;
}
if (!scope.hideTitle) {
scope.dateTitle = scope.date;
}
}
setDate(scope.dateValue);
scope.$watch('dateValue', function(newVal) {
setDate(newVal);
});
if (!scope.hideTitle) {
scope.dateTitle = scope.date;
}
}
};
};

@ -32,7 +32,7 @@ module.exports = function(EditableFieldsState) {
transclude: true,
replace: true,
scope: {},
require: '^inplaceEditorDisplayPane',
require: ['^inplaceEditorDisplayPane', '^workPackageField'],
templateUrl: '/templates/work_packages/inplace_editor/custom/display/spent_time.html',
controller: function() {
this.isLinkViewable = function() {
@ -44,8 +44,9 @@ module.exports = function(EditableFieldsState) {
};
},
controllerAs: 'customEditorController',
link: function(scope, element, attrs, displayPaneController) {
scope.displayPaneController = displayPaneController;
link: function(scope, element, attrs, controllers) {
scope.displayPaneController = controllers[0];
scope.fieldController = controllers[1];
}
};
};

@ -29,4 +29,5 @@
angular.module('openproject.workPackages.directives')
.directive('inplaceEditorWikiTextarea', require('./inplace-editor-wiki-textarea-directive'))
.directive('inplaceEditorDuration', require('./inplace-editor-duration-directive'))
.directive('inplaceEditorDropdown', require('./inplace-editor-dropdown-directive'));
.directive('inplaceEditorDropdown', require('./inplace-editor-dropdown-directive'))
.directive('inplaceEditorDate', require('./inplace-editor-date-directive'));

@ -0,0 +1,164 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
module.exports = function(WorkPackageFieldService, EditableFieldsState,
TimezoneService, ConfigurationService, I18n,
$timeout) {
var parseDate = TimezoneService.parseDate,
parseISODate = TimezoneService.parseISODate,
formattedDate = function(date) {
return TimezoneService.parseDate(date).format('L');
},
formattedISODate = TimezoneService.formattedISODate,
customDateFormat = 'DD/MM/YYYY',
datepickerFormat = 'dd/mm/yy',
customFormattedDate = function(date) {
return parseISODate(date).format(customDateFormat);
};
return {
restrict: 'E',
transclude: true,
replace: true,
scope: {},
require: '^workPackageField',
templateUrl: '/templates/work_packages/inplace_editor/custom/editable/date.html',
controller: function() {
},
controllerAs: 'customEditorController',
link: function(scope, element, attrs, fieldController) {
scope.fieldController = fieldController;
var timerId = 0,
prevDate = '',
input = element,
addClearButton = function (inp) {
setTimeout(function() {
var buttonPane = angular.element('.ui-datepicker-buttonpane');
if(buttonPane.find('.ui-datepicker-clear').length > 0) {
buttonPane.find('.ui-datepicker-clear').remove();
}
angular.element( '<button>', {
text: 'Clear',
click: function() {
setDate(inp, null);
}
})
.addClass('ui-datepicker-clear ui-state-default ui-priority-primary ui-corner-all')
.appendTo(buttonPane);
}, 1);
},
setDate = function(inp, date) {
if(date) {
inp.datepicker('option', 'defaultDate', customFormattedDate(date));
inp.datepicker('option', 'setDate', customFormattedDate(date));
inp.val(customFormattedDate(date));
inp.change();
} else {
inp.datepicker('option', 'defaultDate', null);
inp.datepicker('option', 'setDate', null);
inp.val('');
inp.change();
date = null;
}
};
input.attr('placeholder', '-');
input.on('change', function() {
if(input.val().replace(/^\s+|\s+$/g, '') === '') {
$timeout(function() {
scope.fieldController.writeValue = null;
});
input.val('');
$timeout.cancel(timerId);
return;
}
$timeout.cancel(timerId);
timerId = $timeout(function() {
var date = input.val(),
isValid = TimezoneService.isValid(date, customDateFormat);
if(isValid){
scope.fieldController.writeValue = formattedISODate(parseDate(date, customDateFormat));
}
}, 1000);
});
input.datepicker({
firstDay: ConfigurationService.startOfWeek(),
showWeeks: true,
changeMonth: true,
numberOfMonths: 1,
dateFormat: datepickerFormat,
alterOffset: function(offset) {
var wHeight = angular.element(window).height(),
dpHeight = angular.element('#ui-datepicker-div').height(),
inputTop = input.offset().top,
inputHeight = input.innerHeight();
if((inputTop + inputHeight + dpHeight) > wHeight) {
offset.top -= inputHeight - 4;
}
return offset;
},
beforeShow: function() {
addClearButton(input);
},
onChangeMonthYear: function() {
addClearButton(input);
},
onClose: function(selectedDate) {
if(!selectedDate || selectedDate === '' || selectedDate === prevDate) {
return;
}
prevDate = parseDate(selectedDate, customDateFormat);
$timeout(function() {
scope.fieldController.writeValue = formattedISODate(prevDate);
});
}
});
if(scope.fieldController.writeValue) {
prevDate = formattedDate(scope.fieldController.writeValue);
}
setDate(input, scope.fieldController.writeValue);
angular.element('.work-packages--details-content').scroll(function() {
input.datepicker('hide');
angular.element('#ui-datepicker-div').blur();
});
angular.element(window).resize(function() {
input.datepicker('hide');
angular.element('#ui-datepicker-div').blur();
});
}
};
};

@ -207,11 +207,16 @@ module.exports = function(I18n, WorkPackagesTableService, $window, $timeout, fla
if ($event.target.type != 'checkbox') {
var currentRowCheckState = row.checked;
var multipleChecked = mulipleRowsChecked();
var isLink = angular.element($event.target).is('a');
if (!($event.ctrlKey || $event.shiftKey)) {
scope.setCheckedStateForAllRows(false);
}
if(isLink) {
return;
}
if ($event.shiftKey) {
clearSelection();
activeSelectionBorderIndex = WorkPackagesTableService.selectRowRange(scope.rows, row, activeSelectionBorderIndex);

@ -58,7 +58,7 @@ module.exports = function(
// && workPackage.schema.props.dueDate.writable;
}
if(schema.props[field].type === 'Date') {
return false;
return true;
}
var isWritable = schema.props[field].writable;
@ -196,6 +196,11 @@ module.exports = function(
return true;
}
// strategy pattern, guys
if (field === 'spentTime' && WorkPackageFieldService.getValue(workPackage, field) === 'PT0S') {
return true;
}
if (value.embedded && _.isArray(value.embedded.elements)) {
return value.embedded.elements.length === 0;
}
@ -217,6 +222,9 @@ module.exports = function(
case 'DateRange':
inplaceType = 'daterange';
break;
case 'Date':
inplaceType = 'date';
break;
case 'Float':
inplaceType = 'float';
break;
@ -273,7 +281,6 @@ module.exports = function(
case 'Integer':
case 'Float':
case 'Duration':
case 'Date':
case 'Boolean':
displayStrategy = 'text';
break;
@ -292,6 +299,9 @@ module.exports = function(
case 'DateRange':
displayStrategy = 'daterange';
break;
case 'Date':
displayStrategy = 'date';
break;
}
//workPackageOverviewAttributes
@ -344,6 +354,10 @@ module.exports = function(
if (schema.props[field].type === 'Boolean') {
return value ? I18n.t('js.general_text_yes') : I18n.t('js.general_text_no');
}
if (workPackage.schema.props[field].type === 'Date') {
return value;
}
}
return WorkPackagesHelper.formatValue(value, mappings[field]);

@ -513,6 +513,6 @@
"customField5": null,
"customField6": null,
"customField7": null,
"customField9": null,
"customField9": "2015-04-12",
"customField10": null
}
}

@ -0,0 +1,120 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
/* jshint ignore:start */
var expect = require('../../../../spec_helper.js').expect,
detailsPaneHelper = require('../details-pane-helper.js');
describe('details pane', function() {
describe('custom fields', function() {
var datePicker;
describe('date editbale', function() {
beforeEach(function() {
detailsPaneHelper.loadPane(819, 'overview');
datePicker = element(by.css('.inplace-edit.attribute-customField9'));
});
context('read value', function() {
it('should be present on page', function(){
expect(datePicker.isDisplayed()).to.eventually.be.true;
});
it('shows date range', function() {
expect(datePicker.getText()).to.eventually.equal('04/12/2015');
});
});
context('write value', function() {
var date;
beforeEach(function() {
date = datePicker.$('input.inplace-edit--date');
});
beforeEach(function() {
datePicker.$('.inplace-edit--read-value').click();
});
it('opens calendar on click', function() {
date.click();
expect($('.ui-datepicker').isDisplayed()).to.eventually.be.true;
});
it('shows date in input', function() {
date.getText(function(text) {
expect(text).to.equal('04/12/2015');
});
});
describe('validation', function() {
it('validates valid date', function() {
date.clear();
date.sendKeys('04/12/2015');
date.getText(function(text) {
expect(text).to.equal('04/12/2015');
});
});
it('doesn\'t validate invalid date', function() {
date.clear();
date.sendKeys('13/24/2014');
date.getText(function(text) {
expect(text).to.equal('04/12/2015');
});
});
it('validates empty date', function() {
date.clear();
date.getText(function(text) {
expect(text).to.equal('');
});
});
});
describe('date selection', function() {
it('changes date by clicking on calendar', function() {
date.click();
element.all(by.css('a.ui-state-default')).filter(function(elem) {
return elem.getText().then(function(text) {
return text.indexOf('9') !== -1;
});
}).then(function(filteredElements) {
filteredElements[0].click();
date.getText(function(text) {
expect(text).to.equal('04/09/2015');
});
});
});
});
});
});
});
});
/* jshint ignore:end */

@ -57,28 +57,4 @@ describe('OpenProject', function() {
]);
});
});
describe('click', function() {
context('with Ctrl', function() {
var newWindowHandle;
beforeEach(function() {
var link = element(by.css('[title="16923"]'));
browser.actions()
.mouseMove(link)
.sendKeys(protractor.Key.CONTROL)
.click()
.perform();
});
xit('opens new tab', function() {
browser.getAllWindowHandles().then(function (handles) {
newWindowHandle = handles[1];
browser.switchTo().window(newWindowHandle).then(function () {
expect(browser.getCurrentUrl()).to.eventually.contain('/work_packages/16923');
});
});
});
});
});
});

@ -221,6 +221,9 @@ describe ::API::V3::WorkPackages::Schema::WorkPackageSchemaRepresenter do
describe 'spentTime' do
before do
# don't fail the test for other allowed_to calls than the expected ones
allow(current_user).to receive(:allowed_to?).and_return false
allow(current_user).to receive(:allowed_to?).with(:view_time_entries, work_package.project)
.and_return true
end

Loading…
Cancel
Save