Undo removal of legacy attachments directive

We can just undo this commit for the new one
to be used in the create form as soon as it is ready.
pull/4338/head
Alex Dik 9 years ago
parent 9c6c56bf00
commit d67dd289ac
  1. 2
      frontend/app/components/work-packages/wp-create-form/wp-create-form.directive.html
  2. 2
      frontend/app/components/work-packages/wp-create-form/wp-full-create-form.directive.html
  3. 65
      frontend/app/templates/work_packages/attachments-edit.html
  4. 72
      frontend/app/templates/work_packages/attachments.html
  5. 9
      frontend/app/work_packages/directives/index.js
  6. 155
      frontend/app/work_packages/directives/work-package-attachments-directive.js
  7. 10
      frontend/app/work_packages/services/index.js
  8. 105
      frontend/app/work_packages/services/work-package-attachments-service.js
  9. 120
      frontend/tests/unit/tests/work_packages/directives/work-package-attachments-directive-test.js
  10. 127
      frontend/tests/unit/tests/work_packages/services/work-package-attachments-service-test.js

@ -53,7 +53,7 @@
</div>
</div>
</div>
<wp-attachments work-package="vm.workPackage" data-ng-show="!vm.hideEmptyFields"></wp-attachments>
<work-package-attachments work-package="vm.workPackage" data-ng-show="!vm.hideEmptyFields"></work-package-attachments>
</div>
</div>

@ -83,7 +83,7 @@
</div>
</div>
</div>
<wp-attachments work-package="vm.workPackage" data-ng-show="!vm.hideEmptyFields"></wp-attachments>
<work-package-attachments work-package="vm.workPackage" data-ng-show="!vm.hideEmptyFields"></work-package-attachments>
</div>
</div>

@ -0,0 +1,65 @@
<div class="work-packages--attachments attributes-group">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text">
{{ ::I18n.t('js.label_attachments') }}
</h3>
</div>
</div>
<div class="work-package--attachments--files"
data-ng-show="attachments.length > 0">
<div class="work-package--details--long-field">
<span class="inplace-edit--read"
data-ng-repeat="attachment in attachments">
<span class="inplace-editing--trigger-container">
<span class="inplace-editing--trigger-link"
ng-class="{'-focus': focussing(attachment)}">
<span class="inplace-editing--container">
<span class="inplace-edit--read-value">
<i class="icon-attachment"></i>
<a class="work-package--attachments--filename"
data-ng-href="{{attachment._links.downloadLocation.href}}"
download
data-ng-focus="focus(attachment)"
data-ng-blur="focus(null)">
{{::attachment.fileName}}
</a>
</span>
<a href=''
class="inplace-edit--icon-wrapper"
data-ng-focus="focus(attachment)"
data-ng-blur="focus(null)"
data-ng-click="remove(attachment)"
data-confirm-popup="{{I18n.t('js.text_attachment_destroy_confirmation')}}">
<icon-wrapper icon-name="delete"
data-icon-title="{{::I18n.t('js.label_remove_file',
{ fileName: attachment.fileName })}}">
</icon-wrapper>
</a>
</span>
</span>
</span>
</span>
</div>
</div>
<div data-ng-show="hasRightToUpload"
data-ngf-drop
data-ng-model="files"
data-ng-model-rejected="rejectedFiles"
data-ngf-select
class="work-package--attachments--drop-box"
data-ngf-multiple="true"
data-ngf-change="uploadFilteredFiles($files)"
data-ngf-max-size="{{::maximumFileSize}}"
data-click-on-keypress="[13, 32]">
<div class="work-package--attachments--label">
<i class="icon-attachment"></i>
<p>
{{ ::I18n.t('js.label_drop_files') }}
<br>
{{ ::I18n.t('js.label_drop_files_hint') }}
</p>
</div>
</div>
</div>

