diff --git a/app/views/account/_login.html.erb b/app/views/account/_login.html.erb
index 112f59476a0..f14f204318f 100644
--- a/app/views/account/_login.html.erb
+++ b/app/views/account/_login.html.erb
@@ -77,7 +77,7 @@ See COPYRIGHT and LICENSE files for more details.
+ value="<%=t(:button_login)%>" class="button -primary button_no-margin" tabindex="1" />
diff --git a/app/views/account/_password_login_form.html.erb b/app/views/account/_password_login_form.html.erb
index 1fcf9d217bd..ed3d7816f88 100644
--- a/app/views/account/_password_login_form.html.erb
+++ b/app/views/account/_password_login_form.html.erb
@@ -57,21 +57,24 @@ See COPYRIGHT and LICENSE files for more details.
<%= submit_tag t(:button_login),
name: :login,
- class: 'button -primary',
+ class: 'button -primary button_no-margin',
data: { disable_with: t(:label_loading) } %>
-
-
- <% if Setting.lost_password? %>
- <%= link_to t(:label_password_lost), { controller: '/account', action: 'lost_password' } %>
-
- <% end %>
- <% if Setting::SelfRegistration.enabled? %>
- <%= link_to t(:label_register),
- '',
- title: t(:label_register),
- class: 'registration-modal--activation-link' %>
- <% end %>
+
+
+ <% if Setting.lost_password? %>
+ <%= link_to t(:label_password_lost),
+ { controller: '/account', action: 'lost_password' },
+ class: 'login-form--footer-link' %>
+
+ <% end %>
+ <% if Setting::SelfRegistration.enabled? %>
+ <%= link_to t(:label_register),
+ '',
+ title: t(:label_register),
+ class: 'login-form--footer-link registration-modal--activation-link' %>
+ <% end %>
+
<% end %>
diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml
index 4b0de20a7ed..156563d6e09 100644
--- a/config/locales/js-en.yml
+++ b/config/locales/js-en.yml
@@ -384,17 +384,20 @@ en:
learn_about: "Learn more about the new features"
# Include the version to invalidate outdated translations in other locales.
# Otherwise, e.g. chinese might still have the translations for 10.0 in the 12.0 release.
- "13_3":
+ "13_4":
standard:
- learn_about_link: https://www.openproject.org/blog/openproject-13-3-release/
+ learn_about_link: https://www.openproject.org/blog/openproject-13-4-release/
new_features_html: >
The release contains various new features and improvements:
- Filter and save custom project lists.
- Separate Gantt charts module with new default views.
- Automatically managed project folders for OneDrive/SharePoint integration.
- Show number of "Shared users" in the share button and display them in the work packages list.
- Improved calculations and updates for progress reporting for work package hierarchies.
+ GitLab integration (originally developed by Community contributors)
+ Advanced features for custom project lists
+ Advanced features for the Meetings module
+ Admins are nudged to go through OAuth flow when activating a storage
+ Virus scanning functionality with ClamAV (Enterprise add-on)
+ PDF Export: Lists in table cells are supported
+ WebAuthn/FIDO/U2F is added as a second factor
+ More languages added to the default available set
ical_sharing_modal:
diff --git a/docker/pullpreview/docker-compose.yml b/docker/pullpreview/docker-compose.yml
index f4879929d17..7c9664a6063 100644
--- a/docker/pullpreview/docker-compose.yml
+++ b/docker/pullpreview/docker-compose.yml
@@ -45,7 +45,7 @@ services:
- "caddy_data:/data"
db:
- image: postgres:10
+ image: postgres:13
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: p4ssw0rd
diff --git a/frontend/src/app/core/global_search/input/global-search-input.component.html b/frontend/src/app/core/global_search/input/global-search-input.component.html
index 13f26dd12eb..61491b30798 100644
--- a/frontend/src/app/core/global_search/input/global-search-input.component.html
+++ b/frontend/src/app/core/global_search/input/global-search-input.component.html
@@ -26,11 +26,12 @@
[searchFn]="customSearchFn"
[virtualScroll]="false"
(focus)="onFocus()"
+ (blur)="onFocusOut()"
(search)="search($event)"
(close)="onClose()"
(change)="followItem($event)"
(keydown.enter)="onEnterBeforeResultsLoaded()"
- (keydown.escape)="blur()"
+ (keydown.escape)="onFocusOut()"
(clear)="clearSearch()"
[filters]="autocompleterOptions.filters"
[resource]="autocompleterOptions.resource"
diff --git a/frontend/src/app/core/global_search/input/global-search-input.component.ts b/frontend/src/app/core/global_search/input/global-search-input.component.ts
index cebe005a1e2..aa2e8291087 100644
--- a/frontend/src/app/core/global_search/input/global-search-input.component.ts
+++ b/frontend/src/app/core/global_search/input/global-search-input.component.ts
@@ -251,6 +251,8 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
this.selectedItem = undefined;
this.toggleTopMenuClass();
}
+
+ (
document.activeElement).blur();
}
public onClose():void {
@@ -467,11 +469,6 @@ export class GlobalSearchInputComponent implements AfterViewInit, OnDestroy {
}
}
- public blur():void {
- this.ngSelectComponent.ngSelectInstance.searchTerm = '';
- (document.activeElement).blur();
- }
-
private get currentScope():string {
const serviceScope = this.globalSearchService.projectScope;
return (serviceScope === '') ? 'current_project_and_all_descendants' : serviceScope;
diff --git a/frontend/src/app/features/homescreen/blocks/new-features.component.ts b/frontend/src/app/features/homescreen/blocks/new-features.component.ts
index 37241f6597d..b6f044b4fdd 100644
--- a/frontend/src/app/features/homescreen/blocks/new-features.component.ts
+++ b/frontend/src/app/features/homescreen/blocks/new-features.component.ts
@@ -35,10 +35,10 @@ import { imagePath } from 'core-app/shared/helpers/images/path-helper';
export const homescreenNewFeaturesBlockSelector = 'homescreen-new-features-block';
// The key used in the I18n files to distinguish between versions.
-const OpVersionI18n = '13_3';
+const OpVersionI18n = '13_4';
/** Update the teaser image to the next version */
-const featureTeaserImage = '13_3_features.svg';
+const featureTeaserImage = '13_4_features.svg';
@Component({
template: `
diff --git a/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts b/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts
index 435b8e463f3..9d6fefe8fc3 100644
--- a/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts
+++ b/frontend/src/app/features/team-planner/team-planner/assignee/add-assignee.component.ts
@@ -85,9 +85,9 @@ export class AddAssigneeComponent {
.apiV3Service
.principals
.filtered(filters)
- .get()
+ .getPaginatedResults()
.pipe(
- map((collection) => collection.elements.filter(
+ map((elements) => elements.filter(
(user) => !this.alreadySelected.find((selected) => selected === user.id),
)),
);
diff --git a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts
index 2755041a09e..75342254a4d 100644
--- a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts
+++ b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts
@@ -82,6 +82,7 @@ import {
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
+import { MAGIC_PAGE_NUMBER } from 'core-app/core/apiv3/helpers/get-paginated-results';
import { CalendarDragDropService } from 'core-app/features/team-planner/team-planner/calendar-drag-drop.service';
import { StatusResource } from 'core-app/features/hal/resources/status-resource';
import { ResourceChangeset } from 'core-app/shared/components/fields/changeset/resource-changeset';
@@ -223,7 +224,7 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
return this
.capabilitiesResourceService
- .fetchCapabilities({ pageSize: -1, filters });
+ .fetchCapabilities({ pageSize: MAGIC_PAGE_NUMBER, filters });
}),
map((result) => result
._embedded
@@ -250,6 +251,7 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
filter((ids) => ids.length > 0),
map((ids) => ({
filters: [['id', '=', ids]],
+ pageSize: MAGIC_PAGE_NUMBER,
}) as ApiV3ListParameters),
);
diff --git a/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.html b/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.html
index afb48aa709d..61802814662 100644
--- a/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.html
+++ b/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.html
@@ -8,4 +8,13 @@
-
+
+
+
+
+
+
+
diff --git a/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.ts b/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.ts
index 31c9b9f068f..64377300b05 100644
--- a/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.ts
+++ b/frontend/src/app/shared/components/grids/widgets/wp-calendar/wp-calendar.component.ts
@@ -33,6 +33,7 @@ import { CurrentProjectService } from 'core-app/core/current-project/current-pro
import {
WorkPackageIsolatedQuerySpaceDirective,
} from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive';
+import { CurrentUserService } from 'core-app/core/current-user/current-user.service';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -40,10 +41,19 @@ import {
hostDirectives: [WorkPackageIsolatedQuerySpaceDirective],
})
export class WidgetWpCalendarComponent extends AbstractWidgetComponent {
- constructor(protected readonly i18n:I18nService,
+ text = {
+ missing_permission: this.I18n.t('js.grid.widgets.missing_permission'),
+ };
+
+ hasCapability$ = this.currentUser.hasCapabilities$('work_packages/read', this.currentProject.id);
+
+ constructor(
+ protected readonly I18n:I18nService,
protected readonly injector:Injector,
- protected readonly currentProject:CurrentProjectService) {
- super(i18n, injector);
+ protected readonly currentProject:CurrentProjectService,
+ protected readonly currentUser:CurrentUserService,
+ ) {
+ super(I18n, injector);
}
public get projectIdentifier() {
diff --git a/frontend/src/app/shared/components/storages/file-link-list-item/file-link-list-item.component.ts b/frontend/src/app/shared/components/storages/file-link-list-item/file-link-list-item.component.ts
index 41d12a412d2..1b0c0c344e9 100644
--- a/frontend/src/app/shared/components/storages/file-link-list-item/file-link-list-item.component.ts
+++ b/frontend/src/app/shared/components/storages/file-link-list-item/file-link-list-item.component.ts
@@ -33,8 +33,10 @@ import {
ElementRef,
EventEmitter,
Input,
+ OnChanges,
OnInit,
Output,
+ SimpleChanges,
ViewChild,
} from '@angular/core';
@@ -60,7 +62,7 @@ import SpotDropAlignmentOption from 'core-app/spot/drop-alignment-options';
templateUrl: './file-link-list-item.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class FileLinkListItemComponent implements OnInit, AfterViewInit {
+export class FileLinkListItemComponent implements OnInit, OnChanges, AfterViewInit {
@Input() public fileLink:IFileLink;
@Input() public allowEditing = false;
@@ -132,8 +134,16 @@ export class FileLinkListItemComponent implements OnInit, AfterViewInit {
'js.storages.file_links.download',
{ fileName: this.fileLink.originData.name },
);
+ }
- this.floatingActions = this.getFloatingActions();
+ // Before, the getFloatingActions() method was called in the ngOnInit() method.
+ // The value of the allowEditing property can be calculated after the component is already initialized (in fact it is determined
+ // asynchronously, by getting a value from the server in a separate request). Therefore, the available actions need
+ // to be calculated whenever the value is set.
+ ngOnChanges(changes:SimpleChanges):void {
+ if (changes.allowEditing) {
+ this.floatingActions = this.getFloatingActions();
+ }
}
ngAfterViewInit():void {
diff --git a/frontend/src/assets/images/13_3_features.svg b/frontend/src/assets/images/13_3_features.svg
deleted file mode 100644
index 620ebdbccf8..00000000000
--- a/frontend/src/assets/images/13_3_features.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/frontend/src/assets/images/13_4_features.svg b/frontend/src/assets/images/13_4_features.svg
new file mode 100644
index 00000000000..4a61bf1212c
--- /dev/null
+++ b/frontend/src/assets/images/13_4_features.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/global_styles/content/_accounts.sass b/frontend/src/global_styles/content/_accounts.sass
index d6e0635ab88..e25743fcc01 100644
--- a/frontend/src/global_styles/content/_accounts.sass
+++ b/frontend/src/global_styles/content/_accounts.sass
@@ -28,16 +28,14 @@
#login-form
margin: 50px auto 0
- padding-top: 20px
+ padding: 20px
width: 511px
word-break: break-word
- .login-options-container
+ .login-form--footer
+ display: flex
+ justify-content: space-between
margin-bottom: 10px
- .login-links
- float: right
- text-align: right
- margin-top: -3rem
#content .login-auth-providers.wide
width: auto
@@ -63,13 +61,9 @@
margin-top: 25px
h3
- border: none
- margin-top: 20px
font-weight: normal
font-size: 1rem
- text-decoration: none
text-align: center
-
position: relative
z-index: 1
@@ -101,24 +95,11 @@
@include prevent-float-collapse
margin-top: 1em
- a.auth-provider
- float: left
- margin-left: 10px
- background-color: light-grey
- font-weight: normal
- color: var(--body-font-color)
-
- &:hover
- text-decoration: none
-
- .auth-provider-name
- line-height: normal
- white-space: normal
+ a.auth-provider:hover
+ text-decoration: none
#nav-login-content .login-auth-providers
h3
- &:before
- width: 100%
span
background: var(--header-drop-down-bg-color)
.login-auth-provider-list
diff --git a/frontend/src/global_styles/content/_accounts_mobile.sass b/frontend/src/global_styles/content/_accounts_mobile.sass
index 98a4a4ae8e4..0d3f10e4b07 100644
--- a/frontend/src/global_styles/content/_accounts_mobile.sass
+++ b/frontend/src/global_styles/content/_accounts_mobile.sass
@@ -32,31 +32,23 @@
#nav-login-content .login-auth-providers
width: 100%
- #content .login-auth-providers
- margin-top: 114px
-
- h3:before
- top: -14px
-
#login-form
.form--field-container
@include grid-content(12)
padding: 0
+ margin: 0
- .login-options-container
- position: relative
+ .login-form--footer
+ flex-direction: column
+ align-items: center
- .login-links
- float: none
- position: absolute
- text-align: center
- width: 100%
- margin-top: 0
-
- a
- display: block
+ .login-form--footer-link
+ display: inline-block
+ margin-top: 15px
.button
+ margin-right: 0
+ margin-left: 0
width: 100%
#new_user
diff --git a/frontend/src/global_styles/content/modules/_2fa.sass b/frontend/src/global_styles/content/modules/_2fa.sass
index 552cc8a8b0c..bc015422b9b 100644
--- a/frontend/src/global_styles/content/modules/_2fa.sass
+++ b/frontend/src/global_styles/content/modules/_2fa.sass
@@ -54,42 +54,14 @@
.mobile-otp-new-device--body
flex: 1
-#login-form input[type=radio]
- float: left
-
#resend_otp_container
margin-top: 30px
-#resend_otp_container input[type=radio]
- margin-right: 8px
-
-#resend_otp_container label
- margin-right: 10px
-
#resend_otp_container .resend-header
font-size: 14px
font-weight: var(--base-text-weight-bold)
margin-bottom: 10px
-#resend_otp_container .radios-wrapper
- padding-top: 7px
- float: left
- border: none
- overflow: hidden
- clear: none
-
-
-#resend_otp_container .radios-wrapper label
- float: left
- clear: none
- display: block
- padding: 2px 1em 0 0
-
-
-#resend_otp_container .radios-wrapper input[type=radio]
- float: left
- clear: none
- margin: 2px 0 15px 2px
// Add some paddings to action links
.mobile-otp--two-factor-device-row td.buttons
diff --git a/frontend/src/global_styles/layout/work_packages/_table_baseline_overrides.sass b/frontend/src/global_styles/layout/work_packages/_table_baseline_overrides.sass
index e816fcfa557..aa85bfddeea 100644
--- a/frontend/src/global_styles/layout/work_packages/_table_baseline_overrides.sass
+++ b/frontend/src/global_styles/layout/work_packages/_table_baseline_overrides.sass
@@ -27,8 +27,8 @@
//++
.op-table-baseline--old-field
- // Hide status bubbles in old values
- &[class^=__hl_inline_status]:before
+ // Hide highlighting bubbles in old values
+ &[class^=__hl_inline_]:before
display: none
// Hide avatars in old values
diff --git a/frontend/src/global_styles/openproject/_announcements.sass b/frontend/src/global_styles/openproject/_announcements.sass
index 07fe3a4ca73..8a42ffdcbbc 100644
--- a/frontend/src/global_styles/openproject/_announcements.sass
+++ b/frontend/src/global_styles/openproject/_announcements.sass
@@ -4,3 +4,5 @@
margin-top: 1rem
width: 511px
+ @media screen and (max-width: $breakpoint-sm)
+ width: 100%
diff --git a/modules/grids/config/locales/js-en.yml b/modules/grids/config/locales/js-en.yml
index b4c81602a84..44543cb5754 100644
--- a/modules/grids/config/locales/js-en.yml
+++ b/modules/grids/config/locales/js-en.yml
@@ -8,6 +8,7 @@ en:
text: "Some widgets, like the work package graph widget, are only available in the Enterprise edition."
link: 'Enterprise edition.'
widgets:
+ missing_permission: "You don't have the necessary permissions to view this widget."
custom_text:
title: 'Custom text'
documents:
diff --git a/modules/storages/spec/features/create_file_links_spec.rb b/modules/storages/spec/features/create_file_links_spec.rb
index 7074bb7df4b..0221ffaabf7 100644
--- a/modules/storages/spec/features/create_file_links_spec.rb
+++ b/modules/storages/spec/features/create_file_links_spec.rb
@@ -129,7 +129,7 @@ RSpec.describe 'Managing file links in work package', :js, :webmock do
end
it 'must enable the user to remove a file link' do
- within_test_selector('op-tab-content--tab-section', text: 'MY STORAGE', wait: 25) do
+ within_test_selector('op-tab-content--tab-section', text: 'MY STORAGE') do
within(:list_item, text: 'jingle.ogg') do
page.find('span', text: 'jingle.ogg').hover
page.click_on('Remove file link')
diff --git a/modules/team_planner/spec/features/team_planner_context_menu_spec.rb b/modules/team_planner/spec/features/team_planner_context_menu_spec.rb
index 3054cd7ab24..6813a415f08 100644
--- a/modules/team_planner/spec/features/team_planner_context_menu_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_context_menu_spec.rb
@@ -43,9 +43,7 @@ RSpec.describe 'Work package table context menu',
team_planner.visit!
loading_indicator_saveguard
- retry_block do
- team_planner.add_assignee user
- end
+ team_planner.add_assignee user
team_planner.within_lane(user) do
team_planner.expect_event work_package
diff --git a/modules/team_planner/spec/features/team_planner_create_spec.rb b/modules/team_planner/spec/features/team_planner_create_spec.rb
index 70de2980ba5..7bc0eee95ce 100644
--- a/modules/team_planner/spec/features/team_planner_create_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_create_spec.rb
@@ -77,11 +77,7 @@ RSpec.describe 'Team planner create new work package', :js, with_ee: %i[team_pla
team_planner.expect_assignee(user, present: false)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee user.name
end
it_behaves_like 'can create a new work package'
@@ -119,23 +115,9 @@ RSpec.describe 'Team planner create new work package', :js, with_ee: %i[team_pla
team_planner.expect_assignee(other_user, present: false)
team_planner.expect_assignee(third_user, present: false)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add other_user.name
- end
-
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add third_user.name
- end
-
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee other_user.name
+ team_planner.add_assignee third_user.name
+ team_planner.add_assignee user.name
end
it_behaves_like 'can create a new work package'
diff --git a/modules/team_planner/spec/features/team_planner_dates_spec.rb b/modules/team_planner/spec/features/team_planner_dates_spec.rb
index e429e6b3b75..50d521d2b9f 100644
--- a/modules/team_planner/spec/features/team_planner_dates_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_dates_spec.rb
@@ -41,11 +41,7 @@ RSpec.describe 'Team planner working days', :js,
team_planner.visit!
team_planner.expect_empty_state
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee user.name
# Initially, in the "Work week" view, non working days are hidden
expect(page).to have_css('.fc-day-mon')
diff --git a/modules/team_planner/spec/features/team_planner_project_include_spec.rb b/modules/team_planner/spec/features/team_planner_project_include_spec.rb
index 30391f5ef5a..3d5a42ad9a7 100644
--- a/modules/team_planner/spec/features/team_planner_project_include_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_project_include_spec.rb
@@ -56,15 +56,11 @@ RSpec.describe 'Team planner project include', :js, with_ee: %i[team_planner_vie
work_package_view.expect_assignee(other_user, present: false)
retry_block do
- work_package_view.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- work_package_view.select_user_to_add user.name
+ work_package_view.add_assignee user.name
end
retry_block do
- work_package_view.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- work_package_view.select_user_to_add other_user.name
+ work_package_view.add_assignee other_user.name
end
work_package_view.expect_assignee user
diff --git a/modules/team_planner/spec/features/team_planner_spec.rb b/modules/team_planner/spec/features/team_planner_spec.rb
index 967e5784802..7326ac8e795 100644
--- a/modules/team_planner/spec/features/team_planner_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_spec.rb
@@ -155,19 +155,11 @@ RSpec.describe 'Team planner',
team_planner.expect_assignee(user, present: false)
team_planner.expect_assignee(other_user, present: false)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee user.name
team_planner.expect_empty_state(present: false)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add other_user.name
- end
+ team_planner.add_assignee other_user.name
team_planner.expect_assignee user
team_planner.expect_assignee other_user
@@ -249,21 +241,13 @@ RSpec.describe 'Team planner',
team_planner.expect_assignee(user, present: false)
team_planner.expect_assignee(other_user, present: false)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee user.name
team_planner.expect_empty_state(present: false)
team_planner.expect_assignee(user)
team_planner.expect_assignee(other_user, present: false)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add other_user.name
- end
+ team_planner.add_assignee other_user.name
team_planner.expect_assignee(user)
team_planner.expect_assignee(other_user)
@@ -280,11 +264,7 @@ RSpec.describe 'Team planner',
team_planner.expect_empty_state
# Try one more time to make sure deleting the full filter didn't kill the functionality
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee user.name
team_planner.expect_assignee(user)
team_planner.expect_assignee(other_user, present: false)
@@ -293,11 +273,7 @@ RSpec.describe 'Team planner',
it 'filters possible assignees correctly' do
team_planner.visit!
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.search_user_to_add user_outside_project.name
- end
+ team_planner.search_assignee(user_outside_project.name)
expect(page).to have_css('.ng-option-disabled', text: "No items found")
@@ -307,14 +283,45 @@ RSpec.describe 'Team planner',
team_planner.expect_assignee(user)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.search_user_to_add user.name
- end
+ team_planner.search_assignee user.name
expect(page).to have_css('.ng-option-disabled', text: "No items found")
end
+
+ context 'when the page size is smaller than the number of assignees' do
+ before do
+ allow(Setting)
+ .to receive(:per_page_options_array)
+ .and_return([1])
+ end
+
+ it 'renders assignees and assignee dropdown correctly' do
+ team_planner.visit!
+ team_planner.wait_for_loaded
+
+ # Render all the available users in the select dropdown regardless of the page size
+ team_planner.click_add_user
+
+ team_planner.expect_user_selectable user
+ team_planner.expect_user_selectable other_user
+
+ team_planner.add_assignee user
+ team_planner.add_assignee other_user
+
+ team_planner.save_as('TP1')
+ page.refresh
+ team_planner.wait_for_loaded
+
+ # Render all the available users in the team planner regardless of the page size
+ team_planner.expect_assignee user
+ team_planner.expect_assignee other_user
+
+ # Do not render any available users in the select
+ team_planner.click_add_user
+ team_planner.expect_user_selectable user, present: false
+ team_planner.expect_user_selectable other_user, present: false
+ end
+ end
end
context 'with a readonly work package' do
@@ -337,11 +344,7 @@ RSpec.describe 'Team planner',
team_planner.expect_empty_state
team_planner.expect_assignee(user, present: false)
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee user.name
team_planner.expect_empty_state(present: false)
team_planner.expect_assignee user
diff --git a/modules/team_planner/spec/features/team_planner_subproject_constraints_spec.rb b/modules/team_planner/spec/features/team_planner_subproject_constraints_spec.rb
index 5babad79c12..c3ff5fc2a77 100644
--- a/modules/team_planner/spec/features/team_planner_subproject_constraints_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_subproject_constraints_spec.rb
@@ -58,9 +58,7 @@ RSpec.describe 'Team planner constraints for a subproject',
team_planner.visit!
team_planner.add_assignee user
- retry_block do
- team_planner.add_assignee other_user
- end
+ team_planner.add_assignee other_user
# Include the subproject
project_include.toggle!
diff --git a/modules/team_planner/spec/features/team_planner_user_interaction_spec.rb b/modules/team_planner/spec/features/team_planner_user_interaction_spec.rb
index 457b26983ad..460e46f86d5 100644
--- a/modules/team_planner/spec/features/team_planner_user_interaction_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_user_interaction_spec.rb
@@ -79,9 +79,7 @@ RSpec.describe 'Team planner drag&dop and resizing',
team_planner.visit!
team_planner.add_assignee user
- retry_block do
- team_planner.add_assignee other_user
- end
+ team_planner.add_assignee other_user
team_planner.within_lane(user) do
team_planner.expect_event first_wp, present: false
@@ -247,9 +245,7 @@ RSpec.describe 'Team planner drag&dop and resizing',
team_planner.visit!
team_planner.add_assignee user
- retry_block do
- team_planner.add_assignee other_user
- end
+ team_planner.add_assignee other_user
end
it 'allows neither dragging nor resizing any wp' do
diff --git a/modules/team_planner/spec/features/team_planner_view_modes_spec.rb b/modules/team_planner/spec/features/team_planner_view_modes_spec.rb
index e7e786cc355..56ded17546f 100644
--- a/modules/team_planner/spec/features/team_planner_view_modes_spec.rb
+++ b/modules/team_planner/spec/features/team_planner_view_modes_spec.rb
@@ -36,11 +36,7 @@ RSpec.describe 'Team planner', :js, with_ee: %i[team_planner_view] do
team_planner.visit!
team_planner.expect_empty_state
- retry_block do
- team_planner.click_add_user
- page.find("#{test_selector('tp-add-assignee')} input")
- team_planner.select_user_to_add user.name
- end
+ team_planner.add_assignee user.name
team_planner.expect_view_mode 'Work week'
expect(page).to have_css('.fc-timeline-slot-frame', count: 5)
diff --git a/modules/team_planner/spec/support/pages/team_planner.rb b/modules/team_planner/spec/support/pages/team_planner.rb
index de1656388b8..5b83fd3f574 100644
--- a/modules/team_planner/spec/support/pages/team_planner.rb
+++ b/modules/team_planner/spec/support/pages/team_planner.rb
@@ -225,9 +225,23 @@ module Pages
end
def add_assignee(name)
- click_add_user
- page.find("#{page.test_selector('tp-add-assignee')} input")
- select_user_to_add name
+ retry_block do
+ return if page.has_selector?('.fc-resource', text: name, wait: 0)
+
+ click_add_user
+ page.find("#{page.test_selector('tp-add-assignee')} input")
+ select_user_to_add(name)
+ end
+ end
+
+ def search_assignee(name)
+ retry_block do
+ click_add_user
+ page.find("#{page.test_selector('tp-add-assignee')} input")
+ search_autocomplete page.find('[data-test-selector="tp-add-assignee"]'),
+ query: name,
+ results_selector: 'body'
+ end
end
def click_add_user
@@ -243,10 +257,13 @@ module Pages
results_selector: 'body'
end
- def search_user_to_add(name)
- search_autocomplete page.find('[data-test-selector="tp-add-assignee"]'),
- query: name,
- results_selector: 'body'
+ def expect_user_selectable(user, present: true)
+ name = user.is_a?(User) ? user.name : user.to_s
+
+ expect_ng_option page.find('[data-test-selector="tp-add-assignee"]'),
+ name,
+ results_selector: 'body',
+ present:
end
def change_wp_date_by_resizing(work_package, number_of_days:, is_start_date:)
diff --git a/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/enter_backup_code.html.erb b/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/enter_backup_code.html.erb
index da2ca788a88..4406a7d07c0 100644
--- a/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/enter_backup_code.html.erb
+++ b/modules/two_factor_authentication/app/views/two_factor_authentication/authentication/enter_backup_code.html.erb
@@ -11,7 +11,8 @@
<%= styled_text_field_tag 'backup_code', nil, required: true, autocomplete: 'off', size: 20, maxlength: 20, tabindex: 1, autofocus: true %>
-
@@ -68,15 +68,23 @@
<%= hidden_field_tag 'use_device', @service.device.id %>
-
- <% default_channel = @service.device.channel %>
- <% supported_channels = @used_device.class.supported_channels & @strategy.class.supported_channels %>
- <% supported_channels.each do |channel| %>
+
+ <% default_channel = @service.device.channel %>
+ <% supported_channels = @used_device.class.supported_channels & @strategy.class.supported_channels %>
+ <% supported_channels.each do |channel| %>
+
<%= radio_button_tag 'use_channel', channel, default_channel == channel %>
<%= t("button_otp_by_#{channel}") %>
+<<<<<<< HEAD
<% end %>
+=======
+
+ <% end %>
+
+
+>>>>>>> origin/release/13.4
<% end %>
<% end %>
<% if has_other_devices %>
@@ -95,12 +103,13 @@
<% end %>
<% if has_backup_codes %>
+
+
<%= link_to(t('two_factor_authentication.login.enter_backup_code_title'), { action: :enter_backup_code }) %>
-
-
-
- <% end %>
-
+
+
+ <% end %>
+
diff --git a/modules/two_factor_authentication/app/views/two_factor_authentication/two_factor_devices/confirm.html.erb b/modules/two_factor_authentication/app/views/two_factor_authentication/two_factor_devices/confirm.html.erb
index 64107c8b5c0..44c45f1da3f 100644
--- a/modules/two_factor_authentication/app/views/two_factor_authentication/two_factor_devices/confirm.html.erb
+++ b/modules/two_factor_authentication/app/views/two_factor_authentication/two_factor_devices/confirm.html.erb
@@ -14,6 +14,6 @@