diff --git a/app/views/settings/_general.html.erb b/app/views/settings/_general.html.erb
index 9735606806..7d042352f4 100644
--- a/app/views/settings/_general.html.erb
+++ b/app/views/settings/_general.html.erb
@@ -32,8 +32,10 @@ See doc/COPYRIGHT.rdoc for more details.
diff --git a/bower.json b/bower.json
index 18d6287a9d..780b5fa84a 100644
--- a/bower.json
+++ b/bower.json
@@ -22,7 +22,7 @@
"jquery-migrate": "~1.2.1",
"momentjs": "~2.7.0",
"moment-timezone": "~0.2.0",
- "angular-context-menu": "0.1.2",
+ "angular-context-menu": "finnlabs/angular-context-menu#v0.1.4",
"angular-busy": "~4.0.4",
"hyperagent": "manwithtwowatches/hyperagent#v0.4.2"
},
diff --git a/config/locales/de.yml b/config/locales/de.yml
index ec31eb9c16..46c57868b9 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -33,8 +33,8 @@ de:
deleted: "Account erfolgreich gelöscht"
deletion_info:
data_consequences:
- other: "Von den vom Nutzer im Zuge der Nutzung der Anwendung erstellen Daten (z.B. E-Mail-Adresse, Systemeinstellungen, Tickets, Wiki-Einträgen) werden so viele wie möglich gelöscht. Bitte beachten Sie allerdings, dass Daten wie Tickets und Wiki-Einträge nicht gelöscht werden können, da dies die Arbeit der anderen Nutzer behindern würde. Solche Daten werden daher einem \"Gelöschten Nutzer\" zugewiesen. Da die Daten aller gelöschter Accounts dem \"Gelöschten Nutzer\" zugewiesen werden, ist es unmöglich, die vom Nutzer erstellten Daten diesem Nutzer zuzuordnen."
- self: "Von den von Ihnen im Zuge der Nutzung der Anwendung erstellen Daten (z.B. E-Mail-Adresse, Systemeinstellungen, Tickets, Wiki-Einträgen) werden so viele wie möglich gelöscht. Bitte beachten Sie allerdings, dass Daten wie Tickets und Wiki-Einträge nicht gelöscht werden können, da dies die Arbeit der anderen Nutzer behindern würde. Solche Daten werden daher einem \"Gelöschten Nutzer\" zugewiesen. Da die Daten aller gelöschter Accounts dem \"Gelöschten Nutzer\" zugewiesen werden, ist es unmöglich, die von Ihnen erstellten Daten auf Ihre Person zurückzuführen."
+ other: "Von den vom Nutzer im Zuge der Nutzung der Anwendung erstellen Daten (z.B. E-Mail-Adresse, Systemeinstellungen, Arbeitspaketen, Wiki-Einträgen) werden so viele wie möglich gelöscht. Bitte beachten Sie allerdings, dass Daten wie Arbeitspakete und Wiki-Einträge nicht gelöscht werden können, da dies die Arbeit der anderen Nutzer behindern würde. Solche Daten werden daher einem \"Gelöschten Nutzer\" zugewiesen. Da die Daten aller gelöschter Accounts dem \"Gelöschten Nutzer\" zugewiesen werden, ist es unmöglich, die vom Nutzer erstellten Daten diesem Nutzer zuzuordnen."
+ self: "Von den von Ihnen im Zuge der Nutzung der Anwendung erstellen Daten (z.B. E-Mail-Adresse, Systemeinstellungen, Arbeitspakete, Wiki-Einträgen) werden so viele wie möglich gelöscht. Bitte beachten Sie allerdings, dass Daten wie Arbeitspakete und Wiki-Einträge nicht gelöscht werden können, da dies die Arbeit der anderen Nutzer behindern würde. Solche Daten werden daher einem \"Gelöschten Nutzer\" zugewiesen. Da die Daten aller gelöschter Accounts dem \"Gelöschten Nutzer\" zugewiesen werden, ist es unmöglich, die von Ihnen erstellten Daten auf Ihre Person zurückzuführen."
heading: "Lösche Account %{name}"
info:
other: "Das Löschen des Accounts kann nicht rückgängig gemacht werden."
@@ -115,11 +115,11 @@ de:
repository:
url: "URL"
role:
- assignable: "Tickets können dieser Rolle zugewiesen werden"
+ assignable: "Arbeitspakete können dieser Rolle zugewiesen werden"
time_entry:
activity: "Aktivität"
hours: "Stunden"
- issue: "Ticket"
+ issue: "Arbeitspaket"
spent_on: "Datum"
type: "Typ"
type_color:
@@ -263,7 +263,7 @@ de:
comment: "Kommentar"
custom_field: "Benutzerdefiniertes Feld"
group: "Gruppe"
- issue: "Ticket"
+ issue: "Arbeitspaket"
category: "Kategorie"
status: "Arbeitspaket-Status"
member: "Mitglied"
@@ -349,7 +349,7 @@ de:
button_collapse_all: "Alle zuklappen"
button_configure: "Konfigurieren"
button_copy: "Kopieren"
- button_copy_and_follow: "Kopieren und Ticket anzeigen"
+ button_copy_and_follow: "Kopieren und Arbeitspaket anzeigen"
button_create: "Anlegen"
button_create_and_continue: "Anlegen und weiter"
button_delete: "Löschen"
@@ -364,7 +364,7 @@ de:
button_log_time: "Aufwand buchen"
button_login: "Anmelden"
button_move: "Verschieben"
- button_move_and_follow: "Verschieben und Ticket anzeigen"
+ button_move_and_follow: "Verschieben und Arbeitspaket anzeigen"
button_quote: "Zitieren"
button_remove_widget: "Infobox löschen"
button_rename: "Umbenennen"
@@ -456,6 +456,10 @@ de:
x_seconds:
one: "1 Sekunde"
other: "%{count} Sekunden"
+ units:
+ hour:
+ one: "Stunde"
+ other: "Stunden"
default_activity_design: "Design"
default_activity_development: "Entwicklung"
@@ -486,10 +490,10 @@ de:
description_attachment_toggle: "Dateien aus/einblenden"
description_autocomplete: >
Für dieses Feld werden Sie mit einer Autovervollständigung
- unterstützt. Sie können einen Teil des Tickettitels schreiben und
- bekommen eine Liste von möglichen Tickets angezeigt. Wählen Sie mit
+ unterstützt. Sie können einen Teil des Arbeitspakettitels schreiben und
+ bekommen eine Liste von möglichen Arbeitspaketen angezeigt. Wählen Sie mit
den Pfeiltasten den gewünschten Eintrag und bestätigen Sie mit Tab
- oder Enter. Sie können aber auch die Ticketnummer direkt eintragen.
+ oder Enter. Sie können aber auch die Arbeitspaketnummer direkt eintragen.
description_available_columns: "Verfügbare Spalten"
description_choose_project: "Projekte"
description_compare_from: "Vergleiche von Version"
@@ -529,7 +533,7 @@ de:
error_can_not_archive_project: "Dieses Projekt kann nicht archiviert werden."
error_can_not_delete_custom_field: "Kann das benutzerdefinierte Feld nicht löschen."
- error_can_not_delete_type: "Dieser Typ enthält Tickets und kann nicht gelöscht werden."
+ error_can_not_delete_type: "Dieser Typ enthält Arbeitspakete und kann nicht gelöscht werden."
error_can_not_delete_standard_type: "Standardtypen können nicht gelöscht werden."
error_can_not_remove_role: "Diese Rolle wird verwendet und kann nicht gelöscht werden."
error_can_not_reopen_work_package_on_closed_version: "Das Arbeitspaket ist einer abgeschlossenen Version zugeordnet und kann daher nicht wieder geöffnet werden."
@@ -538,13 +542,13 @@ de:
error_work_package_done_ratios_not_updated: "Der Arbeitspaket-Fortschritt wurde nicht aktualisiert."
error_work_package_not_found_in_project: "Das Arbeitspaket wurde nicht gefunden oder gehört nicht zu diesem Projekt."
error_must_be_project_member: "muss Mitglied des Projekts sein"
- error_no_default_work_package_status: "Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter \"Administration -> Ticket-Status\")."
+ error_no_default_work_package_status: "Es ist kein Status als Standard definiert. Bitte überprüfen Sie Ihre Konfiguration (unter \"Administration -> Arbeitspaket-Status\")."
error_no_type_in_project: "Diesem Projekt ist kein Typ zugeordnet. Bitte überprüfen Sie die Projekteinstellungen."
error_omniauth_registration_timed_out: "Die Registrierung über einen externen Authentifizierungsprovider ist fehlgeschlagen. Bitte versuchen Sie es erneut."
error_scm_annotate: "Der Eintrag existiert nicht oder kann nicht annotiert werden."
error_scm_command_failed: "Beim Zugriff auf das Projektarchiv ist ein Fehler aufgetreten: %{value}"
error_scm_not_found: "Eintrag und/oder Revision existiert nicht im Projektarchiv."
- error_unable_delete_status: "Der Ticket-Status konnte nicht gelöscht werden."
+ error_unable_delete_status: "Der Arbeitspaket-Status konnte nicht gelöscht werden."
error_unable_delete_default_status: "Der Arbeitspaket-Status konnte nicht gelöscht werden, da dieser als Standard-Status markiert ist. Wählen Sie bitte zunächst einen anderen Arbeitspaket-Status als Standard-Status."
error_unable_to_connect: "Fehler beim Verbinden (%{value})"
error_workflow_copy_source: "Bitte wählen Sie einen Quell-Typ und eine Quell-Rolle."
@@ -589,7 +593,7 @@ de:
label_add_another_file: "Eine weitere Datei hinzufügen"
label_add_columns: "Ausgewählte Spalten hinzufügen"
label_add_note: "Kommentar hinzufügen"
- label_add_related_issues: "Zugehöriges Ticket hinzufügen"
+ label_add_related_issues: "Zugehöriges Arbeitspaket hinzufügen"
label_add_related_work_packages: "Zugehöriges Arbeitspaket hinzufügen"
label_add_subtask: "Unteraufgabe hinzufügen"
label_added: "hinzugefügt"
@@ -745,7 +749,7 @@ de:
label_work_package_status_plural: "Arbeitspaket-Status"
label_work_package_tracking: "Arbeitspakete"
label_work_package_updated: "Arbeitspaket aktualisiert"
- label_work_package_view_all: "Alle Tickets anzeigen"
+ label_work_package_view_all: "Alle Arbeitspakete anzeigen"
label_work_package_view_all_assigned_to_me: "Alle mir zugewiesenen Arbeitspakete anzeigen"
label_work_package_view_all_reported_by_me: "Alle von mir erstellten Arbeitspakete anzeigen"
label_work_package_view_all_responsible_for: "Alle von mir verantworteten Arbeitspakete anzeigen"
@@ -1040,13 +1044,13 @@ de:
mail_body_account_information_external: "Sie können sich mit Ihrem Konto %{value} anmelden."
mail_body_lost_password: "Benutzen Sie den folgenden Link, um Ihr Kennwort zu ändern:"
mail_body_register: "Um Ihr Konto zu aktivieren, benutzen Sie folgenden Link:"
- mail_body_reminder: "%{count} Tickets, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:"
+ mail_body_reminder: "%{count} Arbeitspakete, die Ihnen zugewiesen sind, müssen in den nächsten %{days} Tagen abgegeben werden:"
mail_body_wiki_content_added: "Die Wiki-Seite '%{id}' wurde von %{author} hinzugefügt."
mail_body_wiki_content_updated: "Die Wiki-Seite '%{id}' wurde von %{author} aktualisiert."
mail_subject_account_activation_request: "Antrag auf %{value} Kontoaktivierung"
mail_subject_lost_password: "Ihr %{value} Kennwort"
mail_subject_register: "%{value} Kontoaktivierung"
- mail_subject_reminder: "%{count} Tickets müssen in den nächsten %{days} Tagen abgegeben werden"
+ mail_subject_reminder: "%{count} Arbeitspakete müssen in den nächsten %{days} Tagen abgegeben werden"
mail_subject_wiki_content_added: "Wiki-Seite '%{id}' hinzugefügt"
mail_subject_wiki_content_updated: "Wiki-Seite '%{id}' erfolgreich aktualisiert"
@@ -1168,7 +1172,7 @@ de:
permission_list_attachments: "Anhänge auflisten"
permission_log_time: "Aufwände buchen"
permission_manage_boards: "Foren verwalten"
- permission_manage_categories: "Ticket-Kategorien verwalten"
+ permission_manage_categories: "Arbeitspaket-Kategorien verwalten"
permission_manage_work_package_relations: "Arbeitspaket-Beziehungen verwalten"
permission_manage_members: "Mitglieder verwalten"
permission_manage_news: "News verwalten"
@@ -1227,7 +1231,7 @@ de:
setting_brute_force_block_after_failed_logins: "Sperre Benutzer nach dieser Anzahl fehlgeschlagener Loginversuche (deaktivieren mit 0)"
setting_brute_force_block_minutes: "Sperre Benutzer für Dauer (Minuten)"
setting_cache_formatted_text: "Formatierten Text im Cache speichern"
- setting_column_options: "Anpassen der Darstellung der Ticketlisten"
+ setting_column_options: "Anpassen der Darstellung der Arbeitspaketlisten"
setting_commit_fix_keywords: "Schlüsselwörter (Status)"
setting_commit_logs_encoding: "Kodierung der Commit-Log-Meldungen"
setting_commit_logtime_activity_id: "Aktivität für die Zeiterfassung"
@@ -1353,7 +1357,7 @@ de:
text_load_default_configuration: "Standard-Konfiguration laden"
text_min_max_length_info: "0 heißt keine Beschränkung"
text_no_configuration_data: >
- Rollen, Typ, Ticket-Status und Workflows wurden noch nicht
+ Rollen, Typ, Arbeitspaket-Status und Workflows wurden noch nicht
konfiguriert. Es ist sehr zu empfehlen, die Standard-Konfiguration
zu laden. Sobald sie geladen ist, können Sie sie abändern.
text_own_membership_delete_confirmation: "Sie sind dabei, einige oder alle Ihre Berechtigungen zu entfernen. Es ist möglich, dass Sie danach das Projekt nicht mehr ansehen oder bearbeiten dürfen.\nSind Sie sicher, dass Sie dies tun möchten?"
@@ -1375,7 +1379,7 @@ de:
text_tip_work_package_end_day: "Arbeitspaket, das an diesem Tag endet"
text_type_no_workflow: "Kein Workflow für diesen Typ definiert."
text_unallowed_characters: "Nicht erlaubte Zeichen"
- text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Tickets, deren Autor Sie sind oder die Ihnen zugewiesen sind)."
+ text_user_mail_option: "Für nicht ausgewählte Projekte werden Sie nur Benachrichtigungen für Dinge erhalten, die Sie beobachten oder an denen Sie beteiligt sind (z. B. Arbeitspakete, deren Autor Sie sind oder die Ihnen zugewiesen sind)."
text_user_wrote: "%{value} schrieb:"
text_warn_on_leaving_unsaved: "Der eingegebene Text wird nicht gespeichert, wenn Sie die Seite jetzt verlassen."
text_wiki_destroy_confirmation: "Sind Sie sicher, dass Sie dieses Wiki mit sämtlichem Inhalt löschen möchten?"
@@ -1622,6 +1626,7 @@ de:
menu_item: "Menüpunkt"
menu_item_setting: "Sichtbarkeit"
+
wiki_menu_item_for: "Menüpunkt für die Wikiseite \"%{title}\""
wiki_menu_item_setting: "Sichtbarkeit"
wiki_menu_item_new_main_item_explanation: >
diff --git a/config/locales/en.yml b/config/locales/en.yml
index f8a81555ae..4465f9abf0 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -454,6 +454,10 @@ en:
x_seconds:
one: "1 second"
other: "%{count} seconds"
+ units:
+ hour:
+ one: "hour"
+ other: "hours"
default_activity_design: "Design"
default_activity_development: "Development"
@@ -1612,6 +1616,7 @@ en:
menu_item: "Menu item"
menu_item_setting: "Visibility"
+
wiki_menu_item_for: "Menu item for wikipage \"%{title}\""
wiki_menu_item_setting: "Visibility"
wiki_menu_item_new_main_item_explanation: >
diff --git a/config/locales/js-de.yml b/config/locales/js-de.yml
index 19d61287da..6a09e2a779 100644
--- a/config/locales/js-de.yml
+++ b/config/locales/js-de.yml
@@ -41,6 +41,7 @@ de:
button_edit: "Bearbeiten"
button_log_time: "Aufwand buchen"
button_move: "Verschieben"
+ button_open_details: "Öffne Detailansicht"
button_quote: "Zitieren"
button_save: "Speichern"
button_uncheck_all: "Alles abwählen"
@@ -242,6 +243,22 @@ de:
title: "Ihr Browser wird nicht unterstützt"
message: "Sie verwenden einen veralteten Browser. OpenProject unterstützt diesen Browser nicht länger. Bitte aktualisieren Sie Ihren Browser."
learn_more: "Mehr erfahren"
+ wiki_formatting:
+ strong: "Fett"
+ italic: "Kursiv"
+ underline: "Unterstrichen"
+ deleted: "Duchgestrichen"
+ code: "Quelltext"
+ heading1: "Überschrift 1. Ordnung"
+ heading2: "Überschrift 2. Ordnung"
+ heading3: "Überschrift 3. Ordnung"
+ unordered_list: "Aufzählungsliste"
+ ordered_list: "Nummerierte Liste"
+ quote: "Zitieren"
+ unquote: "Zitat entfernen"
+ preformatted_text: "Präformatierter Text"
+ wiki_link: "Verweis (Link) zu einer Wiki-Seite"
+ image: "Grafik"
work_packages:
button_clear: "Zurücksetzen"
description_filter: "Filter"
diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml
index ee0e8388e4..342ccade77 100644
--- a/config/locales/js-en.yml
+++ b/config/locales/js-en.yml
@@ -41,6 +41,7 @@ en:
button_edit: "Edit"
button_log_time: "Log time"
button_move: "Move"
+ button_open_details: "Open details view"
button_quote: "Quote"
button_save: "Save"
button_uncheck_all: "Uncheck all"
@@ -244,6 +245,22 @@ en:
title: "Your browser is not supported"
message: "The browser you are using is no longer supported by OpenProject. Please update your browser."
learn_more: "Learn more"
+ wiki_formatting:
+ strong: "Strong"
+ italic: "Italic"
+ underline: "Underline"
+ deleted: "Deleted"
+ code: "Inline Code"
+ heading1: "Heading 1"
+ heading2: "Heading 2"
+ heading3: "Heading 3"
+ unordered_list: "Unordered List"
+ ordered_list: "Ordered List"
+ quote: "Quote"
+ unquote: "Unquote"
+ preformatted_text: "Preformatted Text"
+ wiki_link: "Link to a Wiki page"
+ image: "Image"
work_packages:
button_clear: "Clear"
description_filter: "Filter"
diff --git a/karma/tests/controllers/details-tab-overview-controller-test.js b/karma/tests/controllers/details-tab-overview-controller-test.js
index 774b99f928..a860b77e84 100644
--- a/karma/tests/controllers/details-tab-overview-controller-test.js
+++ b/karma/tests/controllers/details-tab-overview-controller-test.js
@@ -29,8 +29,14 @@
/*jshint expr: true*/
describe('DetailsTabOverviewController', function() {
+ var DEFAULT_WORK_PACKAGE_PROPERTIES = ['status', 'assignee', 'responsible',
+ 'date', 'percentageDone', 'priority',
+ 'estimatedTime', 'versionName', 'spentTime']
+
var scope;
var buildController;
+ var HookService;
+ var ConfigurationService;
var I18n = { t: angular.identity },
WorkPackagesHelper = {
formatWorkPackageProperty: angular.identity
@@ -57,6 +63,7 @@ describe('DetailsTabOverviewController', function() {
attachments: []
},
};
+ var workPackageAttributesStub;
function buildWorkPackageWithId(id) {
angular.extend(workPackage.props, {id: id});
@@ -68,7 +75,7 @@ describe('DetailsTabOverviewController', function() {
'openproject.config',
'openproject.workPackages.controllers'));
- beforeEach(inject(function($rootScope, $controller, $timeout) {
+ beforeEach(inject(function($rootScope, $controller, $timeout, _HookService_, _ConfigurationService_) {
var workPackageId = 99;
buildController = function() {
@@ -85,6 +92,11 @@ describe('DetailsTabOverviewController', function() {
$timeout.flush();
};
+ HookService = _HookService_;
+ ConfigurationService = _ConfigurationService_;
+
+ workPackageAttributesStub = sinon.stub(ConfigurationService, "workPackageAttributes");
+ workPackageAttributesStub.returns(DEFAULT_WORK_PACKAGE_PROPERTIES);
}));
describe('initialisation', function() {
@@ -330,7 +342,41 @@ describe('DetailsTabOverviewController', function() {
});
});
});
- });
+ describe('Plug-in properties', function() {
+ var propertyName = 'myPluginProperty';
+ var directiveName = 'my-plugin-property-directive';
+
+ beforeEach(function() {
+ gon.settings = { };
+ gon.settings.work_package_attributes = [propertyName];
+
+ var attributes = DEFAULT_WORK_PACKAGE_PROPERTIES.slice(0);
+ attributes.push(propertyName);
+
+ workPackageAttributesStub.returns(attributes);
+
+ var workPackageOverviewAttributesStub = sinon.stub(HookService, "call");
+ workPackageOverviewAttributesStub.withArgs('workPackageOverviewAttributes',
+ { type: propertyName,
+ workPackage: workPackage })
+ .returns([directiveName]);
+ workPackageOverviewAttributesStub.returns([]);
+
+ buildController();
+ });
+
+ it('adds plug-in property to present properties', function() {
+ expect(fetchPresentPropertiesWithName(propertyName)).to.have.length(1);
+ });
+ it('adds plug-in property to present properties', function() {
+ var propertyData = fetchPresentPropertiesWithName(propertyName)[0];
+
+ expect(propertyData.property).to.eq(propertyName);
+ expect(propertyData.format).to.eq('dynamic');
+ expect(propertyData.value).to.eq(directiveName);
+ });
+ });
+ });
});
diff --git a/karma/tests/controllers/work-package-details-controller-test.js b/karma/tests/controllers/work-package-details-controller-test.js
index 8a89cc6667..9f99b59225 100644
--- a/karma/tests/controllers/work-package-details-controller-test.js
+++ b/karma/tests/controllers/work-package-details-controller-test.js
@@ -50,6 +50,12 @@ describe('WorkPackageDetailsController', function() {
]
},
embedded: {
+ author: {
+ props: {
+ id: 1,
+ status: 1
+ }
+ },
activities: [],
watchers: [],
attachments: [],
diff --git a/karma/tests/directives/components/focus-test.js b/karma/tests/directives/components/focus-test.js
index 1f492c6820..6504fb48fe 100644
--- a/karma/tests/directives/components/focus-test.js
+++ b/karma/tests/directives/components/focus-test.js
@@ -32,7 +32,7 @@ describe('focus Directive', function() {
beforeEach(angular.mock.module('openproject.uiComponents'));
beforeEach(module('templates'));
- beforeEach(inject(function($compile, $rootScope, $document) {
+ beforeEach(inject(function($compile, $rootScope, $document, $timeout) {
var html = '';
doc = $document[0];
@@ -46,6 +46,8 @@ describe('focus Directive', function() {
$compile(element)(scope);
scope.$digest();
+
+ $timeout.flush();
};
}));
diff --git a/karma/tests/services/hook-service-test.js b/karma/tests/services/hook-service-test.js
new file mode 100644
index 0000000000..7f54a0e004
--- /dev/null
+++ b/karma/tests/services/hook-service-test.js
@@ -0,0 +1,176 @@
+//-- copyright
+// OpenProject is a project management system.
+// Copyright (C) 2012-2014 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*/
+
+describe('HookService', function() {
+
+ var HookService;
+ var validId = 'myValidCallbacks';
+
+ beforeEach(module('openproject.services'));
+
+ beforeEach(inject(function(_HookService_){
+ HookService = _HookService_;
+ }));
+
+ var shouldBehaveLikeEmptyResult = function(id) {
+ it('returns empty results', function() {
+ expect(HookService.call(id)).to.be.empty;
+ });
+ };
+
+ var shouldBehaveLikeResultWithElements = function(id, count) {
+ it('returns #count results', function() {
+ expect(HookService.call(id).length).to.eq(count);
+ });
+ };
+
+ var shouldBehaveLikeCalledCallback = function(id) {
+ beforeEach(function() {
+ HookService.call(id);
+ });
+
+ it('is called', function() {
+ expect(callback.called).to.be.true;
+ });
+ };
+
+ var shouldBehaveLikeUncalledCallback = function(id) {
+ beforeEach(function() {
+ HookService.call(id);
+ });
+
+ it('is not called', function() {
+ expect(invalidCallback.called).to.be.false;
+ });
+ };
+
+ describe('register', function() {
+ var invalidId = 'myInvalidCallbacks';
+
+ describe('no callback registered', function() {
+ shouldBehaveLikeEmptyResult(invalidId);
+ });
+
+ describe('undefined callback registered', function() {
+ beforeEach(function() {
+ HookService.register('myInvalidCallbacks');
+ });
+
+ shouldBehaveLikeEmptyResult(invalidId);
+ });
+
+ describe('non function callback registered', function() {
+ beforeEach(function() {
+ HookService.register('myInvalidCallbacks', 'eeek');
+ });
+
+ shouldBehaveLikeEmptyResult(invalidId);
+ });
+
+ describe('valid function callback registered', function() {
+ beforeEach(function() {
+ callback = sinon.spy();
+ HookService.register('myValidCallbacks', callback);
+ });
+
+ shouldBehaveLikeEmptyResult(validId);
+
+ shouldBehaveLikeCalledCallback(validId);
+ });
+ });
+
+ describe('call', function() {
+ describe('function that returns undefined', function() {
+ beforeEach(function() {
+ callback = sinon.spy();
+ HookService.register('myValidCallbacks', callback);
+ });
+
+ shouldBehaveLikeCalledCallback(validId);
+
+ shouldBehaveLikeEmptyResult(validId);
+ });
+
+ describe('function that returns something that is not undefined', function() {
+ beforeEach(function() {
+ callback = sinon.stub();
+ callback.returns(new Object());
+
+ HookService.register('myValidCallbacks', callback);
+ });
+
+ shouldBehaveLikeCalledCallback(validId);
+
+ shouldBehaveLikeResultWithElements(validId, 1);
+ });
+
+ describe('function that returns something that is not undefined', function() {
+ beforeEach(function() {
+ callback = sinon.stub();
+ callback.returns(new Object());
+
+ HookService.register('myValidCallbacks', callback);
+ });
+
+ shouldBehaveLikeCalledCallback(validId);
+
+ shouldBehaveLikeResultWithElements(validId, 1);
+ });
+
+ describe('function that returns something that is not undefined', function() {
+ beforeEach(function() {
+ callback = sinon.spy();
+ invalidCallback = sinon.spy();
+
+ HookService.register('myValidCallbacks', callback);
+
+ HookService.register('myInvalidCallbacks', invalidCallback);
+ });
+
+ shouldBehaveLikeCalledCallback(validId);
+
+ shouldBehaveLikeUncalledCallback(validId);
+ });
+
+ describe('function that returns something that is not undefined', function() {
+ beforeEach(function() {
+ callback1 = sinon.stub();
+ callback1.returns(new Object());
+ callback2 = sinon.stub();
+ callback2.returns(new Object());
+
+ HookService.register('myValidCallbacks', callback1);
+ HookService.register('myValidCallbacks', callback2);
+ });
+
+ shouldBehaveLikeResultWithElements(validId, 2);
+ });
+ });
+});
diff --git a/karma/tests/work_packages/work-package-context-menu-test.js b/karma/tests/work_packages/work-package-context-menu-test.js
index a1b224544f..bc5bb50dfc 100644
--- a/karma/tests/work_packages/work-package-context-menu-test.js
+++ b/karma/tests/work_packages/work-package-context-menu-test.js
@@ -100,11 +100,11 @@ describe('workPackageContextMenu', function() {
});
it('lists link tags for any permitted action', function(){
- expect(directListElements.length).to.equal(2);
+ expect(directListElements.length).to.equal(3);
});
it('assigns a css class named by the action', function(){
- expect(directListElements[0].className).to.equal(actions[0]);
+ expect(directListElements[1].className).to.equal(actions[0]);
});
it('adds an icon from the icon fonts to each list container', function() {
@@ -139,7 +139,7 @@ describe('workPackageContextMenu', function() {
});
it('displays a link triggering deleteWorkPackages within the scope', function() {
- expect(directListElements.find('a').attr('ng-click')).to.equal('deleteWorkPackages()');
+ expect(directListElements.find('a.icon-delete').attr('ng-click')).to.equal('deleteWorkPackages()');
});
});
});
diff --git a/lib/api/root.rb b/lib/api/root.rb
index 39917f4c48..1c9274d4ae 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -34,8 +34,17 @@
module API
class Root < Grape::API
prefix :api
+
+ class Formatter
+ def call(object, env)
+ object.respond_to?(:to_json) ? object.to_json : MultiJson.dump(object)
+ end
+ end
+
content_type 'hal+json', 'application/hal+json'
+ content_type :json, 'application/json'
format 'hal+json'
+ formatter 'hal+json', Formatter.new
helpers do
def current_user
@@ -56,7 +65,7 @@ module API
def build_representer(obj, model_klass, representer_klass, options = {})
model = (obj.kind_of?(Array)) ? obj.map{ |o| model_klass.new(o) } : model_klass.new(obj)
- representer_klass.new(model, options).to_json
+ representer_klass.new(model, options)
end
end
diff --git a/lib/api/v3/activities/activities_api.rb b/lib/api/v3/activities/activities_api.rb
index 522cedc3c3..201239b594 100644
--- a/lib/api/v3/activities/activities_api.rb
+++ b/lib/api/v3/activities/activities_api.rb
@@ -46,7 +46,7 @@ module API
get do
authorize(:view_project, context: @activity.journable.project)
- @representer.to_json
+ @representer
end
helpers do
@@ -55,7 +55,7 @@ module API
model = ::API::V3::Activities::ActivityModel.new(activity)
representer = ::API::V3::Activities::ActivityRepresenter.new(model)
- representer.to_json
+ representer
else
errors = activity.errors.full_messages.join(", ")
fail Errors::Validation.new(activity, description: errors)
diff --git a/lib/api/v3/activities/activity_representer.rb b/lib/api/v3/activities/activity_representer.rb
index 71e45c08d6..bdb2133c5d 100644
--- a/lib/api/v3/activities/activity_representer.rb
+++ b/lib/api/v3/activities/activity_representer.rb
@@ -49,20 +49,20 @@ module API
property :_type, exec_context: :decorator
link :self do
- { href: "#{root_url}api/v3/activities/#{represented.model.id}", title: "#{represented.model.id}" }
+ { href: "#{root_path}api/v3/activities/#{represented.model.id}", title: "#{represented.model.id}" }
end
link :workPackage do
- { href: "#{root_url}api/v3/work_packages/#{represented.model.journable.id}", title: "#{represented.model.journable.subject}" }
+ { href: "#{root_path}api/v3/work_packages/#{represented.model.journable.id}", title: "#{represented.model.journable.subject}" }
end
link :user do
- { href: "#{root_url}api/v3/users/#{represented.model.user.id}", title: "#{represented.model.user.name} - #{represented.model.user.login}" }
+ { href: "#{root_path}api/v3/users/#{represented.model.user.id}", title: "#{represented.model.user.name} - #{represented.model.user.login}" }
end
link :update do
{
- href: "#{root_url}api/v3/activities/#{represented.model.id}",
+ href: "#{root_path}api/v3/activities/#{represented.model.id}",
method: :patch,
title: "#{represented.model.id}"
} if current_user_allowed_to_edit?
diff --git a/lib/api/v3/attachments/attachment_representer.rb b/lib/api/v3/attachments/attachment_representer.rb
index 2961ac928e..83d125d848 100644
--- a/lib/api/v3/attachments/attachment_representer.rb
+++ b/lib/api/v3/attachments/attachment_representer.rb
@@ -43,17 +43,17 @@ module API
property :_type, exec_context: :decorator
link :self do
- { href: "#{root_url}api/v3/attachments/#{represented.model.id}", title: "#{represented.model.filename}" }
+ { href: "#{root_path}api/v3/attachments/#{represented.model.id}", title: "#{represented.model.filename}" }
end
link :work_package do
work_package = represented.model.container
- { href: "#{root_url}api/v3/work_packages/#{work_package.id}", title: "#{work_package.subject}" } unless work_package.nil?
+ { href: "#{root_path}api/v3/work_packages/#{work_package.id}", title: "#{work_package.subject}" } unless work_package.nil?
end
link :author do
author = represented.model.author
- { href: "#{root_url}api/v3/users/#{author.id}", title: "#{author.name} - #{author.login}" } unless author.nil?
+ { href: "#{root_path}api/v3/users/#{author.id}", title: "#{author.name} - #{author.login}" } unless author.nil?
end
property :id, getter: -> (*) { model.id }, render_nil: true
diff --git a/lib/api/v3/attachments/attachments_api.rb b/lib/api/v3/attachments/attachments_api.rb
index 4aa7c963b0..e5e056f73e 100644
--- a/lib/api/v3/attachments/attachments_api.rb
+++ b/lib/api/v3/attachments/attachments_api.rb
@@ -46,7 +46,7 @@ module API
get do
authorize(:view_project, context: @attachment.container.project)
- @representer.to_json
+ @representer
end
end
diff --git a/lib/api/v3/priorities/priorities_api.rb b/lib/api/v3/priorities/priorities_api.rb
new file mode 100644
index 0000000000..fd45646904
--- /dev/null
+++ b/lib/api/v3/priorities/priorities_api.rb
@@ -0,0 +1,49 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 API
+ module V3
+ module Priorities
+ class PrioritiesAPI < Grape::API
+
+ resources :priorities do
+ before do
+ @priorities = IssuePriority.all
+ @priorities.map! { |priority| PriorityModel.new(priority) }
+ end
+
+ get do
+ PriorityCollectionRepresenter.new(@priorities)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/priorities/priority_collection_representer.rb b/lib/api/v3/priorities/priority_collection_representer.rb
new file mode 100644
index 0000000000..d2d4b1142f
--- /dev/null
+++ b/lib/api/v3/priorities/priority_collection_representer.rb
@@ -0,0 +1,57 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'roar/decorator'
+require 'representable/json/collection'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ module Priorities
+ class PriorityCollectionRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ link :self do
+ "#{root_path}api/v3/priorities"
+ end
+
+ property :_type, exec_context: :decorator
+
+ collection :priorities, embedded: true, extend: PriorityRepresenter, getter: ->(_) { self }
+
+ def _type
+ 'Priorities'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/priorities/priority_model.rb b/lib/api/v3/priorities/priority_model.rb
new file mode 100644
index 0000000000..d8410b5c61
--- /dev/null
+++ b/lib/api/v3/priorities/priority_model.rb
@@ -0,0 +1,43 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'reform'
+require 'reform/form/coercion'
+
+module API
+ module V3
+ module Priorities
+ class PriorityModel < Reform::Form
+ include Coercion
+
+ property :name, type: String
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/priorities/priority_representer.rb b/lib/api/v3/priorities/priority_representer.rb
new file mode 100644
index 0000000000..ca3d007050
--- /dev/null
+++ b/lib/api/v3/priorities/priority_representer.rb
@@ -0,0 +1,54 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'roar/decorator'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ module Priorities
+ class PriorityRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include Roar::Representer::Feature::Hypermedia
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ property :_type, exec_context: :decorator
+
+ property :id, getter: -> (*) { model.id }, render_nil: true
+ property :name
+
+ def _type
+ 'Priority'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/projects/project_model.rb b/lib/api/v3/projects/project_model.rb
new file mode 100644
index 0000000000..756ec1d109
--- /dev/null
+++ b/lib/api/v3/projects/project_model.rb
@@ -0,0 +1,53 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'reform'
+require 'reform/form/coercion'
+
+module API
+ module V3
+ module Projects
+ class ProjectModel < Reform::Form
+ include Coercion
+
+ property :identifier, type: String, virtual: true
+ property :name, type: String
+ property :description, type: String
+ property :homepage, type: String
+
+ property :created_on, type: DateTime, virtual: true
+ property :updated_on, type: DateTime, virtual: true
+
+ def type
+ model.project_type.name if model.project_type
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/projects/project_representer.rb b/lib/api/v3/projects/project_representer.rb
new file mode 100644
index 0000000000..698218de76
--- /dev/null
+++ b/lib/api/v3/projects/project_representer.rb
@@ -0,0 +1,74 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'roar/decorator'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ module Projects
+ class ProjectRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include Roar::Representer::Feature::Hypermedia
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ property :_type, exec_context: :decorator
+
+ link :self do
+ {
+ href: "#{root_path}api/v3/projects/#{represented.model.id}",
+ title: "#{represented.name}"
+ }
+ end
+
+ link 'versions' do
+ "#{root_path}api/v3/projects/#{represented.model.id}/versions"
+ end
+
+ property :id, getter: -> (*) { model.id }, render_nil: true
+ property :identifier, render_nil: true
+
+ property :name, render_nil: true
+ property :description, render_nil: true
+ property :homepage
+
+ property :created_on, render_nil: true
+ property :updated_on, render_nil: true
+
+ property :type, render_nil: true
+
+ def _type
+ 'Project'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/projects/projects_api.rb b/lib/api/v3/projects/projects_api.rb
new file mode 100644
index 0000000000..f645c2cb74
--- /dev/null
+++ b/lib/api/v3/projects/projects_api.rb
@@ -0,0 +1,57 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 API
+ module V3
+ module Projects
+ class ProjectsAPI < Grape::API
+
+ resources :projects do
+ params do
+ requires :id, desc: 'Project id'
+ end
+
+ namespace ':id' do
+ before do
+ @project = Project.find(params[:id])
+ @model = ProjectModel.new(@project)
+ end
+
+ get do
+ authorize(:view_project, context: @project)
+ ProjectRepresenter.new(@model)
+ end
+
+ mount API::V3::Versions::VersionsAPI
+ end
+
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index 0a4b9e260d..a9b5be6314 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -40,7 +40,7 @@ module API
before do
@query = Query.find(params[:id])
- model = ::API::V3::Queries::QueryModel.new(query: @query)
+ model = ::API::V3::Queries::QueryModel.new(@query)
@representer = ::API::V3::Queries::QueryRepresenter.new(model)
end
@@ -59,16 +59,16 @@ module API
normalized_query_name, @query.id, title: @query.name
)
query_menu_item.save!
- @representer.to_json
+ @representer
end
patch :unstar do
authorize({ controller: :queries, action: :unstar }, context: @query.project, allow: allowed_to_manage_stars?)
query_menu_item = @query.query_menu_item
- return @representer.to_json if @query.query_menu_item.nil?
+ return @representer if @query.query_menu_item.nil?
query_menu_item.destroy
@query.reload
- @representer.to_json
+ @representer
end
end
diff --git a/lib/api/v3/queries/query_model.rb b/lib/api/v3/queries/query_model.rb
index 3f50e3584f..c10ec0fc01 100644
--- a/lib/api/v3/queries/query_model.rb
+++ b/lib/api/v3/queries/query_model.rb
@@ -34,24 +34,17 @@ module API
module V3
module Queries
class QueryModel < Reform::Form
- include Composition
include Coercion
- model :query
-
- property :name, on: :query, type: String
- property :project_id, on: :query, type: Integer
- property :user_id, on: :query, type: Integer
- property :filters, on: :query, type: String
- property :is_public, on: :query, type: String
- property :column_names, on: :query, type: String
- property :sort_criteria, on: :query, type: String
- property :group_by, on: :query, type: String
- property :display_sums, on: :query, type: String
-
- def query
- model[:query]
- end
+ property :name, type: String
+ property :project_id, type: Integer
+ property :user_id, type: Integer
+ property :filters, type: String
+ property :is_public, type: String
+ property :column_names, type: String
+ property :sort_criteria, type: String
+ property :group_by, type: String
+ property :display_sums, type: String
end
end
end
diff --git a/lib/api/v3/queries/query_representer.rb b/lib/api/v3/queries/query_representer.rb
index 3900f1111f..2ead890689 100644
--- a/lib/api/v3/queries/query_representer.rb
+++ b/lib/api/v3/queries/query_representer.rb
@@ -43,23 +43,23 @@ module API
property :_type, exec_context: :decorator
link :self do
- { href: "#{root_url}api/v3/queries/#{represented.query.id}", title: "#{represented.name}" }
+ { href: "#{root_path}api/v3/queries/#{represented.model.id}", title: "#{represented.name}" }
end
- property :id, getter: -> (*) { query.id }, render_nil: true
+ property :id, getter: -> (*) { model.id }, render_nil: true
property :name, render_nil: true
- property :project_id, getter: -> (*) { query.project.id }
- property :project_name, getter: -> (*) { query.project.try(:name) }
- property :user_id, getter: -> (*) { query.user.try(:id) }, render_nil: true
- property :user_name, getter: -> (*) { query.user.try(:name) }, render_nil: true
- property :user_login, getter: -> (*) { query.user.try(:login) }, render_nil: true
- property :user_mail, getter: -> (*) { query.user.try(:mail) }, render_nil: true
+ property :project_id, getter: -> (*) { model.project.id }
+ property :project_name, getter: -> (*) { model.project.try(:name) }
+ property :user_id, getter: -> (*) { model.user.try(:id) }, render_nil: true
+ property :user_name, getter: -> (*) { model.user.try(:name) }, render_nil: true
+ property :user_login, getter: -> (*) { model.user.try(:login) }, render_nil: true
+ property :user_mail, getter: -> (*) { model.user.try(:mail) }, render_nil: true
property :filters, render_nil: true
- property :is_public, getter: -> (*) { query.is_public.to_s }, render_nil: true
+ property :is_public, getter: -> (*) { model.is_public.to_s }, render_nil: true
property :column_names, render_nil: true
property :sort_criteria, render_nil: true
property :group_by, render_nil: true
- property :display_sums, getter: -> (*) { query.display_sums.to_s }, render_nil: true
+ property :display_sums, getter: -> (*) { model.display_sums.to_s }, render_nil: true
property :is_starred, getter: -> (*) { is_starred.to_s }, exec_context: :decorator
def _type
@@ -67,7 +67,7 @@ module API
end
def is_starred
- return true if !represented.query.query_menu_item.nil?
+ return true if !represented.model.query_menu_item.nil?
false
end
end
diff --git a/lib/api/v3/relations/relations_api.rb b/lib/api/v3/relations/relations_api.rb
index c708b0057d..728042d156 100644
--- a/lib/api/v3/relations/relations_api.rb
+++ b/lib/api/v3/relations/relations_api.rb
@@ -19,11 +19,10 @@ module API
r.delay = declared_params[:delay_id]
end
- if relation.valid?
+ if relation.valid? && relation.save
model = ::API::V3::WorkPackages::RelationModel.new(relation)
representer = ::API::V3::WorkPackages::RelationRepresenter.new(model, work_package: relation.to)
- relation.save!
- representer.to_json
+ representer
else
fail Errors::Validation.new(relation)
end
@@ -32,8 +31,7 @@ module API
namespace ':relation_id' do
delete do
authorize(:manage_work_package_relations, context: @work_package.project)
- relation = Relation.find(params[:relation_id])
- relation.delete
+ Relation.destroy(params[:relation_id])
status 204
end
end
diff --git a/lib/api/v3/root.rb b/lib/api/v3/root.rb
index 49966bff99..99c99bc412 100644
--- a/lib/api/v3/root.rb
+++ b/lib/api/v3/root.rb
@@ -38,9 +38,16 @@ module API
mount ::API::V3::Activities::ActivitiesAPI
mount ::API::V3::Attachments::AttachmentsAPI
+ mount ::API::V3::Priorities::PrioritiesAPI
+ mount ::API::V3::Projects::ProjectsAPI
mount ::API::V3::Queries::QueriesAPI
+ mount ::API::V3::Statuses::StatusesAPI
mount ::API::V3::Users::UsersAPI
mount ::API::V3::WorkPackages::WorkPackagesAPI
+
+ get '/' do
+ RootRepresenter.new({})
+ end
end
end
end
diff --git a/lib/api/v3/root_representer.rb b/lib/api/v3/root_representer.rb
new file mode 100644
index 0000000000..eec3f2fb59
--- /dev/null
+++ b/lib/api/v3/root_representer.rb
@@ -0,0 +1,58 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'roar/decorator'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ class RootRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include Roar::Representer::Feature::Hypermedia
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
+
+ link 'priorities' do
+ "#{root_path}api/v3/priorities"
+ end
+
+ link 'project' do
+ {
+ href: "#{root_path}api/v3/project/{project_id}",
+ templated: true
+ }
+ end
+
+ link 'statuses' do
+ "#{root_path}api/v3/statuses"
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/statuses/status_collection_representer.rb b/lib/api/v3/statuses/status_collection_representer.rb
new file mode 100644
index 0000000000..7cc5ef7cc4
--- /dev/null
+++ b/lib/api/v3/statuses/status_collection_representer.rb
@@ -0,0 +1,57 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 status 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 status 2
+# of the License, or (at your option) any later status.
+#
+# 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.
+#++
+
+require 'roar/decorator'
+require 'representable/json/collection'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ module Statuses
+ class StatusCollectionRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ link :self do
+ "#{root_path}api/v3/statuses"
+ end
+
+ property :_type, exec_context: :decorator
+
+ collection :statuses, embedded: true, extend: StatusRepresenter, getter: ->(_) { self }
+
+ def _type
+ 'Statuses'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/statuses/status_model.rb b/lib/api/v3/statuses/status_model.rb
new file mode 100644
index 0000000000..94197e1d97
--- /dev/null
+++ b/lib/api/v3/statuses/status_model.rb
@@ -0,0 +1,43 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'reform'
+require 'reform/form/coercion'
+
+module API
+ module V3
+ module Statuses
+ class StatusModel < Reform::Form
+ include Coercion
+
+ property :name, type: String
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/statuses/status_representer.rb b/lib/api/v3/statuses/status_representer.rb
new file mode 100644
index 0000000000..dd1fe4529a
--- /dev/null
+++ b/lib/api/v3/statuses/status_representer.rb
@@ -0,0 +1,54 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'roar/decorator'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ module Statuses
+ class StatusRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include Roar::Representer::Feature::Hypermedia
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ property :_type, exec_context: :decorator
+
+ property :id, getter: -> (*) { model.id }, render_nil: true
+ property :name
+
+ def _type
+ 'Status'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/statuses/statuses_api.rb b/lib/api/v3/statuses/statuses_api.rb
new file mode 100644
index 0000000000..12d1749150
--- /dev/null
+++ b/lib/api/v3/statuses/statuses_api.rb
@@ -0,0 +1,49 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 API
+ module V3
+ module Statuses
+ class StatusesAPI < Grape::API
+
+ resources :statuses do
+ before do
+ @statuses = Status.all
+ @statuses.map! { |status| StatusModel.new(status) }
+ end
+
+ get do
+ StatusCollectionRepresenter.new(@statuses)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/users/user_representer.rb b/lib/api/v3/users/user_representer.rb
index 698f9cb62d..cfb5cccee4 100644
--- a/lib/api/v3/users/user_representer.rb
+++ b/lib/api/v3/users/user_representer.rb
@@ -52,12 +52,12 @@ module API
property :_type, exec_context: :decorator
link :self do
- { href: "#{root_url}api/v3/users/#{represented.model.id}", title: "#{represented.model.name} - #{represented.model.login}" }
+ { href: "#{root_path}api/v3/users/#{represented.model.id}", title: "#{represented.model.name} - #{represented.model.login}" }
end
link :removeWatcher do
{
- href: "#{root_url}/api/v3/work_packages/#{@work_package.id}/watchers/#{represented.model.id}",
+ href: "#{root_path}api/v3/work_packages/#{@work_package.id}/watchers/#{represented.model.id}",
method: :delete,
title: 'Remove watcher'
} if @work_package && current_user_allowed_to(:delete_work_package_watchers, @work_package)
diff --git a/lib/api/v3/users/users_api.rb b/lib/api/v3/users/users_api.rb
index 47c625f056..bec83d11db 100644
--- a/lib/api/v3/users/users_api.rb
+++ b/lib/api/v3/users/users_api.rb
@@ -39,13 +39,12 @@ module API
namespace ':id' do
before do
- @user = User.find(params[:id])
- model = ::API::V3::Users::UserModel.new(@user)
- @representer = ::API::V3::Users::UserRepresenter.new(model)
+ @user = User.find(params[:id])
+ @model = UserModel.new(@user)
end
get do
- @representer.to_json
+ UserRepresenter.new(@model)
end
end
diff --git a/lib/api/v3/versions/version_collection_representer.rb b/lib/api/v3/versions/version_collection_representer.rb
new file mode 100644
index 0000000000..ba72192fc9
--- /dev/null
+++ b/lib/api/v3/versions/version_collection_representer.rb
@@ -0,0 +1,64 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'roar/decorator'
+require 'representable/json/collection'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ module Versions
+ class VersionCollectionRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ attr_reader :project
+
+ def initialize(model, project:)
+ @project = project
+ super(model)
+ end
+
+ link :self do
+ "#{root_path}api/v3/projects/#{project.id}/versions"
+ end
+
+ property :_type, exec_context: :decorator
+
+ collection :versions, embedded: true, extend: VersionRepresenter, getter: ->(_) { self }
+
+ def _type
+ 'Versions'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/versions/version_model.rb b/lib/api/v3/versions/version_model.rb
new file mode 100644
index 0000000000..9d7bc1b63a
--- /dev/null
+++ b/lib/api/v3/versions/version_model.rb
@@ -0,0 +1,43 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'reform'
+require 'reform/form/coercion'
+
+module API
+ module V3
+ module Versions
+ class VersionModel < Reform::Form
+ include Coercion
+
+ property :name, type: String
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/versions/version_representer.rb b/lib/api/v3/versions/version_representer.rb
new file mode 100644
index 0000000000..2dc22bfbb9
--- /dev/null
+++ b/lib/api/v3/versions/version_representer.rb
@@ -0,0 +1,54 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'roar/decorator'
+require 'roar/representer/json/hal'
+
+module API
+ module V3
+ module Versions
+ class VersionRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include Roar::Representer::Feature::Hypermedia
+ include OpenProject::StaticRouting::UrlHelpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ property :_type, exec_context: :decorator
+
+ property :id, getter: -> (*) { model.id }, render_nil: true
+ property :name
+
+ def _type
+ 'Version'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/versions/versions_api.rb b/lib/api/v3/versions/versions_api.rb
new file mode 100644
index 0000000000..321bdcc6fe
--- /dev/null
+++ b/lib/api/v3/versions/versions_api.rb
@@ -0,0 +1,49 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 API
+ module V3
+ module Versions
+ class VersionsAPI < Grape::API
+
+ resources :versions do
+ before do
+ @versions = @project.shared_versions.all
+ @versions.map! { |version| VersionModel.new(version) }
+ end
+
+ get do
+ VersionCollectionRepresenter.new(@versions, project: @project)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/work_packages/available_status_collection_representer.rb b/lib/api/v3/work_packages/available_status_collection_representer.rb
new file mode 100644
index 0000000000..e0c838b4b5
--- /dev/null
+++ b/lib/api/v3/work_packages/available_status_collection_representer.rb
@@ -0,0 +1,50 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 status 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 status 2
+# of the License, or (at your option) any later status.
+#
+# 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 API
+ module V3
+ module WorkPackages
+ class AvailableStatusCollectionRepresenter < ::API::V3::Statuses::StatusCollectionRepresenter
+ link :self do |opts|
+ "#{work_package_url(opts[:work_package_id])}/available_statuses"
+ end
+
+ link :work_package do |opts|
+ work_package_url(opts[:work_package_id])
+ end
+
+ private
+
+ def work_package_url(work_package_id)
+ "#{root_path}api/v3/work_packages/#{work_package_id}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/work_packages/relation_representer.rb b/lib/api/v3/work_packages/relation_representer.rb
index d3252752f9..0047fe4d36 100644
--- a/lib/api/v3/work_packages/relation_representer.rb
+++ b/lib/api/v3/work_packages/relation_representer.rb
@@ -51,20 +51,20 @@ module API
property :_type, exec_context: :decorator
link :self do
- { href: "#{root_url}api/v3/relations/#{represented.model.id}" }
+ { href: "#{root_path}api/v3/relations/#{represented.model.id}" }
end
link :relatedFrom do
- { href: "#{root_url}api/v3/work_packages/#{represented.model.from_id}" }
+ { href: "#{root_path}api/v3/work_packages/#{represented.model.from_id}" }
end
link :relatedTo do
- { href: "#{root_url}api/v3/work_packages/#{represented.model.to_id}" }
+ { href: "#{root_path}api/v3/work_packages/#{represented.model.to_id}" }
end
link :remove do
{
- href: "#{root_url}api/v3/work_packages/#{represented.model.from.id}/relations/#{represented.model.id}",
+ href: "#{root_path}api/v3/work_packages/#{represented.model.from.id}/relations/#{represented.model.id}",
method: :delete,
title: "Remove relation"
} if current_user_allowed_to(:manage_work_package_relations)
diff --git a/lib/api/v3/work_packages/statuses_api.rb b/lib/api/v3/work_packages/statuses_api.rb
new file mode 100644
index 0000000000..096e34db41
--- /dev/null
+++ b/lib/api/v3/work_packages/statuses_api.rb
@@ -0,0 +1,67 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 API
+ module V3
+ module WorkPackages
+ class StatusesAPI < Grape::API
+ class AvailableStatusesFormatter
+ # this is an ugly hack to get the work package id for the path to self
+ def work_package_id(env)
+ env['rack.routing_args'][:id]
+ end
+
+ def call(object, env)
+ if object.respond_to?(:to_json)
+ object.to_json(work_package_id: work_package_id(env))
+ else
+ MultiJson.dump(object)
+ end
+ end
+ end
+
+ formatter 'hal+json', AvailableStatusesFormatter.new
+
+ get '/available_statuses' do
+ authorize({ controller: :work_packages, action: :update }, context: work_package.project)
+
+ work_package.type = work_package.project.types.find_by_name(params[:type]) if params[:type]
+
+ statuses = work_package.new_statuses_allowed_to(current_user)
+
+ models = statuses.map { |status| ::API::V3::Statuses::StatusModel.new(status) }
+ represented = ::API::V3::WorkPackages::AvailableStatusCollectionRepresenter.new(models)
+
+ represented
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/api/v3/work_packages/work_package_model.rb b/lib/api/v3/work_packages/work_package_model.rb
index 9cb8061fa5..5c62ad0677 100644
--- a/lib/api/v3/work_packages/work_package_model.rb
+++ b/lib/api/v3/work_packages/work_package_model.rb
@@ -34,7 +34,6 @@ module API
module V3
module WorkPackages
class WorkPackageModel < Reform::Form
- include Composition
include Coercion
include ActionView::Helpers::UrlHelper
include OpenProject::TextFormatting
@@ -44,122 +43,125 @@ module API
# N.B. required by ActionView::Helpers::UrlHelper
def controller; nil; end
- model :work_package
+ property :subject, type: String
+ property :start_date, type: Date
+ property :due_date, type: Date
+ property :created_at, type: DateTime
+ property :updated_at, type: DateTime
+ property :author, type: String
+ property :project_id, type: Integer
+ property :parent_id, type: Integer
+ property :responsible_id, type: Integer
+ property :assigned_to_id, type: Integer
+ property :fixed_version_id, type: Integer
- property :subject, on: :work_package, type: String
- property :start_date, on: :work_package, type: Date
- property :due_date, on: :work_package, type: Date
- property :created_at, on: :work_package, type: DateTime
- property :updated_at, on: :work_package, type: DateTime
- property :author, on: :work_package, type: String
- property :project_id, on: :work_package, type: Integer
- property :responsible_id, on: :work_package, type: Integer
- property :assigned_to_id, on: :work_package, type: Integer
- property :fixed_version_id, on: :work_package, type: Integer
-
- def work_package
- model[:work_package]
- end
def description
- format_text(work_package, :description)
+ format_text(model, :description)
end
def raw_description
- work_package.description
+ model.description
end
def raw_description=(value)
- work_package.description = value
+ model.description = value
end
def type
- work_package.type.try(:name)
+ model.type.try(:name)
end
def type=(value)
- type = Type.find(:first, conditions: ['name ilike ?', value])
- work_package.type = type
+ model.type = Type.find_by_name(value)
end
def status
- work_package.status.try(:name)
+ model.status.try(:name)
end
def status=(value)
- status = Status.find(:first, conditions: ['name ilike ?', value])
- work_package.status = status
+ model.status = Status.find_by_name(value)
end
def priority
- work_package.priority.try(:name)
+ model.priority.try(:name)
end
def priority=(value)
- priority = IssuePriority.find(:first, conditions: ['name ilike ?', value])
- work_package.priority = priority
+ model.priority = IssuePriority.find_by_name(value)
end
def estimated_time
- { units: 'hours', value: work_package.estimated_hours }
+ { units: I18n.t(:'datetime.units.hour', count: model.estimated_hours.to_i),
+ value: model.estimated_hours }
end
def estimated_time=(value)
hours = ActiveSupport::JSON.decode(value)['value']
- work_package.estimated_hours = hours
+ model.estimated_hours = hours
end
def version_id=(value)
- work_package.fixed_version_id = value
+ model.fixed_version_id = value
end
def percentage_done
- work_package.done_ratio
+ model.done_ratio
end
def percentage_done=(value)
- work_package.done_ratio = value
+ model.done_ratio = value
end
def author
- ::API::V3::Users::UserModel.new(work_package.author) unless work_package.author.nil?
+ ::API::V3::Users::UserModel.new(model.author) unless model.author.nil?
end
def responsible
- ::API::V3::Users::UserModel.new(work_package.responsible) unless work_package.responsible.nil?
+ ::API::V3::Users::UserModel.new(model.responsible) unless model.responsible.nil?
end
def assignee
- ::API::V3::Users::UserModel.new(work_package.assigned_to) unless work_package.assigned_to.nil?
+ ::API::V3::Users::UserModel.new(model.assigned_to) unless model.assigned_to.nil?
end
def activities
- work_package.journals.map{ |journal| ::API::V3::Activities::ActivityModel.new(journal) }
+ model.journals.map{ |journal| ::API::V3::Activities::ActivityModel.new(journal) }
end
def attachments
- work_package.attachments
+ model.attachments
.map{ |attachment| ::API::V3::Attachments::AttachmentModel.new(attachment) }
end
def watchers
- work_package.watcher_users
+ model.watcher_users
.order(User::USER_FORMATS_STRUCTURE[Setting.user_format])
.map{ |u| ::API::V3::Users::UserModel.new(u) }
end
def relations
- relations = work_package.relations
- visible_relations = relations.find_all { |relation| relation.other_work_package(work_package).visible? }
+ relations = model.relations
+ visible_relations = relations.find_all { |relation| relation.other_work_package(model).visible? }
visible_relations.map{ |relation| RelationModel.new(relation) }
end
def is_closed
- work_package.closed?
+ model.closed?
end
validates_presence_of :subject, :project_id, :type, :author, :status
validates_length_of :subject, maximum: 255
+ validate :validate_parent_constraint
+
+ private
+
+ def validate_parent_constraint
+ if model.parent
+ errors.add :parent_id, :cannot_be_milestone if model.parent.is_milestone?
+ end
+ end
end
end
end
diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb
index f91d02e1ae..6d24aec34a 100644
--- a/lib/api/v3/work_packages/work_package_representer.rb
+++ b/lib/api/v3/work_packages/work_package_representer.rb
@@ -50,95 +50,114 @@ module API
property :_type, exec_context: :decorator
link :self do
- { href: "#{root_url}api/v3/work_packages/#{represented.work_package.id}", title: represented.subject }
+ {
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}",
+ title: "#{represented.subject}"
+ }
+ end
+
+ link :update do
+ {
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}",
+ method: :patch,
+ title: "Update #{represented.subject}"
+ } if current_user_allowed_to(:edit_work_packages, represented.model)
end
link :author do
{
- href: "#{root_url}/api/v3/users/#{represented.work_package.author.id}",
- title: "#{represented.work_package.author.name} - #{represented.work_package.author.login}"
- } unless represented.work_package.author.nil?
+ href: "#{root_path}api/v3/users/#{represented.model.author.id}",
+ title: "#{represented.model.author.name} - #{represented.model.author.login}"
+ } unless represented.model.author.nil?
end
link :responsible do
{
- href: "#{root_url}/api/v3/users/#{represented.work_package.responsible.id}",
- title: "#{represented.work_package.responsible.name} - #{represented.work_package.responsible.login}"
- } unless represented.work_package.responsible.nil?
+ href: "#{root_path}api/v3/users/#{represented.model.responsible.id}",
+ title: "#{represented.model.responsible.name} - #{represented.model.responsible.login}"
+ } unless represented.model.responsible.nil?
end
link :assignee do
{
- href: "#{root_url}/api/v3/users/#{represented.work_package.assigned_to.id}",
- title: "#{represented.work_package.assigned_to.name} - #{represented.work_package.assigned_to.login}"
- } unless represented.work_package.assigned_to.nil?
+ href: "#{root_path}api/v3/users/#{represented.model.assigned_to.id}",
+ title: "#{represented.model.assigned_to.name} - #{represented.model.assigned_to.login}"
+ } unless represented.model.assigned_to.nil?
+ end
+
+ link :availableStatuses do
+ {
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}/available_statuses",
+ title: 'Available Statuses'
+ } if @current_user.allowed_to?({ controller: :work_packages, action: :update },
+ represented.model.project)
end
link :availableWatchers do
- {
- href: "#{root_url}api/v3/work_packages/#{represented.work_package.id}/available_watchers",
- title: "Available Watchers"
- }
+ {
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}/available_watchers",
+ title: 'Available Watchers'
+ }
end
link :watch do
{
- href: "#{root_url}/api/v3/work_packages/#{represented.work_package.id}/watchers",
- method: :post,
- data: { user_id: @current_user.id },
- title: 'Watch work package'
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}/watchers",
+ method: :post,
+ data: { user_id: @current_user.id },
+ title: 'Watch work package'
} if !@current_user.anonymous? &&
- current_user_allowed_to(:view_work_packages, represented.work_package) &&
- !represented.work_package.watcher_users.include?(@current_user)
+ current_user_allowed_to(:view_work_packages, represented.model) &&
+ !represented.model.watcher_users.include?(@current_user)
end
link :unwatch do
{
- href: "#{root_url}/api/v3/work_packages/#{represented.work_package.id}/watchers/#{@current_user.id}",
- method: :delete,
- title: 'Unwatch work package'
- } if current_user_allowed_to(:view_work_packages, represented.work_package) && represented.work_package.watcher_users.include?(@current_user)
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}/watchers/#{@current_user.id}",
+ method: :delete,
+ title: 'Unwatch work package'
+ } if current_user_allowed_to(:view_work_packages, represented.model) && represented.model.watcher_users.include?(@current_user)
end
link :addWatcher do
{
- href: "#{root_url}/api/v3/work_packages/#{represented.work_package.id}/watchers{?user_id}",
- method: :post,
- title: 'Add watcher',
- templated: true
- } if current_user_allowed_to(:add_work_package_watchers, represented.work_package)
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}/watchers{?user_id}",
+ method: :post,
+ title: 'Add watcher',
+ templated: true
+ } if current_user_allowed_to(:add_work_package_watchers, represented.model)
end
link :addRelation do
{
- href: "#{root_url}/api/v3/work_packages/#{represented.work_package.id}/relations",
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}/relations",
method: :post,
title: 'Add relation'
- } if current_user_allowed_to(:manage_work_package_relations, represented.work_package)
+ } if current_user_allowed_to(:manage_work_package_relations, represented.model)
end
link :addComment do
{
- href: "#{root_url}api/v3/work_packages/#{represented.work_package.id}/activities",
+ href: "#{root_path}api/v3/work_packages/#{represented.model.id}/activities",
method: :post,
title: 'Add comment'
- } if current_user_allowed_to(:add_work_package_notes, represented.work_package)
+ } if current_user_allowed_to(:add_work_package_notes, represented.model)
end
link :parent do
{
- href: "#{root_url}/api/v3/work_packages/#{represented.work_package.parent.id}",
- title: represented.work_package.parent.subject
- } unless represented.work_package.parent.nil? || !represented.work_package.parent.visible?
+ href: "#{root_path}api/v3/work_packages/#{represented.model.parent.id}",
+ title: represented.model.parent.subject
+ } unless represented.model.parent.nil? || !represented.model.parent.visible?
end
links :children do
visible_children.map do |child|
- { href: "#{root_url}/api/v3/work_packages/#{child.id}", title: child.subject }
+ { href: "#{root_path}api/v3/work_packages/#{child.id}", title: child.subject }
end unless visible_children.empty?
end
- property :id, getter: -> (*) { work_package.id }, render_nil: true
+ property :id, getter: -> (*) { model.id }, render_nil: true
property :subject, render_nil: true
property :type, render_nil: true
property :description, render_nil: true
@@ -146,16 +165,17 @@ module API
property :status, render_nil: true
property :is_closed
property :priority, render_nil: true
- property :start_date, getter: -> (*) { work_package.start_date }, render_nil: true
- property :due_date, getter: -> (*) { work_package.due_date }, render_nil: true
+ property :start_date, getter: -> (*) { model.start_date.to_datetime.utc.iso8601 unless model.start_date.nil? }, render_nil: true
+ property :due_date, getter: -> (*) { model.due_date.to_datetime.utc.iso8601 unless model.due_date.nil? }, render_nil: true
property :estimated_time, render_nil: true
property :percentage_done, render_nil: true
- property :version_id, getter: -> (*) { work_package.fixed_version.try(:id) }, render_nil: true
- property :version_name, getter: -> (*) { work_package.fixed_version.try(:name) }, render_nil: true
- property :project_id, getter: -> (*) { work_package.project.id }
- property :project_name, getter: -> (*) { work_package.project.try(:name) }
- property :created_at, getter: -> (*) { work_package.created_at.utc.iso8601}, render_nil: true
- property :updated_at, getter: -> (*) { work_package.updated_at.utc.iso8601}, render_nil: true
+ property :version_id, getter: -> (*) { model.fixed_version.try(:id) }, render_nil: true
+ property :version_name, getter: -> (*) { model.fixed_version.try(:name) }, render_nil: true
+ property :project_id, getter: -> (*) { model.project.id }
+ property :project_name, getter: -> (*) { model.project.try(:name) }
+ property :parent_id, render_nil: true
+ property :created_at, getter: -> (*) { model.created_at.utc.iso8601}, render_nil: true
+ property :updated_at, getter: -> (*) { model.updated_at.utc.iso8601}, render_nil: true
collection :custom_properties, exec_context: :decorator, render_nil: true
@@ -164,7 +184,7 @@ module API
property :assignee, embedded: true, class: ::API::V3::Users::UserModel, decorator: ::API::V3::Users::UserRepresenter, if: -> (*) { !assignee.nil? }
property :activities, embedded: true, exec_context: :decorator
- property :watchers, embedded: true, exec_context: :decorator, if: -> (*) { current_user_allowed_to(:view_work_package_watchers, represented.work_package) }
+ property :watchers, embedded: true, exec_context: :decorator, if: -> (*) { current_user_allowed_to(:view_work_package_watchers, represented.model) }
collection :attachments, embedded: true, class: ::API::V3::Attachments::AttachmentModel, decorator: ::API::V3::Attachments::AttachmentRepresenter
property :relations, embedded: true, exec_context: :decorator
@@ -177,24 +197,24 @@ module API
end
def watchers
- represented.watchers.map{ |watcher| ::API::V3::Users::UserRepresenter.new(watcher, work_package: represented.work_package, current_user: @current_user) }
+ represented.watchers.map{ |watcher| ::API::V3::Users::UserRepresenter.new(watcher, work_package: represented.model, current_user: @current_user) }
end
def relations
- represented.relations.map{ |relation| RelationRepresenter.new(relation, work_package: represented.work_package, current_user: @current_user) }
+ represented.relations.map{ |relation| RelationRepresenter.new(relation, work_package: represented.model, current_user: @current_user) }
end
def custom_properties
- values = represented.work_package.custom_field_values
+ values = represented.model.custom_field_values
values.map { |v| { name: v.custom_field.name, format: v.custom_field.field_format, value: v.value }}
end
def current_user_allowed_to(permission, work_package)
- @current_user && @current_user.allowed_to?(permission, work_package.project)
+ @current_user && @current_user.allowed_to?(permission, represented.model.project)
end
def visible_children
- @visible_children ||= represented.work_package.children.find_all { |child| child.visible? }
+ @visible_children ||= represented.model.children.find_all { |child| child.visible? }
end
end
end
diff --git a/lib/api/v3/work_packages/work_packages_api.rb b/lib/api/v3/work_packages/work_packages_api.rb
index 7d0b18539f..0dc08ac5c5 100644
--- a/lib/api/v3/work_packages/work_packages_api.rb
+++ b/lib/api/v3/work_packages/work_packages_api.rb
@@ -38,15 +38,30 @@ module API
end
namespace ':id' do
+ helpers do
+ attr_reader :work_package
+ end
+
before do
@work_package = WorkPackage.find(params[:id])
- model = ::API::V3::WorkPackages::WorkPackageModel.new(work_package: @work_package)
+ model = ::API::V3::WorkPackages::WorkPackageModel.new(@work_package)
@representer = ::API::V3::WorkPackages::WorkPackageRepresenter.new(model, { current_user: current_user }, :activities, :users)
end
get do
authorize({ controller: :work_packages_api, action: :get }, context: @work_package.project)
- @representer.to_json
+ @representer
+ end
+
+ patch do
+ authorize(:edit_work_packages, context: @work_package.project)
+ @representer.from_json(env['api.request.input'])
+ @representer.represented.sync
+ if @representer.represented.model.valid? && @representer.represented.save
+ @representer
+ else
+ fail Errors::Validation.new(@representer.represented.model)
+ end
end
resource :activities do
@@ -57,7 +72,7 @@ module API
model = ::API::V3::Activities::ActivityModel.new(work_package.journals.last)
representer = ::API::V3::Activities::ActivityRepresenter.new(model, { current_user: current_user })
- representer.to_json
+ representer
else
errors = work_package.errors.full_messages.join(", ")
fail Errors::Validation.new(work_package, description: errors)
@@ -94,7 +109,9 @@ module API
end
mount ::API::V3::WorkPackages::WatchersAPI
+ mount ::API::V3::WorkPackages::StatusesAPI
mount ::API::V3::Relations::RelationsAPI
+
end
end
diff --git a/lib/plugins/acts_as_journalized/lib/journal_formatter/base.rb b/lib/plugins/acts_as_journalized/lib/journal_formatter/base.rb
index c778b18536..8abe5c6e07 100644
--- a/lib/plugins/acts_as_journalized/lib/journal_formatter/base.rb
+++ b/lib/plugins/acts_as_journalized/lib/journal_formatter/base.rb
@@ -64,9 +64,9 @@ class JournalFormatter::Base
def format_html_details(label, old_value, value)
label = content_tag('strong', label)
- old_value = content_tag("i", h(old_value)) if old_value && !old_value.blank?
+ old_value = content_tag("i", h(old_value), title: h(old_value)) if old_value && !old_value.blank?
old_value = content_tag("strike", old_value) if old_value and value.blank?
- value = content_tag("i", h(value)) if value.present?
+ value = content_tag("i", h(value), title: h(value)) if value.present?
value ||= ""
[label, old_value, value]
diff --git a/lib/redmine/i18n.rb b/lib/redmine/i18n.rb
index e768a52867..eb1aaf546f 100644
--- a/lib/redmine/i18n.rb
+++ b/lib/redmine/i18n.rb
@@ -101,7 +101,12 @@ module Redmine
end
def all_languages
- @@all_languages ||= Dir.glob(Rails.root.join('config/locales/*.yml')).collect {|f| File.basename(f).split('.').first}.collect(&:to_sym)
+ @@all_languages ||= begin
+ Dir.glob(Rails.root.join('config/locales/*.yml'))
+ .map { |f| File.basename(f).split('.').first }
+ .reject! { |l| /\Ajs-/.match(l.to_s) }
+ .map(&:to_sym)
+ end
end
def find_language(lang)
diff --git a/lib/redmine/wiki_formatting/textile/helper.rb b/lib/redmine/wiki_formatting/textile/helper.rb
index 85e4bdefee..be0bd5568c 100644
--- a/lib/redmine/wiki_formatting/textile/helper.rb
+++ b/lib/redmine/wiki_formatting/textile/helper.rb
@@ -38,7 +38,13 @@ module Redmine
:class => 'icon icon-help',
:onclick => "window.open(\"#{ url }\", \"\", \"resizable=yes, location=no, width=600, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
- javascript_tag("var wikiToolbar = new jsToolBar($('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript help_link}'); wikiToolbar.draw();")
+ javascript_tag(<<-EOF)
+ var wikiToolbar = new jsToolBar($('#{field_id}'));
+ wikiToolbar.setHelpLink('#{escape_javascript help_link}');
+ // initialize the toolbar later, so that i18n-js has a chance to set the translations
+ // for the wiki-buttons first.
+ jQuery(function(){ wikiToolbar.draw(); });
+ EOF
end
def initial_page_content(page)
@@ -46,12 +52,6 @@ module Redmine
end
def heads_for_wiki_formatter
- unless @heads_for_wiki_formatter_included
- content_for :header_tags do
- javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language.to_s.downcase}")
- end
- @heads_for_wiki_formatter_included = true
- end
end
end
end
diff --git a/lib/tasks/locales.rake b/lib/tasks/locales.rake
deleted file mode 100644
index ee50f6751f..0000000000
--- a/lib/tasks/locales.rake
+++ /dev/null
@@ -1,145 +0,0 @@
-#-- encoding: UTF-8
-#-- copyright
-# OpenProject is a project management system.
-# Copyright (C) 2012-2014 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.
-#++
-
-desc 'Updates and checks locales against en.yml'
-task :locales do
- %w(locales:update locales:check_interpolation).collect do |task|
- Rake::Task[task].invoke
- end
-end
-
-namespace :locales do
- desc 'Updates language files based on en.yml content (only works for new top level keys).'
- task :update do
- dir = ENV['DIR'] || './config/locales'
-
- en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en']
-
- files = Dir.glob(File.join(dir,'*.{yaml,yml}'))
- files.each do |file|
- puts "Updating file #{file}"
- file_strings = YAML.load(File.read(file))
- file_strings = file_strings[file_strings.keys.first]
-
- missing_keys = en_strings.keys - file_strings.keys
- next if missing_keys.empty?
-
- puts "==> Missing #{missing_keys.size} keys (#{missing_keys.join(', ')})"
- lang = File.open(file, 'a')
-
- missing_keys.each do |key|
- {key => en_strings[key]}.to_yaml.each_line do |line|
- next if line =~ /\A---/ || line.empty?
- puts " #{line}"
- lang << " #{line}"
- end
- end
-
- lang.close
- end
- end
-
- desc 'Checks interpolation arguments in locals against en.yml'
- task :check_interpolation do
- dir = ENV['DIR'] || './config/locales'
- en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en']
- files = Dir.glob(File.join(dir,'*.{yaml,yml}'))
- files.each do |file|
- file_strings = YAML.load(File.read(file))
- file_strings = file_strings[file_strings.keys.first]
-
- file_strings.each do |key, string|
- next unless string.is_a?(String)
- string.scan /%\{\w+\}/ do |match|
- unless en_strings[key].nil? || en_strings[key].include?(match)
- puts "#{file}: #{key} uses #{match} not found in en.yml"
- end
- end
- end
- end
- end
-
- desc <<-END_DESC
-Removes a translation string from all locale file (only works for top-level childless non-multiline keys, probably doesn\'t work on windows).
-
-Options:
- key=key_1,key_2 Comma-separated list of keys to delete
- skip=en,de Comma-separated list of locale files to ignore (filename without extension)
-END_DESC
-
- task :remove_key do
- dir = ENV['DIR'] || './config/locales'
- files = Dir.glob(File.join(dir,'*.yml'))
- skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil
- deletes = ENV['key'] ? Regexp.union(ENV['key'].split(',')) : nil
- # Ignore multiline keys (begin with | or >) and keys with children (nothing meaningful after :)
- delete_regex = /\A #{deletes}: +[^\|>\s#].*\z/
-
- files.each do |path|
- # Skip certain locales
- (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips
- puts "Deleting selected keys from #{path}"
- orig_content = File.open(path, 'r') {|file| file.read}
- File.open(path, 'w') {|file| orig_content.each_line {|line| file.puts line unless line.chomp =~ delete_regex}}
- end
- end
-
- desc <<-END_DESC
-Adds a new top-level translation string to all locale file (only works for childless keys, probably doesn\'t work on windows, doesn't check for duplicates).
-
-Options:
- key="some_key=foo"
- key1="another_key=bar"
- key_fb="foo=bar" Keys to add in the form key=value, every option of the form key[,\\d,_*] will be recognised
- skip=en,de Comma-separated list of locale files to ignore (filename without extension)
-END_DESC
-
- task :add_key do
- dir = ENV['DIR'] || './config/locales'
- files = Dir.glob(File.join(dir,'*.yml'))
- skips = ENV['skip'] ? Regexp.union(ENV['skip'].split(',')) : nil
- keys_regex = /\Akey(\d+|_.+)?\z/
- adds = ENV.reject {|k,v| !(k =~ keys_regex)}.values.collect {|v| Array.new v.split("=",2)}
- key_list = adds.collect {|v| v[0]}.join(", ")
-
- files.each do |path|
- # Skip certain locales
- (puts "Skipping #{path}"; next) if File.basename(path, ".yml") =~ skips
- # TODO: Check for dupliate/existing keys
- puts "Adding #{key_list} to #{path}"
- File.open(path, 'a') do |file|
- adds.each do |kv|
- Hash[*kv].to_yaml.each_line do |line|
- file.puts " #{line}" unless (line =~ /\A---/ || line.empty?)
- end
- end
- end
- end
- end
-end
diff --git a/public/templates/work_packages.list.details.html b/public/templates/work_packages.list.details.html
index 6d7e2d3d44..66f4280ace 100644
--- a/public/templates/work_packages.list.details.html
+++ b/public/templates/work_packages.list.details.html
@@ -27,7 +27,7 @@
diff --git a/spec/api/query_resource_spec.rb b/spec/api/query_resource_spec.rb
index 48c7af6bcf..43c6c2136d 100644
--- a/spec/api/query_resource_spec.rb
+++ b/spec/api/query_resource_spec.rb
@@ -49,7 +49,7 @@ describe 'API v3 Query resource', :type => :request do
"_type" => 'Query',
"_links" => {
"self" => {
- "href" => "http://localhost:3000/api/v3/queries/#{query.id}",
+ "href" => "/api/v3/queries/#{query.id}",
"title" => query.name
}
},
@@ -229,7 +229,7 @@ describe 'API v3 Query resource', :type => :request do
"_type" => 'Query',
"_links" => {
"self" => {
- "href" => "http://localhost:3000/api/v3/queries/#{query.id}",
+ "href" => "/api/v3/queries/#{query.id}",
"title" => query.name
}
},
diff --git a/spec/api/watcher_resource_spec.rb b/spec/api/watcher_resource_spec.rb
index 00c2cc45ab..d8fe4a7871 100644
--- a/spec/api/watcher_resource_spec.rb
+++ b/spec/api/watcher_resource_spec.rb
@@ -53,7 +53,7 @@ describe 'API v3 Watcher resource', :type => :request do
let(:new_watcher) { available_watcher }
before do
- post post_path, user_id: new_watcher.id
+ post post_path, %{{"user_id": #{new_watcher.id}}}, { 'CONTENT_TYPE' => 'application/json' }
end
context 'authorized user' do
diff --git a/spec/api/work_package_resource_spec.rb b/spec/api/work_package_resource_spec.rb
index e38f02a26c..87039ead19 100644
--- a/spec/api/work_package_resource_spec.rb
+++ b/spec/api/work_package_resource_spec.rb
@@ -64,7 +64,7 @@ h4. things we like
}}
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project', :is_public => false) }
- let(:role) { FactoryGirl.create(:role, permissions: [:view_work_packages, :view_timelines]) }
+ let(:role) { FactoryGirl.create(:role, permissions: [:view_work_packages, :view_timelines, :edit_work_packages]) }
let(:current_user) { FactoryGirl.create(:user, member_in_project: project, member_through_role: role) }
let(:watcher) do
FactoryGirl
@@ -205,4 +205,71 @@ h4. things we like
end
end
+
+ describe '#patch' do
+ let(:patch_path) { "/api/v3/work_packages/#{work_package.id}" }
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ patch patch_path, params.to_json, { 'CONTENT_TYPE' => 'application/json' }
+ end
+ subject(:response) { last_response }
+
+ context 'user with needed permissions' do
+ context 'valid update' do
+ let(:params) do
+ {
+ subject: 'Updated subject',
+ rawDescription: '
Updated description
',
+ priority: FactoryGirl.create(:priority).name,
+ startDate: (Date.yesterday - 1.week).to_datetime.utc.iso8601,
+ dueDate: (Date.yesterday + 2.weeks).to_datetime.utc.iso8601,
+ percentageDone: 90,
+ }
+ end
+
+ it 'should respond with 200' do
+ expect(response.status).to eq(200)
+ end
+
+ it 'should respond with updated work package' do
+ expect(subject.body).to be_json_eql('Updated subject'.to_json).at_path('subject')
+ expect(subject.body).to be_json_eql(params[:priority].to_json).at_path('priority')
+ end
+
+ it 'should update the dates in iso8601 format' do
+ expect(subject.body).to be_json_eql(params[:startDate].to_json).at_path('startDate')
+ expect(subject.body).to be_json_eql(params[:dueDate].to_json).at_path('dueDate')
+ end
+
+ it 'should allow html in raw description' do
+ expect(subject.body).to be_json_eql('
Updated description
'.to_json).at_path('rawDescription')
+ end
+
+ end
+
+ context 'invalid update' do
+ let(:params) do
+ {
+ subject: ' ',
+ type: FactoryGirl.create(:type).name,
+ rawDescription: '
Updated description
',
+ status: FactoryGirl.create(:status).name,
+ priority: FactoryGirl.create(:priority).name,
+ startDate: (Date.new - 1.week).to_datetime.utc.iso8601,
+ dueDate: (Date.new + 2.weeks).to_datetime.utc.iso8601,
+ percentageDone: 90,
+ }
+ end
+
+ it 'should respond with 422' do
+ expect(response.status).to eq 422
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq(["Subject can't be blank", "Type is not included in the list"])
+ end
+ end
+ end
+ end
end
diff --git a/spec/controllers/work_packages_controller_spec.rb b/spec/controllers/work_packages_controller_spec.rb
index cb0ed7bb1a..299965a7f8 100644
--- a/spec/controllers/work_packages_controller_spec.rb
+++ b/spec/controllers/work_packages_controller_spec.rb
@@ -502,8 +502,8 @@ describe WorkPackagesController, :type => :controller do
# default activity counts as blank as long as everything else is blank too
put 'update', params.call(work_package.id, default_activity.id)
- expect(response.status).to eq(200)
- expect(response.body).to have_content("Successful update")
+ expect(flash[:notice]).to eq(I18n.t(:notice_successful_update))
+ expect(response).to redirect_to(work_package_path(work_package))
end
it 'should still give an error for a non-blank time entry' do
@@ -535,10 +535,10 @@ describe WorkPackagesController, :type => :controller do
.and_return(true)
end
- it 'should respond with 200 OK' do
+ it 'should redirect to the show action' do
call_action
- expect(response.response_code).to eq(200)
+ expect(response).to redirect_to(work_package_path(stub_work_package))
end
it 'should show a flash message' do
@@ -575,10 +575,10 @@ describe WorkPackagesController, :type => :controller do
.and_return([double('unsaved_attachment')])
end
- it 'should respond with 200 OK' do
+ it 'should redirect to the show action' do
call_action
- expect(response.response_code).to eq(200)
+ expect(response).to redirect_to(work_package_path(stub_work_package))
end
it 'should show a flash message' do
diff --git a/spec/features/accessibility/work_packages/work_package_query_spec.rb b/spec/features/accessibility/work_packages/work_package_query_spec.rb
index 140cd0e21b..def75bb1d5 100644
--- a/spec/features/accessibility/work_packages/work_package_query_spec.rb
+++ b/spec/features/accessibility/work_packages/work_package_query_spec.rb
@@ -32,8 +32,8 @@ require 'features/work_packages/work_packages_page'
describe 'Work package index accessibility', :type => :feature do
let(:user) { FactoryGirl.create(:admin) }
let(:project) { FactoryGirl.create(:project) }
- let!(:work_package) { FactoryGirl.create(:work_package,
- project: project) }
+ let(:work_package) { FactoryGirl.create(:work_package,
+ project: project) }
let(:work_packages_page) { WorkPackagesPage.new(project) }
let(:sort_ascending_selector) { '.icon-sort-ascending' }
let(:sort_descending_selector) { '.icon-sort-descending' }
@@ -41,25 +41,29 @@ describe 'Work package index accessibility', :type => :feature do
before do
allow(User).to receive(:current).and_return(user)
+ work_package
+
work_packages_page.visit_index
end
describe 'Select all link' do
- def select_all_link
- find('table.workpackages-table th.checkbox a')
- end
+ let(:link_selector) { 'table.workpackages-table th.checkbox a' }
describe 'Initial state', js: true do
- it { expect(select_all_link).not_to be_nil }
+ it { expect(page).to have_selector(link_selector) }
- it { expect(select_all_link[:title]).to eq(I18n.t(:button_check_all)) }
+ context 'attributes' do
+ before { expect(page).to have_selector(link_selector) }
- it { expect(select_all_link[:alt]).to eq(I18n.t(:button_check_all)) }
+ it { expect(find(link_selector)[:title]).to eq(I18n.t(:button_check_all)) }
- it do
- expect(select_all_link).to have_selector('.hidden-for-sighted',
- :visible => false,
- :text => I18n.t(:button_check_all))
+ it { expect(find(link_selector)[:alt]).to eq(I18n.t(:button_check_all)) }
+
+ it do
+ expect(find(link_selector)).to have_selector('.hidden-for-sighted',
+ visible: false,
+ text: I18n.t(:button_check_all))
+ end
end
end
@@ -81,26 +85,22 @@ describe 'Work package index accessibility', :type => :feature do
end
describe 'Sort link', js: true do
- def column_header_link
- find(column_header_link_selector)
- end
-
def click_sort_ascending_link
- execute_script "jQuery('#{sort_ascending_selector}').click()"
+ expect(page).to have_selector(sort_ascending_selector)
+ element = find(sort_ascending_selector)
+ element.click
end
def click_sort_descending_link
- execute_script "jQuery('#{sort_descending_selector}').click()"
+ expect(page).to have_selector(sort_descending_selector)
+ element = find(sort_descending_selector)
+ element.click
end
shared_examples_for 'sort column' do
- def column_header
- find(column_header_selector)
- end
-
it do
- expect(column_header).not_to be_nil
- expect(column_header.find("span.sort-header")[:title]).to eq(sort_text)
+ expect(page).to have_selector(column_header_selector)
+ expect(find(column_header_selector + " span.sort-header")[:title]).to eq(sort_text)
end
end
@@ -123,13 +123,15 @@ describe 'Work package index accessibility', :type => :feature do
end
shared_examples_for 'sortable column' do
+ before { expect(page).to have_selector(column_header_selector) }
+
describe 'Initial sort' do
it_behaves_like 'unsorted column'
end
describe 'descending' do
before do
- column_header_link.click
+ find(column_header_link_selector).click
click_sort_descending_link
end
@@ -138,7 +140,7 @@ describe 'Work package index accessibility', :type => :feature do
describe 'ascending' do
before do
- column_header_link.click
+ find(column_header_link_selector).click
click_sort_ascending_link
end
@@ -148,7 +150,7 @@ describe 'Work package index accessibility', :type => :feature do
describe 'id column' do
let(:link_caption) { '#' }
- let(:column_header_selector) { 'table.workpackages-table th.checkbox + th + th' }
+ let(:column_header_selector) { 'table.workpackages-table th:nth-of-type(2)' }
let(:column_header_link_selector) { column_header_selector + ' a' }
it_behaves_like 'sortable column'
@@ -156,7 +158,7 @@ describe 'Work package index accessibility', :type => :feature do
describe 'type column' do
let(:link_caption) { 'Type' }
- let(:column_header_selector) { 'table.workpackages-table th.checkbox + th + th + th' }
+ let(:column_header_selector) { 'table.workpackages-table th:nth-of-type(3)' }
let(:column_header_link_selector) { column_header_selector + ' a' }
it_behaves_like 'sortable column'
@@ -164,7 +166,7 @@ describe 'Work package index accessibility', :type => :feature do
describe 'status column' do
let(:link_caption) { 'Status' }
- let(:column_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th' }
+ let(:column_header_selector) { 'table.workpackages-table th:nth-of-type(4)' }
let(:column_header_link_selector) { column_header_selector + ' a' }
it_behaves_like 'sortable column'
@@ -172,7 +174,7 @@ describe 'Work package index accessibility', :type => :feature do
describe 'priority column' do
let(:link_caption) { 'Priority' }
- let(:column_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th + th' }
+ let(:column_header_selector) { 'table.workpackages-table th:nth-of-type(5)' }
let(:column_header_link_selector) { column_header_selector + ' a' }
it_behaves_like 'sortable column'
@@ -180,7 +182,7 @@ describe 'Work package index accessibility', :type => :feature do
describe 'subject column' do
let(:link_caption) { 'Subject' }
- let(:column_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th + th + th' }
+ let(:column_header_selector) { 'table.workpackages-table th:nth-of-type(6)' }
let(:column_header_link_selector) { column_header_selector + ' a' }
it_behaves_like 'sortable column'
@@ -188,10 +190,50 @@ describe 'Work package index accessibility', :type => :feature do
describe 'assigned to column' do
let(:link_caption) { 'Assignee' }
- let(:column_header_selector) { 'table.workpackages-table th.checkbox + th + th + th + th + th + th + th' }
+ let(:column_header_selector) { 'table.workpackages-table th:nth-of-type(7)' }
let(:column_header_link_selector) { column_header_selector + ' a' }
it_behaves_like 'sortable column'
end
end
+
+ describe 'context menus' do
+ shared_examples_for 'context menu' do
+ describe 'focus' do
+ before do
+ expect(page).to have_selector(source_link)
+ element = find(source_link)
+ element.native.send_keys(keys)
+ end
+
+ it { expect(page).to have_selector(target_link + ':focus') }
+
+ describe 'reset' do
+ before do
+ expect(page).to have_selector(target_link)
+ element = find(target_link)
+ element.native.send_keys(:enter)
+ end
+
+ it { expect(page).to have_selector(source_link + ':focus') }
+ end
+ end
+ end
+
+ describe 'work package context menu', js: true do
+ it_behaves_like 'context menu' do
+ let(:target_link) { '#work-package-context-menu li.open a' }
+ let(:source_link) { ".workpackages-table tr.issue td.id a" }
+ let(:keys) { [:shift, :alt, :f10] }
+ end
+ end
+
+ describe 'column header drop down menu', js: true do
+ it_behaves_like 'context menu' do
+ let(:source_link) { 'table.workpackages-table th:nth-of-type(2) a' }
+ let(:target_link) { '#column-context-menu .menu li:first-of-type a' }
+ let(:keys) { :enter }
+ end
+ end
+ end
end
diff --git a/spec/features/work_packages/select_work_package_row_spec.rb b/spec/features/work_packages/select_work_package_row_spec.rb
new file mode 100644
index 0000000000..f1b53cabf1
--- /dev/null
+++ b/spec/features/work_packages/select_work_package_row_spec.rb
@@ -0,0 +1,315 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+require 'features/work_packages/work_packages_page'
+
+describe 'Select work package row', :type => :feature do
+ let(:user) { FactoryGirl.create(:admin) }
+ let(:project) { FactoryGirl.create(:project) }
+ let(:work_package_1) { FactoryGirl.create(:work_package,
+ project: project) }
+ let(:work_package_2) { FactoryGirl.create(:work_package,
+ project: project) }
+ let(:work_package_3) { FactoryGirl.create(:work_package,
+ project: project) }
+ let(:work_packages_page) { WorkPackagesPage.new(project) }
+
+ before do
+ allow(User).to receive(:current).and_return(user)
+
+ work_package_1
+ work_package_2
+ work_package_3
+
+ work_packages_page.visit_index
+ end
+
+ describe 'Work package row selection', js: true do
+ def select_work_package_row(number, mouse_button_behavior=:left)
+ element = find(".workpackages-table tr:nth-of-type(#{number}).issue td.id")
+ case mouse_button_behavior
+ when :double
+ element.double_click
+ when :right
+ element.right_click
+ else
+ element.click
+ end
+ end
+
+ def select_work_package_row_with_shift(number)
+ element = find(".workpackages-table tr:nth-of-type(#{number}).issue td.id")
+ page.driver.browser.action.key_down(:shift)
+ .click(element.native)
+ .key_up(:shift)
+ .perform
+ end
+
+ def select_work_package_row_with_ctrl(number)
+ element = find(".workpackages-table tr:nth-of-type(#{number}).issue td.id")
+ page.driver.browser.action.key_down(:control)
+ .click(element.native)
+ .key_up(:control)
+ .perform
+ end
+
+ def check_row_selection_state(row_index, state=true)
+ selector = ".workpackages-table tr:nth-of-type(#{row_index}).issue input[type=checkbox]:checked"
+
+ expect(page).to (state ? have_selector(selector) : have_no_selector(selector))
+ end
+
+ shared_examples_for 'work package row selected' do
+ let(:indices) { Array(index) }
+
+ it do
+ Capybara.default_selector = :css
+
+ indices.each do |i|
+ check_row_selection_state(i);
+ end
+ end
+ end
+
+ shared_examples_for 'work package row not selected' do
+ let(:indices) { Array(index) }
+
+ it do
+ Capybara.default_selector = :css
+
+ indices.each do |i|
+ check_row_selection_state(i, false);
+ end
+ end
+ end
+
+ shared_examples_for 'right click preserves selection' do
+ before { select_work_package_row(selected_rows.first, :right) }
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { selected_rows }
+ end
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { unselected_rows }
+ end
+ end
+
+ describe 'single selection' do
+ shared_examples_for 'single select' do
+ before { select_work_package_row(1, mouse_button) }
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { 1 }
+ end
+
+ context 'select a different row' do
+ before do
+ check_row_selection_state(1);
+ select_work_package_row(2, mouse_button)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { 2 }
+ end
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { 1 }
+ end
+ end
+ end
+
+ shared_examples_for 'double select unselects' do
+ context 'clicking selected row again' do
+ before do
+ select_work_package_row(1, mouse_button)
+ check_row_selection_state(1);
+ select_work_package_row(1, mouse_button)
+ end
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { 1 }
+ end
+ end
+ end
+
+ it_behaves_like 'single select' do
+ let(:mouse_button) { :left }
+ end
+
+ it_behaves_like 'double select unselects' do
+ let(:mouse_button) { :left }
+ end
+
+ it_behaves_like 'single select' do
+ let(:mouse_button) { :right }
+ end
+ end
+
+ describe 'range selection' do
+ context 'first row selected' do
+ before { select_work_package_row_with_shift(1) }
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { 1 }
+ end
+
+ context 'select following row' do
+ before do
+ check_row_selection_state(1);
+ select_work_package_row_with_shift(2)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { [1, 2] }
+ end
+
+ context 'uninvolved row' do
+ before { check_row_selection_state(2) }
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { 3 }
+ end
+
+ it_behaves_like 'right click preserves selection' do
+ let(:selected_rows) { [1, 2] }
+ let(:unselected_rows) { 3 }
+ end
+ end
+ end
+
+ context 'select first after next row' do
+ before do
+ check_row_selection_state(1);
+ select_work_package_row_with_shift(3)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { [1, 2, 3] }
+ end
+
+ context 'select row after first selected row' do
+ before do
+ check_row_selection_state(2)
+ check_row_selection_state(3)
+
+ select_work_package_row_with_shift(2)
+
+ check_row_selection_state(3, false)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { [1, 2] }
+ end
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { 3 }
+ end
+ end
+ end
+ end
+
+ context 'swapping' do
+ before { select_work_package_row(2) }
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { 2 }
+ end
+
+ context 'select predecessor' do
+ before do
+ check_row_selection_state(2)
+ select_work_package_row_with_shift(1)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { [1, 2] }
+ end
+
+ context 'select successor' do
+ before do
+ check_row_selection_state(1)
+ select_work_package_row_with_shift(3)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { [2, 3] }
+ end
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { 1 }
+ end
+ end
+ end
+ end
+ end
+
+ describe 'specific selection' do
+ before { select_work_package_row_with_ctrl(1) }
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { 1 }
+ end
+
+ context 'select first after next row' do
+ before do
+ check_row_selection_state(1)
+ select_work_package_row_with_ctrl(3)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { [1, 3] }
+ end
+
+ context 'uninvolved row' do
+ before { check_row_selection_state(3) }
+
+ it_behaves_like 'work package row not selected' do
+ let(:index) { 2 }
+ end
+
+ it_behaves_like 'right click preserves selection' do
+ let(:selected_rows) { [1, 3] }
+ let(:unselected_rows) { 2 }
+ end
+ end
+ end
+ end
+
+ describe 'opening work package details' do
+ before do
+ select_work_package_row(1, :double)
+ end
+
+ it_behaves_like 'work package row selected' do
+ let(:index) { 1 }
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/v3/priorities/priority_collection_representer_spec.rb b/spec/lib/api/v3/priorities/priority_collection_representer_spec.rb
new file mode 100644
index 0000000000..9772960da7
--- /dev/null
+++ b/spec/lib/api/v3/priorities/priority_collection_representer_spec.rb
@@ -0,0 +1,54 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 status 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 status 2
+# of the License, or (at your option) any later status.
+#
+# 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Priorities::PriorityCollectionRepresenter do
+ let(:priorities) { FactoryGirl.build_list(:priority, 3) }
+ let(:models) { priorities.map { |priority|
+ ::API::V3::Priorities::PriorityModel.new(priority)
+ } }
+ let(:representer) { described_class.new(models) }
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it { should include_json('Priorities'.to_json).at_path('_type') }
+
+ it { should have_json_type(Object).at_path('_links') }
+ it 'should link to self' do
+ expect(subject).to have_json_path('_links/self/href')
+ end
+
+ describe 'priorities' do
+ it { should have_json_path('_embedded/priorities') }
+ it { should have_json_size(3).at_path('_embedded/priorities') }
+ it { should have_json_path('_embedded/priorities/2/name') }
+ end
+ end
+end
diff --git a/spec/lib/api/v3/priorities/priority_model_spec.rb b/spec/lib/api/v3/priorities/priority_model_spec.rb
new file mode 100644
index 0000000000..0f1f25cd9b
--- /dev/null
+++ b/spec/lib/api/v3/priorities/priority_model_spec.rb
@@ -0,0 +1,37 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Priorities::PriorityModel do
+ subject(:model) { ::API::V3::Priorities::PriorityModel.new(priority) }
+ let(:priority) { FactoryGirl.build(:priority, attributes) }
+ let(:attributes) { { name: 'Specific Priority' } }
+
+ its(:name) { should eq 'Specific Priority' }
+end
diff --git a/spec/lib/api/v3/priorities/priority_representer_spec.rb b/spec/lib/api/v3/priorities/priority_representer_spec.rb
new file mode 100644
index 0000000000..ecdbc3e714
--- /dev/null
+++ b/spec/lib/api/v3/priorities/priority_representer_spec.rb
@@ -0,0 +1,51 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Priorities::PriorityRepresenter do
+ let(:priority) { FactoryGirl.build(:priority) }
+ let(:model) { ::API::V3::Priorities::PriorityModel.new(priority) }
+ let(:representer) { described_class.new(model) }
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it { should include_json('Priority'.to_json).at_path('_type') }
+
+ xit { should have_json_type(Object).at_path('_links') }
+ xit 'should link to self' do
+ expect(subject).to have_json_path('_links/self/href')
+ end
+
+ describe 'priority' do
+ it { should have_json_path('id') }
+ it { should have_json_path('name') }
+ end
+ end
+end
diff --git a/spec/lib/api/v3/projects/project_model_spec.rb b/spec/lib/api/v3/projects/project_model_spec.rb
new file mode 100644
index 0000000000..ac28fe9ff8
--- /dev/null
+++ b/spec/lib/api/v3/projects/project_model_spec.rb
@@ -0,0 +1,44 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Projects::ProjectModel do
+ subject(:model) { ::API::V3::Projects::ProjectModel.new(project) }
+
+ let(:project_type) {
+ FactoryGirl.build(:project_type, id: 1, name: 'Hypermedia-Ready Type')
+ }
+ let(:project) {
+ FactoryGirl.build(:project, attributes.merge(project_type: project_type))
+ }
+ let(:attributes) { { name: 'Hypermedia-Ready Project' } }
+
+ its(:name) { should eq 'Hypermedia-Ready Project' }
+ its(:type) { should eq 'Hypermedia-Ready Type' }
+end
diff --git a/spec/lib/api/v3/projects/project_representer_spec.rb b/spec/lib/api/v3/projects/project_representer_spec.rb
new file mode 100644
index 0000000000..6b96b04035
--- /dev/null
+++ b/spec/lib/api/v3/projects/project_representer_spec.rb
@@ -0,0 +1,63 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Projects::ProjectRepresenter do
+ let(:project) { FactoryGirl.build(:project) }
+ let(:model) { ::API::V3::Projects::ProjectModel.new(project) }
+ let(:representer) { described_class.new(model) }
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it { should include_json('Project'.to_json).at_path('_type') }
+
+ describe 'project' do
+ it { should have_json_path('id') }
+ it { should have_json_path('identifier') }
+ it { should have_json_path('name') }
+ it { should have_json_path('description') }
+ it { should have_json_path('createdOn') }
+ it { should have_json_path('updatedOn') }
+ it { should have_json_path('type') }
+ end
+
+ describe '_links' do
+ it { should have_json_type(Object).at_path('_links') }
+ it 'should link to self' do
+ expect(subject).to have_json_path('_links/self/href')
+ end
+
+ describe 'versions' do
+ it { should have_json_path('_links/versions') }
+ it { should have_json_path('_links/versions/href') }
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/v3/root_representer_spec.rb b/spec/lib/api/v3/root_representer_spec.rb
new file mode 100644
index 0000000000..0bdfbfb661
--- /dev/null
+++ b/spec/lib/api/v3/root_representer_spec.rb
@@ -0,0 +1,57 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::RootRepresenter do
+ let(:representer) { described_class.new({}) }
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ describe '_links' do
+ it { should have_json_type(Object).at_path('_links') }
+
+ describe 'priorities' do
+ it { should have_json_path('_links/priorities') }
+ it { should have_json_path('_links/priorities/href') }
+ end
+
+ describe 'project' do
+ it { should have_json_path('_links/project') }
+ it { should have_json_path('_links/project/href') }
+ it { should have_json_path('_links/project/templated') }
+ end
+
+ describe 'statuses' do
+ it { should have_json_path('_links/statuses') }
+ it { should have_json_path('_links/statuses/href') }
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/v3/statuses/shared/status_collection_representer.rb b/spec/lib/api/v3/statuses/shared/status_collection_representer.rb
new file mode 100644
index 0000000000..ba4b3d1370
--- /dev/null
+++ b/spec/lib/api/v3/statuses/shared/status_collection_representer.rb
@@ -0,0 +1,25 @@
+
+RSpec.shared_examples "status collection representer" do
+ let(:statuses) { FactoryGirl.build_list(:status, 3) }
+ let(:models) { statuses.map { |status|
+ ::API::V3::Statuses::StatusModel.new(status)
+ } }
+ let(:representer) { described_class.new(models) }
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it { should include_json('Statuses'.to_json).at_path('_type') }
+
+ it { should have_json_type(Object).at_path('_links') }
+ it 'should link to self' do
+ expect(subject).to have_json_path('_links/self/href')
+ end
+
+ describe 'statuses' do
+ it { should have_json_path('_embedded/statuses') }
+ it { should have_json_size(3).at_path('_embedded/statuses') }
+ it { should have_json_path('_embedded/statuses/2/name') }
+ end
+ end
+end
diff --git a/spec/lib/api/v3/statuses/status_collection_representer_spec.rb b/spec/lib/api/v3/statuses/status_collection_representer_spec.rb
new file mode 100644
index 0000000000..bbbbd959b2
--- /dev/null
+++ b/spec/lib/api/v3/statuses/status_collection_representer_spec.rb
@@ -0,0 +1,42 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 status 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 status 2
+# of the License, or (at your option) any later status.
+#
+# 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.
+#++
+
+require 'spec_helper'
+require 'lib/api/v3/statuses/shared/status_collection_representer'
+
+describe ::API::V3::Statuses::StatusCollectionRepresenter do
+ include_examples 'status collection representer'
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it 'should have link to self' do
+ expect(parse_json(subject)['_links']['self']['href']).to match(%r{api/v3/statuses$})
+ end
+ end
+end
diff --git a/spec/lib/api/v3/statuses/status_model_spec.rb b/spec/lib/api/v3/statuses/status_model_spec.rb
new file mode 100644
index 0000000000..86735a26a3
--- /dev/null
+++ b/spec/lib/api/v3/statuses/status_model_spec.rb
@@ -0,0 +1,37 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Statuses::StatusModel do
+ subject(:model) { ::API::V3::Statuses::StatusModel.new(version) }
+ let(:version) { FactoryGirl.build(:status, attributes) }
+ let(:attributes) { { name: 'Specific Status' } }
+
+ its(:name) { should eq 'Specific Status' }
+end
diff --git a/spec/lib/api/v3/statuses/status_representer_spec.rb b/spec/lib/api/v3/statuses/status_representer_spec.rb
new file mode 100644
index 0000000000..94f497044f
--- /dev/null
+++ b/spec/lib/api/v3/statuses/status_representer_spec.rb
@@ -0,0 +1,51 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Statuses::StatusRepresenter do
+ let(:status) { FactoryGirl.build(:status) }
+ let(:model) { ::API::V3::Statuses::StatusModel.new(status) }
+ let(:representer) { described_class.new(model) }
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it { should include_json('Status'.to_json).at_path('_type') }
+
+ xit { should have_json_type(Object).at_path('_links') }
+ xit 'should link to self' do
+ expect(subject).to have_json_path('_links/self/href')
+ end
+
+ describe 'status' do
+ it { should have_json_path('id') }
+ it { should have_json_path('name') }
+ end
+ end
+end
diff --git a/spec/lib/api/v3/versions/version_collection_representer_spec.rb b/spec/lib/api/v3/versions/version_collection_representer_spec.rb
new file mode 100644
index 0000000000..191cf6ff88
--- /dev/null
+++ b/spec/lib/api/v3/versions/version_collection_representer_spec.rb
@@ -0,0 +1,64 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Versions::VersionCollectionRepresenter do
+ let(:project) { FactoryGirl.build(:project, id: 666) }
+ let(:versions) { FactoryGirl.build_list(:version, 3) }
+ let(:models) { versions.map { |version|
+ ::API::V3::Versions::VersionModel.new(version)
+ } }
+ let(:representer) { described_class.new(models, project: project) }
+
+ describe '#initialize' do
+ context 'with incorrect parameters' do
+ it 'should raise without a project' do
+ expect { described_class.new(models) }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it { should include_json('Versions'.to_json).at_path('_type') }
+
+ it { should have_json_type(Object).at_path('_links') }
+ it 'should link to self' do
+ expect(generated).to have_json_path('_links/self/href')
+ expect(parse_json(generated, '_links/self/href')).to match %r{/api/v3/projects/666/versions$}
+ end
+
+ describe 'versions' do
+ it { should have_json_path('_embedded/versions') }
+ it { should have_json_size(3).at_path('_embedded/versions') }
+ it { should have_json_path('_embedded/versions/2/name') }
+ end
+ end
+end
diff --git a/spec/lib/api/v3/versions/version_model_spec.rb b/spec/lib/api/v3/versions/version_model_spec.rb
new file mode 100644
index 0000000000..4891cda83e
--- /dev/null
+++ b/spec/lib/api/v3/versions/version_model_spec.rb
@@ -0,0 +1,37 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Versions::VersionModel do
+ subject(:model) { ::API::V3::Versions::VersionModel.new(version) }
+ let(:version) { FactoryGirl.build(:version, attributes) }
+ let(:attributes) { { name: 'Specific Version' } }
+
+ its(:name) { should eq 'Specific Version' }
+end
diff --git a/spec/lib/api/v3/versions/version_representer_spec.rb b/spec/lib/api/v3/versions/version_representer_spec.rb
new file mode 100644
index 0000000000..2d23d30242
--- /dev/null
+++ b/spec/lib/api/v3/versions/version_representer_spec.rb
@@ -0,0 +1,51 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Versions::VersionRepresenter do
+ let(:version) { FactoryGirl.build(:version) }
+ let(:model) { ::API::V3::Versions::VersionModel.new(version) }
+ let(:representer) { described_class.new(model) }
+
+ context 'generation' do
+ subject(:generated) { representer.to_json }
+
+ it { should include_json('Version'.to_json).at_path('_type') }
+
+ xit { should have_json_type(Object).at_path('_links') }
+ xit 'should link to self' do
+ expect(subject).to have_json_path('_links/self/href')
+ end
+
+ describe 'version' do
+ it { should have_json_path('id') }
+ it { should have_json_path('name') }
+ end
+ end
+end
diff --git a/spec/lib/api/v3/work_packages/available_status_collection_representer_spec.rb b/spec/lib/api/v3/work_packages/available_status_collection_representer_spec.rb
new file mode 100644
index 0000000000..272e1cd282
--- /dev/null
+++ b/spec/lib/api/v3/work_packages/available_status_collection_representer_spec.rb
@@ -0,0 +1,46 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 status 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 status 2
+# of the License, or (at your option) any later status.
+#
+# 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.
+#++
+
+require 'spec_helper'
+require 'lib/api/v3/statuses/shared/status_collection_representer'
+
+describe ::API::V3::WorkPackages::AvailableStatusCollectionRepresenter do
+ include_examples 'status collection representer'
+
+ context 'generation' do
+ subject(:generated) { representer.to_json(work_package_id: 1) }
+
+ it 'should have link to self' do
+ expect(parse_json(subject, '_links/self/href')).to end_with('api/v3/work_packages/1/available_statuses')
+ end
+
+ it 'should have link to work_package' do
+ expect(parse_json(subject, '_links/work_package/href')).to end_with('api/v3/work_packages/1')
+ end
+ end
+end
diff --git a/spec/lib/api/v3/work_packages/work_package_model_spec.rb b/spec/lib/api/v3/work_packages/work_package_model_spec.rb
index 38d2f14c4e..6d8315272f 100644
--- a/spec/lib/api/v3/work_packages/work_package_model_spec.rb
+++ b/spec/lib/api/v3/work_packages/work_package_model_spec.rb
@@ -31,10 +31,7 @@ require 'spec_helper'
describe ::API::V3::WorkPackages::WorkPackageModel do
include Capybara::RSpecMatchers
- subject(:model) { ::API::V3::WorkPackages::WorkPackageModel.new(
- work_package: work_package
- )
- }
+ subject(:model) { ::API::V3::WorkPackages::WorkPackageModel.new(work_package) }
let(:work_package) { FactoryGirl.build(:work_package, attributes) }
context 'with a formatted description' do
@@ -112,4 +109,38 @@ h2. Plan for this month
end
end
end
+
+ describe :estimated_time do
+ let(:value) { 6.0 }
+ let(:attributes) do
+ { estimated_hours: value }
+ end
+
+ it 'should have the estimated_hours as the value' do
+ expect(model.estimated_time[:value]).to eql(value)
+ end
+
+ it 'should have units in de if the language is de' do
+ I18n.with_locale(:de) do
+ expect(model.estimated_time[:units]).to eql(I18n.t(:'datetime.units.hour',
+ :count => value.to_i))
+ end
+ end
+
+ it 'should have units in en if the language is en' do
+ I18n.with_locale(:en) do
+ expect(model.estimated_time[:units]).to eql(I18n.t(:'datetime.units.hour',
+ :count => value.to_i))
+ end
+ end
+
+ it 'should make sense if the hours are 0' do
+ work_package.estimated_hours = 0.0
+
+ I18n.with_locale(:en) do
+ expect(model.estimated_time[:units]).to eql(I18n.t(:'datetime.units.hour',
+ :count => 2)) # we want plural on 0
+ end
+ end
+ end
end
diff --git a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb
index 1ab1714a8d..1061cc4ed8 100644
--- a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb
+++ b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb
@@ -34,10 +34,7 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
let(:representer) { described_class.new(model, current_user: current_user) }
- let(:model) { ::API::V3::WorkPackages::WorkPackageModel.new(
- work_package: work_package
- )
- }
+ let(:model) { ::API::V3::WorkPackages::WorkPackageModel.new(work_package) }
let(:work_package) { FactoryGirl.build(:work_package,
created_at: DateTime.now,
updated_at: DateTime.now
diff --git a/spec/lib/journal_formatter/attachment_spec.rb b/spec/lib/journal_formatter/attachment_spec.rb
index c69691a1f6..215ee78923 100644
--- a/spec/lib/journal_formatter/attachment_spec.rb
+++ b/spec/lib/journal_formatter/attachment_spec.rb
@@ -75,7 +75,7 @@ describe OpenProject::JournalFormatter::Attachment do
describe "WITH the first value beeing an id as string, and the second nil" do
let(:expected) { I18n.t(:text_journal_deleted,
:label => "#{I18n.t(:'activerecord.models.attachment')}",
- :old => "#{attachment.id}") }
+ :old => "#{attachment.id}") }
it { expect(instance.render(key, [attachment.id.to_s, nil])).to eq(expected) }
end
diff --git a/spec/lib/journal_formatter/custom_field_spec.rb b/spec/lib/journal_formatter/custom_field_spec.rb
index 172e57b5b3..b3b9636f58 100644
--- a/spec/lib/journal_formatter/custom_field_spec.rb
+++ b/spec/lib/journal_formatter/custom_field_spec.rb
@@ -48,31 +48,35 @@ describe OpenProject::JournalFormatter::CustomField do
describe :render do
describe "WITH the first value beeing nil, and the second a valid value as string" do
let(:values) { [nil, "1"] }
+ let(:formatted_value) { format_value(values.last, custom_field.field_format) }
let(:expected) { I18n.t(:text_journal_set_to,
:label => "#{custom_field.name}",
- :value => "#{ format_value(values.last, custom_field.field_format) }") }
+ :value => "#{formatted_value}") }
it { expect(instance.render(key, values)).to eq(expected) }
end
describe "WITH the first value beeing a valid value as a string, and the second beeing a valid value as a string" do
let(:values) { ["0", "1"] }
+ let(:old_formatted_value) { format_value(values.first, custom_field.field_format) }
+ let(:new_formatted_value) { format_value(values.last, custom_field.field_format) }
let(:expected) { I18n.t(:text_journal_changed,
:label => "#{custom_field.name}",
- :old => "#{ format_value(values.first, custom_field.field_format) }",
- :new => "#{ format_value(values.last, custom_field.field_format) }") }
+ :old => "#{old_formatted_value}",
+ :new => "#{new_formatted_value}") }
it { expect(instance.render(key, values)).to eq(expected) }
end
describe "WITH the first value beeing a valid value as a string, and the second beeing nil" do
let(:values) { ["0", nil] }
+ let(:formatted_value) { format_value(values.first, custom_field.field_format) }
let(:expected) { I18n.t(:text_journal_deleted,
:label => "#{custom_field.name}",
- :old => "#{ format_value(values.first, custom_field.field_format) }") }
+ :old => "#{formatted_value}") }
it { expect(instance.render(key, values)).to eq(expected) }
end
@@ -118,7 +122,7 @@ describe OpenProject::JournalFormatter::CustomField do
let(:expected) { I18n.t(:text_journal_set_to,
:label => "#{I18n.t(:label_deleted_custom_field)}",
- :value => "#{ values.last }") }
+ :value => "#{ values.last }") }
it { expect(instance.render(key, values)).to eq(expected) }
end
@@ -130,8 +134,8 @@ describe OpenProject::JournalFormatter::CustomField do
let(:expected) { I18n.t(:text_journal_changed,
:label => "#{I18n.t(:label_deleted_custom_field)}",
- :old => "#{ values.first }",
- :new => "#{ values.last }") }
+ :old => "#{ values.first }",
+ :new => "#{ values.last }") }
it { expect(instance.render(key, values)).to eq(expected) }
end
@@ -143,7 +147,7 @@ describe OpenProject::JournalFormatter::CustomField do
let(:expected) { I18n.t(:text_journal_deleted,
:label => "#{I18n.t(:label_deleted_custom_field)}",
- :old => "#{ values.first }") }
+ :old => "#{ values.first }") }
it { expect(instance.render(key, values)).to eq(expected) }
end
diff --git a/spec/lib/redmine/i18n_spec.rb b/spec/lib/redmine/i18n_spec.rb
index 5d2b6b2d56..1e5d18f063 100644
--- a/spec/lib/redmine/i18n_spec.rb
+++ b/spec/lib/redmine/i18n_spec.rb
@@ -29,7 +29,7 @@
require 'spec_helper'
module OpenProject
- describe I18n do
+ describe I18n, type: :helper do
include Redmine::I18n
let(:format) { '%d/%m/%Y' }
@@ -69,5 +69,16 @@ module OpenProject
expect(format_time_as_date(time,format)).to eq '30/06/2013'
end
end
+
+ describe :all_languages do
+ it 'should at least return en' do
+ # using this to ensure that the files are evaluated
+ expect(all_languages).to include(:en)
+ end
+
+ it 'should return no js language as they are duplicates of the rest of the other language' do
+ expect(all_languages.any?{ |l| /\Ajs-/.match(l.to_s) }).to be_false
+ end
+ end
end
end
diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb
index 4d2a035009..b2da311df1 100644
--- a/spec/mailers/user_mailer_spec.rb
+++ b/spec/mailers/user_mailer_spec.rb
@@ -416,7 +416,7 @@ describe UserMailer, :type => :mailer do
end
context 'changed done ratio' do
- let(:expected) { "#{expected_prefix} changed from 40 to 100" }
+ let(:expected) { "#{expected_prefix} changed from 40 to 100" }
before do
allow(journal).to receive(:details).and_return({"done_ratio" => [40, 100]})
@@ -428,7 +428,7 @@ describe UserMailer, :type => :mailer do
end
context 'new done ratio' do
- let(:expected) { "#{expected_prefix} changed from 0 to 100" }
+ let(:expected) { "#{expected_prefix} changed from 0 to 100" }
before do
allow(journal).to receive(:details).and_return({"done_ratio" => [nil, 100]})
@@ -440,7 +440,7 @@ describe UserMailer, :type => :mailer do
end
context 'deleted done ratio' do
- let(:expected) { "#{expected_prefix} changed from 50 to 0" }
+ let(:expected) { "#{expected_prefix} changed from 50 to 0" }
before do
allow(journal).to receive(:details).and_return({"done_ratio" => [50, nil]})
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
index e3499b4ac5..dc8a57065d 100644
--- a/spec/models/setting_spec.rb
+++ b/spec/models/setting_spec.rb
@@ -50,7 +50,6 @@ describe Setting, :type => :model do
describe "changing a setting" do
context "setting doesn't exist in the database" do
before do
- Setting.destroy_all
Setting.host_name = "some name"
end
@@ -61,6 +60,10 @@ describe Setting, :type => :model do
it "stores the setting" do
expect(Setting.find_by_name('host_name').value).to eq "some name"
end
+
+ after do
+ Setting.find_by_name('host_name').destroy
+ end
end
context "setting already exist in the database" do
@@ -76,17 +79,28 @@ describe Setting, :type => :model do
it "stores the setting" do
expect(Setting.find_by_name('host_name').value).to eq "some other name"
end
+
+ after do
+ Setting.find_by_name('host_name').destroy
+ end
end
end
# tests the serialization feature to store complex data types like arrays in settings
describe "serialized settings" do
- it "serializes arrays" do
+ before do
# note: notified_events is marked as serialized in settings.yml (no type-based automagic here)
Setting.notified_events = ['some_event']
+ end
+
+ it "serializes arrays" do
expect(Setting.notified_events).to eq ['some_event']
expect(Setting.find_by_name('notified_events').value).to eq ['some_event']
end
+
+ after do
+ Setting.find_by_name('notified_events').destroy
+ end
end
# tests stuff regarding settings callbacks
@@ -149,6 +163,10 @@ describe Setting, :type => :model do
Setting.host_name = 'some other name'
expect(collector).to include 'some name'
end
+
+ after do
+ Setting.destroy_all
+ end
end
end
diff --git a/spec/requests/activities_api_spec.rb b/spec/requests/activities_api_spec.rb
index ecc1c3bccd..3d9478cacf 100644
--- a/spec/requests/activities_api_spec.rb
+++ b/spec/requests/activities_api_spec.rb
@@ -64,7 +64,7 @@ describe API::V3::Activities::ActivitiesAPI, :type => :request do
shared_context "edit activity" do
before { patch "/api/v3/activities/#{journal.id}",
- comment: comment }
+ { comment: comment }.to_json, { 'CONTENT_TYPE' => 'application/json' } }
end
it_behaves_like "safeguarded API" do
diff --git a/spec/requests/api/v3/priority_resource_spec.rb b/spec/requests/api/v3/priority_resource_spec.rb
new file mode 100644
index 0000000000..30657a4748
--- /dev/null
+++ b/spec/requests/api/v3/priority_resource_spec.rb
@@ -0,0 +1,67 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+require 'rack/test'
+
+describe 'API v3 Priority resource' do
+ include Rack::Test::Methods
+
+ let(:current_user) { FactoryGirl.create(:user) }
+ let(:role) { FactoryGirl.create(:role, permissions: []) }
+ let(:project) { FactoryGirl.create(:project, is_public: false) }
+ let(:priorities) { FactoryGirl.create_list(:priority, 2) }
+
+ describe '#get' do
+ subject(:response) { last_response }
+
+ context 'logged in user' do
+ let(:get_path) { "/api/v3/priorities" }
+ before do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role.id]
+ member.save!
+
+ priorities
+
+ get get_path
+ end
+
+ it 'should respond with 200' do
+ expect(subject.status).to eq(200)
+ end
+
+ it 'should respond with priorities, scoped to project' do
+ expect(subject.body).to include_json('Priorities'.to_json).at_path('_type')
+ expect(subject.body).to have_json_size(2).at_path('_embedded/priorities')
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/project_resource_spec.rb b/spec/requests/api/v3/project_resource_spec.rb
new file mode 100644
index 0000000000..b0a9cf6104
--- /dev/null
+++ b/spec/requests/api/v3/project_resource_spec.rb
@@ -0,0 +1,86 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+require 'rack/test'
+
+describe 'API v3 Project resource' do
+ include Rack::Test::Methods
+
+ let(:current_user) { FactoryGirl.create(:user) }
+ let(:project) { FactoryGirl.create(:project, is_public: false) }
+ let(:role) { FactoryGirl.create(:role) }
+
+ describe '#get' do
+ subject(:response) { last_response }
+
+ context 'logged in user' do
+ let(:get_path) { "/api/v3/projects/#{project.id}" }
+ before do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role.id]
+ member.save!
+ get get_path
+ end
+
+ it 'should respond with 200' do
+ expect(subject.status).to eq(200)
+ end
+
+ it 'should respond with correct project' do
+ expect(subject.body).to include_json('Project'.to_json).at_path('_type')
+ expect(subject.body).to be_json_eql(project.identifier.to_json).at_path('identifier')
+ end
+
+ context 'requesting nonexistent project' do
+ let(:get_path) { "/api/v3/projects/9999" }
+ it 'should respond with 404' do
+ expect(subject.status).to eq(404)
+ end
+
+ it 'should respond with explanatory error message' do
+ expect(subject.body).to include_json('not_found'.to_json).at_path('title')
+ end
+ end
+
+ context 'requesting project without sufficient permissions' do
+ let(:another_project) { FactoryGirl.create(:project, is_public: false) }
+ let(:get_path) { "/api/v3/projects/#{another_project.id}" }
+
+ it 'should respond with 403' do
+ expect(subject.status).to eq(403)
+ end
+
+ it 'should respond with explanatory error message' do
+ expect(subject.body).to include_json('not_authorized'.to_json).at_path('title')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/root_resource_spec.rb b/spec/requests/api/v3/root_resource_spec.rb
new file mode 100644
index 0000000000..799a893928
--- /dev/null
+++ b/spec/requests/api/v3/root_resource_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+require 'rack/test'
+
+describe 'API v3 Root resource' do
+ include Rack::Test::Methods
+
+ let(:current_user) { FactoryGirl.create(:user) }
+ let(:role) { FactoryGirl.create(:role, permissions: []) }
+ let(:project) { FactoryGirl.create(:project, is_public: false) }
+
+ describe '#get' do
+ subject(:response) { last_response }
+ let(:get_path) { "/api/v3" }
+
+ context 'anonymous user' do
+ before do
+ get get_path
+ end
+
+ it 'should respond with 200' do
+ expect(subject.status).to eq(200)
+ end
+
+ it 'should respond with links' do
+ expect(subject.body).to have_json_path('_links/priorities')
+ expect(subject.body).to have_json_path('_links/project')
+ expect(subject.body).to have_json_path('_links/statuses')
+ end
+ end
+
+ context 'logged in user' do
+ before do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role.id]
+ member.save!
+
+ get get_path
+ end
+
+ it 'should respond with 200' do
+ expect(subject.status).to eq(200)
+ end
+
+ it 'should respond with links' do
+ expect(subject.body).to have_json_path('_links/priorities')
+ expect(subject.body).to have_json_path('_links/project')
+ expect(subject.body).to have_json_path('_links/statuses')
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/status_resource_spec.rb b/spec/requests/api/v3/status_resource_spec.rb
new file mode 100644
index 0000000000..7a5252a8c8
--- /dev/null
+++ b/spec/requests/api/v3/status_resource_spec.rb
@@ -0,0 +1,67 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+require 'rack/test'
+
+describe 'API v3 Status resource' do
+ include Rack::Test::Methods
+
+ let(:current_user) { FactoryGirl.create(:user) }
+ let(:role) { FactoryGirl.create(:role, permissions: []) }
+ let(:project) { FactoryGirl.create(:project, is_public: false) }
+ let(:statuses) { FactoryGirl.create_list(:status, 4) }
+
+ describe '#get' do
+ subject(:response) { last_response }
+
+ context 'logged in user' do
+ let(:get_path) { "/api/v3/statuses" }
+ before do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role.id]
+ member.save!
+
+ statuses
+
+ get get_path
+ end
+
+ it 'should respond with 200' do
+ expect(subject.status).to eq(200)
+ end
+
+ it 'should respond with statuses, scoped to project' do
+ expect(subject.body).to include_json('Statuses'.to_json).at_path('_type')
+ expect(subject.body).to have_json_size(4).at_path('_embedded/statuses')
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/version_resource_spec.rb b/spec/requests/api/v3/version_resource_spec.rb
new file mode 100644
index 0000000000..54ffa0f96c
--- /dev/null
+++ b/spec/requests/api/v3/version_resource_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+require 'rack/test'
+
+describe 'API v3 Version resource' do
+ include Rack::Test::Methods
+
+ let(:current_user) { FactoryGirl.create(:user) }
+ let(:role) { FactoryGirl.create(:role, permissions: []) }
+ let(:project) { FactoryGirl.create(:project, is_public: false) }
+ let(:versions) { FactoryGirl.create_list(:version, 4, project: project) }
+ let(:other_versions) { FactoryGirl.create_list(:version, 2) }
+
+ describe '#get' do
+ subject(:response) { last_response }
+
+ context 'logged in user' do
+ let(:get_path) { "/api/v3/projects/#{project.id}/versions" }
+ before do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role.id]
+ member.save!
+
+ versions
+ other_versions
+
+ get get_path
+ end
+
+ it 'should respond with 200' do
+ expect(subject.status).to eq(200)
+ end
+
+ it 'should respond with versions, scoped to project' do
+ expect(subject.body).to include_json('Versions'.to_json).at_path('_type')
+ expect(subject.body).to have_json_size(4).at_path('_embedded/versions')
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/work_packages/available_statuses_endpoint_spec.rb b/spec/requests/api/v3/work_packages/available_statuses_endpoint_spec.rb
new file mode 100644
index 0000000000..3f4272b98c
--- /dev/null
+++ b/spec/requests/api/v3/work_packages/available_statuses_endpoint_spec.rb
@@ -0,0 +1,98 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+require 'spec_helper'
+require 'rack/test'
+
+describe API::V3::WorkPackages::StatusesAPI do
+ include Rack::Test::Methods
+
+ def app
+ API::V3::Root
+ end
+
+ let(:authorized_user) do
+ FactoryGirl.create(:user, member_in_project: project,
+ member_through_role: authorized_role)
+ end
+
+ let(:unauthorized_user) do
+ FactoryGirl.create(:user, member_in_project: project,
+ member_through_role: unauthorized_role)
+ end
+ let(:authorized_role) { FactoryGirl.create(:role, permissions: [:edit_work_packages]) }
+ let(:unauthorized_role) { FactoryGirl.create(:role, permissions: []) }
+ let(:project) { FactoryGirl.create(:valid_project, is_public: false) }
+ let(:work_package) do
+ FactoryGirl.create(:work_package, project: project)
+ end
+ let(:status) do
+ FactoryGirl.create(:status)
+ end
+
+ describe '#get work_packages/:id/available_statuses' do
+ let(:get_path) { "/api/v3/work_packages/#{work_package.id}/available_statuses" }
+ subject(:response) { last_response }
+
+ context 'permitted user' do
+ let(:new_type) { project.types.first }
+ let(:new_status) { Status.where("id NOT IN (#{work_package.status.id})").first }
+ before do
+ allow(User).to receive(:current).and_return authorized_user
+
+ FactoryGirl.create(:workflow, old_status: work_package.status,
+ new_status: new_status,
+ role: authorized_role,
+ type_id: new_type.id)
+
+ get get_path, type: new_type.name
+ end
+
+ it 'should respond with 200' do
+ expect(subject.status).to eql(200)
+ end
+
+ it 'should return a json collection of all statuses' do
+ expect(parse_json(response.body, '_embedded/statuses/0/name')).to eql(new_status.name)
+ end
+ end
+
+ context 'unauthorized user' do
+ before do
+ allow(User).to receive(:current).and_return unauthorized_user
+
+ get get_path
+ end
+
+ it 'should respond with 403' do
+ expect(subject.status).to eql(403)
+ end
+ end
+ end
+end
diff --git a/spec/requests/work_packages_api_spec.rb b/spec/requests/work_packages_api_spec.rb
index 323142f031..a9b151ba08 100644
--- a/spec/requests/work_packages_api_spec.rb
+++ b/spec/requests/work_packages_api_spec.rb
@@ -39,7 +39,7 @@ describe API::V3::WorkPackages::WorkPackagesAPI, :type => :request do
describe "POST /api/v3/work_packages/:id/activities" do
shared_context "create activity" do
before { post "/api/v3/work_packages/#{work_package.id}/activities",
- comment: comment }
+ { comment: comment }.to_json, { 'CONTENT_TYPE' => 'application/json' } }
end
it_behaves_like "safeguarded API" do
diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb
index 2a32251c4b..fae11cd28f 100644
--- a/test/functional/application_controller_test.rb
+++ b/test/functional/application_controller_test.rb
@@ -44,7 +44,10 @@ class ApplicationControllerTest < ActionController::TestCase
# check that all language files are valid
def test_localization
- lang_files_count = Dir[Rails.root.join('config/locales/*.yml')].size
+ lang_files_count = Dir.glob(Rails.root.join('config/locales/*.yml'))
+ .map { |f| File.basename(f) }
+ .reject { |b| b.starts_with? 'js' }
+ .size
Setting.available_languages = Setting.all_languages
assert_equal lang_files_count, valid_languages.size
valid_languages.each do |lang|