@ -0,0 +1,72 @@
<div class="work-packages--attachments attributes-group">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text">
{{ ::I18n.t('js.label_attachments') }}
</h3>
</div>
</div>
<div class="work-package--attachments--files"
data-ng-show="files.length > 0">
<div class="work-package--details--long-field">
<span class="inplace-edit--read"
data-ng-repeat="attachment in files">
<span class="inplace-editing--trigger-container">
<span class="inplace-editing--trigger-link"
ng-class="{'-focus': focussing(attachment)}">
<span class="inplace-editing--container">
<span class="inplace-edit--read-value">
<i class="icon-attachment"></i>
<a class="work-package--attachments--filename -no-decoration"
href=""
data-ng-focus="focus(attachment)"
data-ng-blur="focus(null)"
ng-click="true">
{{::attachment.name}}
</a>
</span>
<a href=''
class="inplace-edit--icon-wrapper"
data-ng-focus="focus(attachment)"
data-ng-blur="focus(null)"
data-ng-click="remove(attachment)"
data-confirm-popup="{{I18n.t('js.text_attachment_destroy_confirmation')}}">
<icon-wrapper icon-name="delete"
data-icon-title="{{::I18n.t('js.label_remove_file',
{ fileName: attachment.name })}}">
</icon-wrapper>
</a>
</span>
</span>
</span>
</span>
</div>
</div>
<div data-ngf-drop
data-ng-show="hasRightToUpload"
data-ng-model="files"
data-ng-model-rejected="rejectedFiles"
data-ngf-select
class="work-package--attachments--drop-box"
data-ngf-multiple="true"
data-ngf-allow-dir="true"
data-ngf-keep-distinct
data-ngf-max-size="{{::maximumFileSize}}"
data-ng-disabled="fetchingConfiguration"
data-ngf-keep="true"
data-ngf-change="filterFiles($files)"
data-click-on-keypress="[13, 32]">
<div class="work-package--attachments--label">
<i class="icon-attachment"></i>
<p data-ng-hide="fetchingConfiguration">
{{ ::I18n.t('js.label_drop_files') }}
<br>
{{ ::I18n.t('js.label_drop_files_hint') }}
</p>
<p data-ng-show="fetchingConfiguration">
{{ ::I18n.t('js.label_wait') }}
</p>
</div>
</div>
</div>

