From c8081225f156400a57274bac1544028133f2a509 Mon Sep 17 00:00:00 2001 From: ulferts Date: Tue, 10 Mar 2020 22:05:08 +0100 Subject: [PATCH 01/13] fix project tree sorting In case the last element is nested, we need to sort all ancestor levels before returning the projects. Otherwise, only the root layer is sorted. --- app/models/project.rb | 144 +++++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 65 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index b80d3aab14d..b7d1313d0b4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -407,80 +407,94 @@ class Project < ActiveRecord::Base parents | descendants # Set union end - # Returns an auto-generated project identifier based on the last identifier used - def self.next_identifier - p = Project.newest.first - p.nil? ? nil : p.identifier.to_s.succ - end + class << self + # Returns an auto-generated project identifier based on the last identifier used + def next_identifier + p = Project.newest.first + p.nil? ? nil : p.identifier.to_s.succ + end - # builds up a project hierarchy helper structure for use with #project_tree_from_hierarchy - # - # it expects a simple list of projects with a #lft column (awesome_nested_set) - # and returns a hierarchy based on #lft - # - # the result is a nested list of root level projects that contain their child projects - # but, each entry is actually a ruby hash wrapping the project and child projects - # the keys are :project and :children where :children is in the same format again - # - # result = [ root_level_project_info_1, root_level_project_info_2, ... ] - # - # where each entry has the form - # - # project_info = { project: the_project, children: [ child_info_1, child_info_2, ... ] } - # - # if a project has no children the :children array is just empty - # - def self.build_projects_hierarchy(projects) - ancestors = [] - result = [] + # builds up a project hierarchy helper structure for use with #project_tree_from_hierarchy + # + # it expects a simple list of projects with a #lft column (awesome_nested_set) + # and returns a hierarchy based on #lft + # + # the result is a nested list of root level projects that contain their child projects + # but, each entry is actually a ruby hash wrapping the project and child projects + # the keys are :project and :children where :children is in the same format again + # + # result = [ root_level_project_info_1, root_level_project_info_2, ... ] + # + # where each entry has the form + # + # project_info = { project: the_project, children: [ child_info_1, child_info_2, ... ] } + # + # if a project has no children the :children array is just empty + # + def build_projects_hierarchy(projects) + ancestors = [] + result = [] - projects.sort_by(&:lft).each do |project| - while ancestors.any? && !project.is_descendant_of?(ancestors.last[:project]) - # before we pop back one level, we sort the child projects by name - ancestors.last[:children] = ancestors.last[:children].sort_by { |h| h[:project].name.downcase if h[:project].name } - ancestors.pop + projects.sort_by(&:lft).each do |project| + while ancestors.any? && !project.is_descendant_of?(ancestors.last[:project]) + # before we pop back one level, we sort the child projects by name + ancestors.last[:children] = sort_by_name(ancestors.last[:children]) + ancestors.pop + end + + current_hierarchy = { project: project, children: [] } + current_tree = ancestors.any? ? ancestors.last[:children] : result + + current_tree << current_hierarchy + ancestors << current_hierarchy end - current_hierarchy = { project: project, children: [] } - current_tree = ancestors.any? ? ancestors.last[:children] : result - - current_tree << current_hierarchy - ancestors << current_hierarchy + # When the last project is deeply nested, we need to sort + # all layers we are in. + ancestors.each do |level| + level[:children] = sort_by_name(level[:children]) + end + # we need one extra element to ensure sorting at the end + # at the end the root level must be sorted as well + sort_by_name(result) end - # at the end the root level must be sorted as well - result.sort_by { |h| h[:project].name&.downcase } - end - - def self.project_tree_from_hierarchy(projects_hierarchy, level, &block) - projects_hierarchy.each do |hierarchy| - project = hierarchy[:project] - children = hierarchy[:children] - yield project, level - # recursively show children - project_tree_from_hierarchy(children, level + 1, &block) if children.any? + def project_tree_from_hierarchy(projects_hierarchy, level, &block) + projects_hierarchy.each do |hierarchy| + project = hierarchy[:project] + children = hierarchy[:children] + yield project, level + # recursively show children + project_tree_from_hierarchy(children, level + 1, &block) if children.any? + end end - end - # Yields the given block for each project with its level in the tree - def self.project_tree(projects, &block) - projects_hierarchy = build_projects_hierarchy(projects) - project_tree_from_hierarchy(projects_hierarchy, 0, &block) - end - - def self.project_level_list(projects) - list = [] - project_tree(projects) do |project, level| - element = { - project: project, - level: level - } - - element.merge!(yield(project)) if block_given? - - list << element + # Yields the given block for each project with its level in the tree + def project_tree(projects, &block) + projects_hierarchy = build_projects_hierarchy(projects) + project_tree_from_hierarchy(projects_hierarchy, 0, &block) + end + + def project_level_list(projects) + list = [] + project_tree(projects) do |project, level| + element = { + project: project, + level: level + } + + element.merge!(yield(project)) if block_given? + + list << element + end + list + end + + private + + def sort_by_name(project_hashes) + project_hashes.sort_by { |h| h[:project].name&.downcase } end - list end def allowed_permissions From 9eb38b8346e32f0632212e2e26b8fd2964ff31e9 Mon Sep 17 00:00:00 2001 From: ulferts Date: Thu, 27 Feb 2020 13:21:15 +0100 Subject: [PATCH 02/13] fix assignable version order --- app/models/project.rb | 2 +- .../v3/work_packages/form/work_package_form_resource_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index b7d1313d0b4..20e58cc84ba 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -306,7 +306,7 @@ class Project < ActiveRecord::Base # reduce the number of db queries when performing operations including the # project's versions. def assignable_versions - @all_shared_versions ||= shared_versions.with_status_open.to_a + @all_shared_versions ||= shared_versions.with_status_open.order_by_newest_date.to_a end # Returns a hash of project users grouped by role diff --git a/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb b/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb index 2abb3f70fd7..d5585400003 100644 --- a/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb +++ b/spec/requests/api/v3/work_packages/form/work_package_form_resource_spec.rb @@ -517,8 +517,8 @@ describe 'API v3 Work package form resource', type: :request, with_mail: false d describe 'version' do let(:path) { '_embedded/payload/_links/version/href' } - let(:target_version) { FactoryBot.create(:version, project: project, name: 'zzzz') } - let(:other_version) { FactoryBot.create(:version, project: project, name: 'aaaa') } + let(:target_version) { FactoryBot.create(:version, project: project, start_date: Date.today - 2.days) } + let(:other_version) { FactoryBot.create(:version, project: project, start_date: Date.today - 1.day) } let(:version_link) { api_v3_paths.version target_version.id } let(:version_parameter) { { _links: { version: { href: version_link } } } } let(:params) { valid_params.merge(version_parameter) } From 459c8414c0015b979a0bb3b5a746ff0ee396d12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 11 Mar 2020 11:06:07 +0100 Subject: [PATCH 03/13] [32501] Make long test CF creation form show WYSIWYG editor --- app/assets/javascripts/custom-fields.js | 8 ++- app/views/custom_fields/_form.html.erb | 22 ++++++- .../custom_fields/create_long_text_spec.rb | 66 +++++++++++++++++++ spec/support/pages/custom_fields.rb | 6 +- 4 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 spec/features/custom_fields/create_long_text_spec.rb diff --git a/app/assets/javascripts/custom-fields.js b/app/assets/javascripts/custom-fields.js index 18a291ece94..a8dbcae2536 100644 --- a/app/assets/javascripts/custom-fields.js +++ b/app/assets/javascripts/custom-fields.js @@ -44,6 +44,7 @@ possibleValues = $('#custom_field_possible_values_attributes'), defaultValueFields = $('#custom_field_default_value_attributes'), spanDefaultText = $('#default_value_text'), + spanDefaultLongText = $('#default_value_long_text'), spanDefaultBool = $('#default_value_bool'), textOrientationField = $('#custom_field_text_orientation'); @@ -66,10 +67,10 @@ unsearchable = function() { searchable.attr('checked', false).hide(); }; // defaults (reset these fields before doing anything else) - $.each([spanDefaultBool, spanDefaultText, multiSelect, textOrientationField], function(idx, element) { + $.each([spanDefaultBool, spanDefaultLongText, spanDefaultText, multiSelect, textOrientationField], function(idx, element) { deactivate(element); }); - show(defaultValueFields); + activate(defaultValueFields); activate(spanDefaultText); switch (format.val()) { @@ -109,6 +110,8 @@ unsearchable(); break; case 'text': + activate(spanDefaultLongText); + deactivate(spanDefaultText); show(lengthField, regexpField, searchable, textOrientationField); deactivate(possibleValues); activate(textOrientationField); @@ -122,6 +125,7 @@ // assign the switch format function to the select field format.on('change', toggleFormat).trigger('change'); + toggleFormat(); }); $(function() { diff --git a/app/views/custom_fields/_form.html.erb b/app/views/custom_fields/_form.html.erb index 534c5576c5f..31cedbe80cb 100644 --- a/app/views/custom_fields/_form.html.erb +++ b/app/views/custom_fields/_form.html.erb @@ -87,15 +87,31 @@ See docs/COPYRIGHT.rdoc for more details. <% end %>
- <% if @custom_field.new_record? || @custom_field.field_format != 'bool' %> - <%= f.text_field(:default_value, container_class: '-wide') %> + <% if @custom_field.new_record? || !%w[text bool].include?(@custom_field.field_format) %> + <%= f.text_field :default_value, + id: 'custom_fields_default_value_text', + for: 'custom_fields_default_value_text', + container_class: '-wide' %> <% end %>
+
<%= call_hook(:view_custom_fields_form_upper_box, custom_field: @custom_field, form: f) %> diff --git a/spec/features/custom_fields/create_long_text_spec.rb b/spec/features/custom_fields/create_long_text_spec.rb new file mode 100644 index 00000000000..eb795daafcd --- /dev/null +++ b/spec/features/custom_fields/create_long_text_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' +require 'support/pages/custom_fields' + +describe 'custom fields', js: true do + let(:user) { FactoryBot.create :admin } + let(:cf_page) { Pages::CustomFields.new } + let(:editor) { ::Components::WysiwygEditor.new '#default_value_long_text' } + let(:type) { FactoryBot.create :type_task } + let(:project) { FactoryBot.create :project, enabled_module_names: %i[work_package_tracking], types: [type] } + + let(:wp_page) { Pages::FullWorkPackageCreate.new project: project } + + let(:default_text) do + <<~MARKDOWN + # This is an exemplary test + + **Foo bar** + + MARKDOWN + end + + before do + login_as(user) + end + + describe "creating a new long text custom field" do + before do + cf_page.visit! + click_on "Create a new custom field" + end + + it "creates a new bool custom field" do + cf_page.set_name "New Field" + cf_page.select_format "Long text" + + sleep 1 + + editor.set_markdown default_text + + cf_page.set_all_projects true + click_on "Save" + + expect(page).to have_text("Successful creation") + expect(page).to have_text("New Field") + + cf = CustomField.last + expect(cf.field_format).to eq 'text' + + # textareas get carriage returns entered + expect(cf.default_value.gsub("\r\n", "\n").strip).to eq default_text.strip + + type.custom_fields << cf + type.save! + + + wp_page.visit! + wp_editor = TextEditorField.new(page, 'description', selector: ".inline-edit--container.customField#{cf.id}") + wp_editor.expect_active! + + wp_editor.ckeditor.in_editor do |container, _| + expect(container).to have_selector('h1', text: 'This is an exemplary test') + expect(container).to have_selector('strong', text: 'Foo bar') + end + end + end +end diff --git a/spec/support/pages/custom_fields.rb b/spec/support/pages/custom_fields.rb index e6840a17b56..a7efbf48101 100644 --- a/spec/support/pages/custom_fields.rb +++ b/spec/support/pages/custom_fields.rb @@ -43,7 +43,11 @@ module Pages end def set_default_value(value) - find("#custom_field_default_value").set value + fill_in 'custom_field[default_value]', with: value + end + + def set_all_projects(value) + find('#custom_field_is_for_all').set value end def has_form_element?(name) From 1b16dbcc072418792c389e10d8783dc1c3a8c020 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 11 Mar 2020 13:24:32 +0100 Subject: [PATCH 04/13] avoid double submit of form configuration The form configuration used to be submitted at least twice. In most cases, the first submit was canceled fast enough to not reach the backend. But if it was not canceled fast enough, the duplicate post lead to some errors when e.g. the referenced queries where already deleted. --- app/models/type/attribute_groups.rb | 1 - app/services/base_type_service.rb | 1 - app/views/types/form/_form_configuration.html.erb | 1 + .../types/type-form-configuration.component.ts | 15 ++++++++++++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/models/type/attribute_groups.rb b/app/models/type/attribute_groups.rb index 5873e531477..93bb4968af7 100644 --- a/app/models/type/attribute_groups.rb +++ b/app/models/type/attribute_groups.rb @@ -136,7 +136,6 @@ module Type::AttributeGroups self.attribute_groups_objects = nil end - private def write_attribute_groups_objects diff --git a/app/services/base_type_service.rb b/app/services/base_type_service.rb index 448f4db1cf2..00353931646 100644 --- a/app/services/base_type_service.rb +++ b/app/services/base_type_service.rb @@ -104,7 +104,6 @@ class BaseTypeService def transform_attribute_groups(groups) groups.map do |group| - if group['type'] == 'query' transform_query_group(group) else diff --git a/app/views/types/form/_form_configuration.html.erb b/app/views/types/form/_form_configuration.html.erb index 1abfcf013b2..d851152ccc3 100644 --- a/app/views/types/form/_form_configuration.html.erb +++ b/app/views/types/form/_form_configuration.html.erb @@ -89,6 +89,7 @@ See docs/COPYRIGHT.rdoc for more details.
<%= styled_button_tag t(@type.new_record? ? :button_create : :button_save), + data: { disable_with: t(@type.new_record? ? :button_create : :button_save) }, class: 'form-configuration--save -highlight -with-icon icon-checkmark' %>
diff --git a/frontend/src/app/modules/admin/types/type-form-configuration.component.ts b/frontend/src/app/modules/admin/types/type-form-configuration.component.ts index 5df24de52da..763c1fe712f 100644 --- a/frontend/src/app/modules/admin/types/type-form-configuration.component.ts +++ b/frontend/src/app/modules/admin/types/type-form-configuration.component.ts @@ -79,9 +79,22 @@ export class TypeFormConfigurationComponent implements OnInit, OnDestroy { this.form = jQuery(this.element).closest('form'); this.submit = this.form.find('.form-configuration--save'); + // In the following we are triggering the form submit ourselves to work around + // a firefox shortcoming. But to avoid double submits which are sometimes not canceled fast + // enough, we need to memoize whether we have already submitted. + let submitted = false; + + this.form.on('submit', (event) => { + submitted = true; + }); + // Capture mousedown on button because firefox breaks blur on click this.submit.on('mousedown', (event) => { - setTimeout(() => this.form.trigger('submit'), 50); + setTimeout(() => { + if (!submitted) { + this.form.trigger('submit'); + } + }, 50); return true; }); From 533ef71d7025996a692a7aeacc95e3279b4aa74b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 11 Mar 2020 14:12:57 +0100 Subject: [PATCH 05/13] Fix local name for forums [ci skip] --- .../copy_projects/copy_settings/_copy_associations.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/copy_projects/copy_settings/_copy_associations.html.erb b/app/views/copy_projects/copy_settings/_copy_associations.html.erb index f127c91aa5e..e2cb2a55416 100644 --- a/app/views/copy_projects/copy_settings/_copy_associations.html.erb +++ b/app/views/copy_projects/copy_settings/_copy_associations.html.erb @@ -34,7 +34,7 @@ See docs/COPYRIGHT.rdoc for more details. locals: { name: "queries", checked: true, label: l(:label_query_plural), count: project.queries.count } %> <%= render partial: "copy_projects/copy_settings/block_checkbox", - locals: { name: "boards", checked: false, label: l(:label_forum_plural), + locals: { name: "forums", checked: false, label: l(:label_forum_plural), count: project.forums.count } %> <%= render partial: "copy_projects/copy_settings/block_checkbox", locals: { name: "members", checked: true, label: l(:label_member_plural), From e7965de77c03637101331fca52888441c6e1e1d8 Mon Sep 17 00:00:00 2001 From: Niels Lindenthal Date: Thu, 12 Mar 2020 07:38:36 +0100 Subject: [PATCH 06/13] Correct Release Version of 9.0.4 It seems as if there is the wrong version listed. Can you please check? --- docs/release-notes/9-0-4/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/release-notes/9-0-4/README.md b/docs/release-notes/9-0-4/README.md index 05a5d161cfe..0d10d4696b0 100644 --- a/docs/release-notes/9-0-4/README.md +++ b/docs/release-notes/9-0-4/README.md @@ -1,8 +1,8 @@ --- -title: OpenProject 9.0.3 +title: OpenProject 9.0.4 sidebar_navigation: - title: 9.0.3 -release_version: 9.0.3 + title: 9.0.4 +release_version: 9.0.4 release_date: 2019-07-23 --- @@ -20,4 +20,4 @@ Thanks to David Haintz from the SEC Consult Vulnerability Lab (https://www.sec-c #### Contributions -Thanks to David Haintz from [SEC Consult Vulnerability Lab](https://www.sec-consult.com/) for identifying and responsibly disclosing the identified issues. \ No newline at end of file +Thanks to David Haintz from [SEC Consult Vulnerability Lab](https://www.sec-consult.com/) for identifying and responsibly disclosing the identified issues. From 31e305aa967ee221bbc43d6d1593451163f9b8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Thu, 12 Mar 2020 08:00:43 +0100 Subject: [PATCH 07/13] Show warning for edge versions < 18 --- app/helpers/browser_helper.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/helpers/browser_helper.rb b/app/helpers/browser_helper.rb index 2b50687503e..e7fef0c5571 100644 --- a/app/helpers/browser_helper.rb +++ b/app/helpers/browser_helper.rb @@ -20,6 +20,9 @@ module BrowserHelper # Older version of safari return true if browser.safari? && version < 12 + # Older version of EDGE + return true if browser.edge? && version < 18 + false end end From 633a82dff871ee0bce83e8b59b614e81f30a197d Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 11 Mar 2020 09:31:26 +0100 Subject: [PATCH 08/13] display user name as link for non admins The showUser link of the user representer is now used to signal when the link can be followed. That link can only be followed when the user is not logged. The frontend now only checks the availability of the link to determine whether the user name in the work package activity is to be displayed as a link or not --- .../user/user-activity.component.html | 4 ++-- .../user/user-activity.component.ts | 6 +++--- lib/api/v3/users/user_representer.rb | 2 ++ .../lib/api/v3/users/user_representer_spec.rb | 19 +++++++++++++++---- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/wp-activity/user/user-activity.component.html b/frontend/src/app/components/wp-activity/user/user-activity.component.html index b4d78915e13..d176803966c 100644 --- a/frontend/src/app/components/wp-activity/user/user-activity.component.html +++ b/frontend/src/app/components/wp-activity/user/user-activity.component.html @@ -8,13 +8,13 @@ data-class-list="avatar"> - + - {{ userName }} + {{ userName }} {{ isInitial ? text.label_created_on : text.label_updated_on }} diff --git a/frontend/src/app/components/wp-activity/user/user-activity.component.ts b/frontend/src/app/components/wp-activity/user/user-activity.component.ts index bf90260eda3..1d4fc7bd431 100644 --- a/frontend/src/app/components/wp-activity/user/user-activity.component.ts +++ b/frontend/src/app/components/wp-activity/user/user-activity.component.ts @@ -66,7 +66,6 @@ export class UserActivityComponent extends WorkPackageCommentFieldHandler implem public userId:string | number; public userName:string; - public userActive:boolean; public userPath:string | null; public userLabel:string; public userAvatar:string; @@ -128,9 +127,10 @@ export class UserActivityComponent extends WorkPackageCommentFieldHandler implem .then((user:UserResource) => { this.userId = user.id!; this.userName = user.name; - this.userActive = user.isActive; this.userAvatar = user.avatar; - this.userPath = user.showUser.href; + if (user.showUser) { + this.userPath = user.showUser.href; + } this.userLabel = this.I18n.t('js.label_author', {user: this.userName}); this.cdRef.detectChanges(); }); diff --git a/lib/api/v3/users/user_representer.rb b/lib/api/v3/users/user_representer.rb index cd31e40d86d..0bcaf665b7c 100644 --- a/lib/api/v3/users/user_representer.rb +++ b/lib/api/v3/users/user_representer.rb @@ -48,6 +48,8 @@ module API self_link link :showUser do + next if represented.locked? + { href: api_v3_paths.show_user(represented.id), type: 'text/html' diff --git a/spec/lib/api/v3/users/user_representer_spec.rb b/spec/lib/api/v3/users/user_representer_spec.rb index 5b1e0787a88..9b617a1672f 100644 --- a/spec/lib/api/v3/users/user_representer_spec.rb +++ b/spec/lib/api/v3/users/user_representer_spec.rb @@ -29,7 +29,8 @@ require 'spec_helper' describe ::API::V3::Users::UserRepresenter do - let(:user) { FactoryBot.build_stubbed(:user, status: 1) } + let(:status) { Principal::STATUSES[:active] } + let(:user) { FactoryBot.build_stubbed(:user, status: status) } let(:current_user) { FactoryBot.build_stubbed(:user) } let(:representer) { described_class.new(user, current_user: current_user) } @@ -150,9 +151,19 @@ describe ::API::V3::Users::UserRepresenter do expect(subject).to have_json_path('_links/self/href') end - it_behaves_like 'has an untitled link' do - let(:link) { 'showUser' } - let(:href) { "/users/#{user.id}" } + context 'showUser' do + it_behaves_like 'has an untitled link' do + let(:link) { 'showUser' } + let(:href) { "/users/#{user.id}" } + end + + context 'with a locked user' do + let(:status) { Principal::STATUSES[:locked] } + + it_behaves_like 'has no link' do + let(:link) { 'showUser' } + end + end end context 'when regular current_user' do From a4b705ac0bdcf318a2b828ba7eb5f1a261169fb1 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 11 Mar 2020 10:01:10 +0100 Subject: [PATCH 09/13] user-link component handles having no show link correctly --- .../user/user-link/user-link.component.ts | 31 ++++++++++++------- .../modules/hal/resources/user-resource.ts | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/user/user-link/user-link.component.ts b/frontend/src/app/components/user/user-link/user-link.component.ts index 3d3695dd4ef..68612f38a6f 100644 --- a/frontend/src/app/components/user/user-link/user-link.component.ts +++ b/frontend/src/app/components/user/user-link/user-link.component.ts @@ -26,7 +26,7 @@ // See docs/COPYRIGHT.rdoc for more details. //++ -import {Component, Inject, Input} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; import {UserResource} from 'core-app/modules/hal/resources/user-resource'; import {I18nService} from 'core-app/modules/common/i18n/i18n.service'; import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper.service'; @@ -34,26 +34,33 @@ import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper @Component({ selector: 'user-link', template: ` - + [textContent]="name"> - ` + + {{ name }} + + `, + changeDetection: ChangeDetectionStrategy.OnPush }) export class UserLinkComponent { @Input() user:UserResource; - public href:string; - public label:string; - public name:string; - constructor(readonly pathHelper:PathHelperService, readonly I18n:I18nService) { } - ngOnInit() { - this.href = this.pathHelper.userPath(this.user.idFromLink); - this.name = this.user.name; - this.label = this.I18n.t('js.label_author', { user: this.name }); + public get href() { + return this.user && this.user.showUserPath; + } + + public get name() { + return this.user && this.user.name; + } + + public get label() { + return this.I18n.t('js.label_author', { user: this.name }); } } diff --git a/frontend/src/app/modules/hal/resources/user-resource.ts b/frontend/src/app/modules/hal/resources/user-resource.ts index 669284d7a09..ea7eb2d6763 100644 --- a/frontend/src/app/modules/hal/resources/user-resource.ts +++ b/frontend/src/app/modules/hal/resources/user-resource.ts @@ -55,7 +55,7 @@ export class UserResource extends HalResource { } public get showUserPath() { - return this.showUser.$link.href; + return this.showUser ? this.showUser.$link.href : null; } public get isActive() { From 86ddfe82ca440f1cbe39d9d3072f6d50d0d2e873 Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 11 Mar 2020 10:01:58 +0100 Subject: [PATCH 10/13] activity uses user-link component --- .../wp-activity/user/user-activity.component.html | 8 ++------ .../wp-activity/user/user-activity.component.ts | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/components/wp-activity/user/user-activity.component.html b/frontend/src/app/components/wp-activity/user/user-activity.component.html index d176803966c..37585e755af 100644 --- a/frontend/src/app/components/wp-activity/user/user-activity.component.html +++ b/frontend/src/app/components/wp-activity/user/user-activity.component.html @@ -8,13 +8,9 @@ data-class-list="avatar"> - - - + + - {{ userName }} {{ isInitial ? text.label_created_on : text.label_updated_on }} diff --git a/frontend/src/app/components/wp-activity/user/user-activity.component.ts b/frontend/src/app/components/wp-activity/user/user-activity.component.ts index 1d4fc7bd431..3171808c275 100644 --- a/frontend/src/app/components/wp-activity/user/user-activity.component.ts +++ b/frontend/src/app/components/wp-activity/user/user-activity.component.ts @@ -65,9 +65,8 @@ export class UserActivityComponent extends WorkPackageCommentFieldHandler implem public userCanQuote = false; public userId:string | number; + public user:UserResource; public userName:string; - public userPath:string | null; - public userLabel:string; public userAvatar:string; public details:any[] = []; public isComment:boolean; @@ -125,13 +124,10 @@ export class UserActivityComponent extends WorkPackageCommentFieldHandler implem this.userCacheService .require(this.activity.user.idFromLink) .then((user:UserResource) => { + this.user = user; this.userId = user.id!; this.userName = user.name; this.userAvatar = user.avatar; - if (user.showUser) { - this.userPath = user.showUser.href; - } - this.userLabel = this.I18n.t('js.label_author', {user: this.userName}); this.cdRef.detectChanges(); }); } From 16991b9ed0068fe12183c66d13c9801c59d9ef2d Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 11 Mar 2020 10:02:46 +0100 Subject: [PATCH 11/13] now unnecessary variables removed The necessary information has been migrated to the single-view component some time ago --- .../wp-full-view/wp-full-view.component.ts | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts b/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts index fcb475f509c..36831ba5ab3 100644 --- a/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts +++ b/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts @@ -26,11 +26,9 @@ // See docs/COPYRIGHT.rdoc for more details. // ++ -import {UserResource} from 'core-app/modules/hal/resources/user-resource'; import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource'; import {WorkPackageViewFocusService} from 'core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-focus.service'; import {StateService} from '@uirouter/core'; -import {TypeResource} from 'core-app/modules/hal/resources/type-resource'; import {Component, Injector, OnInit} from '@angular/core'; import {WorkPackageViewSelectionService} from 'core-app/modules/work_packages/routing/wp-view-base/view-services/wp-view-selection.service'; import {States} from 'core-components/states.service'; @@ -52,13 +50,6 @@ export class WorkPackagesFullViewComponent extends WorkPackageSingleViewBase imp public displayWatchButton:boolean; public watchers:any; - // Properties - public type:TypeResource; - public author:UserResource; - public authorPath:string; - public authorActive:boolean; - public attachments:any; - // More menu public permittedActions:any; public actionsAvailable:any; @@ -109,16 +100,5 @@ export class WorkPackagesFullViewComponent extends WorkPackageSingleViewBase imp if (wp.watchers) { this.watchers = (wp.watchers as any).elements; } - - // Type - this.type = wp.type; - - // Author - this.author = wp.author; - this.authorPath = this.author.showUserPath as string; - this.authorActive = this.author.isActive; - - // Attachments - this.attachments = wp.attachments.elements; } } From 59bcd48dd80b141091074a22caeddebeaee61ebc Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 11 Mar 2020 13:52:27 +0100 Subject: [PATCH 12/13] adapt npm test --- .../user-link/user-link.component.spec.ts | 59 +++++++++++++------ .../user/user-link/user-link.component.ts | 3 +- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/components/user/user-link/user-link.component.spec.ts b/frontend/src/app/components/user/user-link/user-link.component.spec.ts index 2b239dda211..69018051022 100644 --- a/frontend/src/app/components/user/user-link/user-link.component.spec.ts +++ b/frontend/src/app/components/user/user-link/user-link.component.spec.ts @@ -42,6 +42,11 @@ describe('UserLinkComponent component test', () => { t: (key:string, args:any) => `Author: ${args.user}` }; + let app:UserLinkComponent; + let fixture:ComponentFixture; + let element:HTMLElement; + let user:UserResource; + beforeEach(async(() => { // noinspection JSIgnoredPromiseFromCall @@ -54,32 +59,50 @@ describe('UserLinkComponent component test', () => { { provide: PathHelperService, useValue: PathHelperStub }, ] }).compileComponents(); + + fixture = TestBed.createComponent(UserLinkComponent); + app = fixture.debugElement.componentInstance; + element = fixture.elementRef.nativeElement; })); describe('inner element', function() { - let app:UserLinkComponent; - let fixture:ComponentFixture - let element:HTMLElement; + describe('with the uer having the showUserPath attribute', function() { + beforeEach(async(() => { + user = { + name: 'First Last', + showUserPath: '/users/1' + } as UserResource; - let user = { - name: 'First Last', - href: '/api/v3/users/1', - idFromLink: '1', - } as UserResource; + app.user = user; + fixture.detectChanges(); + })); - it('should render an inner link with specified classes', function() { - fixture = TestBed.createComponent(UserLinkComponent); - app = fixture.debugElement.componentInstance; - element = fixture.elementRef.nativeElement; + it('should render an inner link with specified classes', function () { + const link = element.querySelector('a')!; - app.user = user; - fixture.detectChanges(); + expect(link.textContent).toEqual('First Last'); + expect(link.getAttribute('title')).toEqual('Author: First Last'); + expect(link.getAttribute('href')).toEqual('/users/1'); + }); + }); - const link = element.querySelector('a')!; + describe('with the user not having the showUserPath attribute', function() { + beforeEach(async(() => { + user = { + name: 'First Last', + showUserPath: null + } as UserResource; - expect(link.textContent).toEqual('First Last'); - expect(link.getAttribute('title')).toEqual('Author: First Last'); - expect(link.getAttribute('href')).toEqual('/users/1'); + app.user = user; + fixture.detectChanges(); + })); + + it('renders only the name', function () { + const link = element.querySelector('a'); + + expect(link).toBeNull(); + expect(element.textContent).toEqual(' First Last '); + }); }); }); }); diff --git a/frontend/src/app/components/user/user-link/user-link.component.ts b/frontend/src/app/components/user/user-link/user-link.component.ts index 68612f38a6f..7943e90eb06 100644 --- a/frontend/src/app/components/user/user-link/user-link.component.ts +++ b/frontend/src/app/components/user/user-link/user-link.component.ts @@ -48,8 +48,7 @@ import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper export class UserLinkComponent { @Input() user:UserResource; - constructor(readonly pathHelper:PathHelperService, - readonly I18n:I18nService) { + constructor(readonly I18n:I18nService) { } public get href() { From bde331ba5810f39c1db630d563528e38919e356c Mon Sep 17 00:00:00 2001 From: Travis CI User Date: Thu, 12 Mar 2020 10:07:48 +0000 Subject: [PATCH 13/13] update locales from crowdin [ci skip] --- config/locales/crowdin/el.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index 47143620bce..6630f09fd76 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -61,7 +61,7 @@ el: header-item-font-hover-color: "Χρώμα γραμματοσειράς στοιχείων κεφαλίδας με δυνατότητα κλικ, όταν ο δείκτης βρίσκεται πάνω." header-border-bottom-color: "Λεπτή γραμμή κάτω από την κεφαλίδα. Αφήστε αυτό το πεδίο κενό αν δεν θέλετε καμία γραμμή." main-menu-bg-color: "Χρώμα φόντου του μενού της αριστερής πλευράς." - theme_warning: Changing the theme will overwrite you custom style. The design will then be lost. Are you sure you want to continue? + theme_warning: Η αλλαγή του θέματος θα αντικαταστήσει το προσαρμοσμένο στυλ. Ο σχεδιασμός θα χαθεί. Είστε βέβαιοι ότι θέλετε να συνεχίσετε; enterprise: upgrade_to_ee: "Αναβαθμίστε σε Έκδοση για Επιχειρήσεις" add_token: "Ανεβάστε ενα token υποστήριξης έκδοσης για επιχειρήσεις" @@ -1154,7 +1154,7 @@ el: forums: "Φόρουμ κοινότητας" newsletter: "Ειδοποιήσεις ασφάλεια / Ενημερωτικό δελτίο" journals: - changes_retracted: "The changes where retracted." + changes_retracted: "Οι αλλαγές αποσύρθηκαν." links: configuration_guide: 'Οδηγός διαμόρφωσης' instructions_after_registration: "Μπορείτε να συνδεθείτε μόλις ο λογαριασμός σας ενεργοποιηθεί κάνοντας κλικ στο %{signin}."