@ -54,6 +54,15 @@ angular.module('openproject.workPackages.directives')
])
.directive('workPackageDynamicAttribute', ['$compile', require(
'./work-package-dynamic-attribute-directive')])
.directive('workPackageAttachments', [
'WorkPackageAttachmentsService',
'NotificationsService',
'I18n',
'ConfigurationService',
'ConversionService',
'FocusHelper',
require('./work-package-attachments-directive')
])
.directive('workPackageWatcher', [
'I18n',
require('./work-package-watcher-directive')

@ -0,0 +1,155 @@
//-- 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(
workPackageAttachmentsService,
NotificationsService,
I18n,
ConfigurationService,
ConversionService
) {
'use strict';
var editMode = function(attrs) {
return typeof attrs.edit !== 'undefined';
};
var attachmentsController = function(scope, element, attrs) {
scope.files = [];
scope.element = element;
var workPackage = scope.workPackage(),
upload = function(event, workPackage) {
if (angular.isUndefined(scope.files)) {
return;
}
if (scope.files.length > 0) {
workPackageAttachmentsService.upload(workPackage, scope.files).then(function() {
scope.files = [];
loadAttachments();
});
}
},
loadAttachments = function() {
if (!editMode(attrs)) {
return;
}
scope.loading = true;
workPackageAttachmentsService.load(workPackage, true).then(function(attachments) {
scope.attachments = attachments;
}).finally(function() {
scope.loading = false;
});
};
scope.I18n = I18n;
scope.rejectedFiles = [];
scope.size = ConversionService.fileSize;
scope.hasRightToUpload = !!(workPackage.links.addAttachment || workPackage.isNew);
var currentlyRemoving = [];
scope.remove = function(file) {
currentlyRemoving.push(file);
workPackageAttachmentsService.remove(file).then(function(file) {
_.remove(scope.attachments, file);
_.remove(scope.files, file);
}).finally(function() {
_.remove(currentlyRemoving, file);
});
};
scope.deleting = function(attachment) {
return _.findIndex(currentlyRemoving, attachment) > -1;
};
var currentlyFocusing = null;
scope.focus = function(attachment) {
currentlyFocusing = attachment;
};
scope.focussing = function(attachment) {
return currentlyFocusing === attachment;
};
scope.$on('uploadPendingAttachments', upload);
scope.filterFiles = function(files) {
// Directories cannot be uploaded and as such, should not become files in
// the sense of this directive. The files within the direcotories will
// be taken though.
_.remove(files, function(file) {
return file.type === 'directory';
});
};
scope.uploadFilteredFiles = function(files) {
scope.filterFiles(files);
scope.$emit('uploadPendingAttachments', workPackage);
};
scope.$watch('rejectedFiles', function(rejectedFiles) {
if (rejectedFiles.length === 0) {
return;
}
var errors = _.map(rejectedFiles, function(file) {
return file.name + ' (' + scope.size(file.size) + ')';
}),
message = I18n.t('js.label_rejected_files_reason',
{ maximumFilesize: scope.size(scope.maximumFileSize) }
);
NotificationsService.addError(message, errors);
});
scope.fetchingConfiguration = true;
ConfigurationService.api().then(function(settings) {
scope.maximumFileSize = settings.maximumAttachmentFileSize;
// somehow, I18n cannot interpolate function results, so we need to cache this once
scope.maxFileSize = scope.size(settings.maximumAttachmentFileSize);
scope.fetchingConfiguration = false;
});
loadAttachments();
};
return {
restrict: 'E',
replace: true,
scope: {
workPackage: '&'
},
templateUrl: function(element, attrs) {
if (editMode(attrs)) {
return '/templates/work_packages/attachments-edit.html';
}
return '/templates/work_packages/attachments.html';
},
link: attachmentsController
};
};

@ -52,4 +52,14 @@ angular.module('openproject.workPackages.services')
.service('WorkPackagesOverviewService', [
'WORK_PACKAGE_ATTRIBUTES',
require('./work-packages-overview-service')
])
.service('WorkPackageAttachmentsService', [
'Upload', // 'Upload' is provided by ngFileUpload
'PathHelper',
'I18n',
'NotificationsService',
'$q',
'$timeout',
'$http',
require('./work-package-attachments-service')
]);

@ -0,0 +1,105 @@
//-- 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(Upload, PathHelper, I18n, NotificationsService, $q, $timeout, $http) {
'use strict';
var upload = function(workPackage, files) {
var uploadPath = workPackage.links.addAttachment.url();
// for file in files build some promises, create a notification per WP,
// notify the noticiation (wat?) about progress
var uploads = _.map(files, function(file) {
var options = {
url: uploadPath,
fields: {
metadata: {
fileName: file.name,
description: file.description
}
},
file: file
};
return Upload.upload(options);
});
// notify the user
var message = I18n.t('js.label_upload_notification', {
id: workPackage.props.id,
subject: workPackage.props.subject
});
var notification = NotificationsService.addWorkPackageUpload(message, uploads);
var allUploadsDone = $q.defer();
$q.all(uploads).then(function() {
$timeout(function() { // let the notification linger for a bit
NotificationsService.remove(notification);
allUploadsDone.resolve();
}, 700);
}, function(err) {
allUploadsDone.reject(err);
});
return allUploadsDone.promise;
},
load = function(workPackage, reload) {
var path = workPackage.links.attachments.url(),
attachments = $q.defer();
$http.get(path, { cache: !reload }).success(function(response) {
attachments.resolve(response._embedded.elements);
}).error(function(err) {
attachments.reject(err);
});
return attachments.promise;
},
remove = function(fileOrAttachment) {
var removal = $q.defer();
if (angular.isObject(fileOrAttachment._links)) {
var path = fileOrAttachment._links.self.href;
$http.delete(path).success(function() {
removal.resolve(fileOrAttachment);
}).error(function(err) {
removal.reject(err);
});
} else {
removal.resolve(fileOrAttachment);
}
return removal.promise;
},
hasAttachments = function(workPackage) {
var existance = $q.defer();
load(workPackage).then(function(attachments) {
existance.resolve(attachments.length > 0);
});
return existance.promise;
};
return {
upload: upload,
remove: remove,
load: load,
hasAttachments: hasAttachments
};
};

@ -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.
//++
describe('WorkPackageAttachmentsDirective', function() {
var compile, element, rootScope, scope, isolatedScope,
workPackage = {links: {}};
beforeEach(angular.mock.module('openproject.workPackages.directives'));
beforeEach(module('openproject.templates'));
var loadPromise,
workPackageAttachmentsService = {
load: function() {
return loadPromise;
}
},
apiPromise,
configurationService = {
api: function() {
return apiPromise;
}
};
beforeEach(module('openproject.workPackages.services', function($provide) {
$provide.constant('WorkPackageAttachmentsService', workPackageAttachmentsService);
}));
beforeEach(module('openproject.config', function($provide) {
$provide.constant('ConfigurationService', configurationService);
}));
beforeEach(inject(function($rootScope, $compile, $q) {
apiPromise = $q(function(resolve) {
return resolve('');
});
loadPromise = $q(function(resolve) {
return resolve([]);
});
var html = '<work-package-attachments edit work-package="workPackage">' +
'</work-package-attachments>';
element = angular.element(html);
rootScope = $rootScope;
scope = $rootScope.$new();
scope.workPackage = workPackage;
compile = function() {
$compile(element)(scope);
scope.$digest();
console.log('ISO SCOPEEEEEE', element.isolateScope());
};
}));
describe('filterFiles', function() {
beforeEach(function() {
compile();
isolatedScope = element.isolateScope();
});
it('filters out attachments of type directory', function() {
var files = [{type: 'directory'}, {type: 'file'}];
isolatedScope.filterFiles(files, {}, {}, false);
expect(files).to.eql([{type: 'file'}]);
});
});
describe('uploadFilteredFiles', function() {
var files = [{type: 'directory'}, {type: 'file'}],
dumbPromise = {
then: function(call) { return call(); }
};
beforeEach(function() {
compile();
isolatedScope = element.isolateScope();
});
it('triggers uploading of non directory files', function() {
//need to have files to be able to trigger uploads
isolatedScope.files = files;
var uploadStub = workPackageAttachmentsService.upload = sinon.stub().returns(dumbPromise);
isolatedScope.uploadFilteredFiles(files, {}, {}, true);
expect(uploadStub.calledWith(workPackage, [{type: 'file'}])).to.be.true;
});
});
});

@ -0,0 +1,127 @@
//-- 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 expr: true */
/* globals WebKitBlobBuilder */
describe('workPackageAttachmentsService', function() {
'use strict';
var WorkPackageAttachmentsService, $httpBackend;
// mock me a work package
// TODO: remove that hyperagent.js nonsense asap
var workPackage = {
props: {
id: 1
},
links: {
attachments: {
url: function() {
return '/api/v3/work_packages/1/attachments';
}
},
addAttachment: {
url: function() {
return '/api/v3/work_packages/1/attachments';
}
}
}
};
// mock me an attachment
var attachment = {
_links: {
self: {
href: '/attachments/1234'
}
}
};
beforeEach(module('openproject.workPackages'));
beforeEach(inject(function(_WorkPackageAttachmentsService_, _$httpBackend_){
WorkPackageAttachmentsService = _WorkPackageAttachmentsService_;
$httpBackend = _$httpBackend_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.verifyNoOutstandingExpectation();
});
describe('loading attachments', function() {
beforeEach(function() {
$httpBackend.expectGET('/api/v3/work_packages/1/attachments').respond({
_embedded: {
elements: [1,2,3]
}
});
});
it('should retrieve attachments for a given work pacakge', function () {
WorkPackageAttachmentsService.load(workPackage).then(function(result) {
expect(result).to.eql([1,2,3]);
});
$httpBackend.flush();
});
});
describe('creating an attachment', function() {
beforeEach(function() {
$httpBackend.expectPOST('/api/v3/work_packages/1/attachments').respond({});
});
function createFiles() {
var blob;
try {
var builder = new WebKitBlobBuilder();
builder.append(['I am a TestFile for WebKit browsers']);
blob = builder.getBlob();
} catch(Error) {
blob = new Blob(['I am a testfile']);
}
return [blob];
}
it('should create an attachment for a given work package', function () {
var files = createFiles();
WorkPackageAttachmentsService.upload(workPackage, files);
$httpBackend.flush();
});
});
describe('deleting an attachment', function() {
beforeEach(function() {
$httpBackend.expectDELETE('/attachments/1234').respond({});
});
it('should remove an attachment', function () {
WorkPackageAttachmentsService.remove(attachment);
$httpBackend.flush();
});
});
});
Loading…
Cancel
Save