mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Replace show_banners with hide_banners
Encode features in the configuration API, instead of relying on a boolean yes/no flag
This commit is contained in:
@@ -46,7 +46,7 @@ module EnterpriseEdition
|
||||
description: nil,
|
||||
link_title: nil,
|
||||
href: nil,
|
||||
skip_render: !EnterpriseToken.show_banners?(feature: feature_key),
|
||||
skip_render: EnterpriseToken.hide_banners?,
|
||||
**system_arguments)
|
||||
@system_arguments = system_arguments
|
||||
@system_arguments[:test_selector] = "op-enterprise-banner-#{feature_key.to_s.tr('_', '-')}"
|
||||
|
||||
@@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
render(EnterpriseEdition::BannerComponent.new(:automatic_subject_generation, mb: 3))
|
||||
render(EnterpriseEdition::BannerComponent.new(:work_package_subject_generation, mb: 3))
|
||||
%>
|
||||
|
||||
<%=
|
||||
|
||||
@@ -242,7 +242,9 @@ module ApplicationHelper
|
||||
css << ("action-#{action_name}")
|
||||
end
|
||||
|
||||
css << "ee-banners-#{EnterpriseToken.show_banners? ? 'visible' : 'hidden'}"
|
||||
if EnterpriseToken.hide_banners?
|
||||
css << "ee-banners-hidden"
|
||||
end
|
||||
|
||||
css << "env-#{Rails.env}"
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ module HomescreenHelper
|
||||
##
|
||||
# Determine whether we should render the links on homescreen?
|
||||
def show_homescreen_links?
|
||||
EnterpriseToken.show_banners? || OpenProject::Configuration.show_community_links?
|
||||
OpenProject::Configuration.show_community_links?
|
||||
end
|
||||
|
||||
##
|
||||
|
||||
@@ -45,12 +45,8 @@ class EnterpriseToken < ApplicationRecord
|
||||
current && !current.expired?
|
||||
end
|
||||
|
||||
def show_banners?(feature: nil)
|
||||
if feature
|
||||
OpenProject::Configuration.ee_manager_visible? && (!active? || !allows_to?(feature))
|
||||
else
|
||||
OpenProject::Configuration.ee_manager_visible? && !active?
|
||||
end
|
||||
def hide_banners?
|
||||
!OpenProject::Configuration.ee_manager_visible?
|
||||
end
|
||||
|
||||
def banner_type_for(feature:)
|
||||
@@ -88,6 +84,7 @@ class EnterpriseToken < ApplicationRecord
|
||||
:reprieve_days,
|
||||
:reprieve_days_left,
|
||||
:restrictions,
|
||||
:available_features,
|
||||
:plan,
|
||||
:features,
|
||||
:version,
|
||||
|
||||
@@ -33,7 +33,8 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<div class="form--field">
|
||||
<label class="form--label" for="project_enabled_module_names_<%= name %>">
|
||||
<%= l_or_humanize(name, prefix: "project_module_") %>
|
||||
<% if EnterpriseToken.show_banners? && OpenProject::AccessControl.module_enterprise_feature?(name) %>
|
||||
<% feature = OpenProject::AccessControl.module_enterprise_feature?(name) %>
|
||||
<% if feature && !EnterpriseToken.allows_to?(feature) %>
|
||||
<%= spot_icon(
|
||||
"enterprise-addons",
|
||||
inline: true,
|
||||
|
||||
@@ -37,7 +37,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
render(EnterpriseEdition::BannerComponent.new(:form_configuration, mb: 3))
|
||||
%>
|
||||
|
||||
<% if EnterpriseToken.show_banners? %>
|
||||
<% unless EnterpriseToken.allows_to?(:form_configuration) %>
|
||||
<%= angular_component_tag "opce-no-results",
|
||||
inputs: {
|
||||
title: t("text_form_configuration") + t("text_custom_field_hint_activate_per_project")
|
||||
|
||||
@@ -56,7 +56,7 @@ OpenProject::Static::Homescreen.manage :blocks do |blocks|
|
||||
},
|
||||
{
|
||||
partial: "community",
|
||||
if: Proc.new { EnterpriseToken.show_banners? || OpenProject::Configuration.show_community_links? }
|
||||
if: Proc.new { OpenProject::Configuration.show_community_links? }
|
||||
},
|
||||
{
|
||||
partial: "administration",
|
||||
@@ -64,7 +64,7 @@ OpenProject::Static::Homescreen.manage :blocks do |blocks|
|
||||
},
|
||||
{
|
||||
partial: "upsale",
|
||||
if: Proc.new { EnterpriseToken.show_banners? }
|
||||
if: Proc.new { !EnterpriseToken.hide_banners? }
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
@@ -2044,7 +2044,7 @@ en:
|
||||
plan_name: "%{plan} enterprise plan"
|
||||
plan_text_html: "Available only through the %{plan_name}."
|
||||
link_title: "More information"
|
||||
automatic_subject_generation:
|
||||
work_package_subject_generation:
|
||||
description: "Create automatically generated subjects using referenced attributes and text."
|
||||
customize_life_cycle:
|
||||
description: "Create and organize different project phases than the ones provided by PM2 project cycle planning."
|
||||
|
||||
@@ -131,7 +131,7 @@ pricing:
|
||||
progress_tracking_docs:
|
||||
href: https://www.openproject.org/docs/user-guide/time-and-costs/progress-tracking/
|
||||
enterprise_docs:
|
||||
automatic_subject_generation:
|
||||
work_package_subject_generation:
|
||||
href: https://www.openproject.org/docs/system-admin-guide/manage-work-packages/work-package-types/#work-package-subject-configuration-enterprise-add-on
|
||||
form_configuration:
|
||||
href: https://www.openproject.org/docs/system-admin-guide/manage-work-packages/work-package-types/#work-package-form-configuration-enterprise-add-on
|
||||
|
||||
@@ -146,6 +146,10 @@ export class ConfigurationService {
|
||||
return this.systemPreference<string[]>('activeFeatureFlags');
|
||||
}
|
||||
|
||||
public get availableFeatures():string[] {
|
||||
return this.systemPreference<string[]>('availableFeatures');
|
||||
}
|
||||
|
||||
private loadConfiguration() {
|
||||
return this
|
||||
.apiV3Service
|
||||
|
||||
@@ -29,17 +29,21 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { enterpriseEditionUrl } from 'core-app/core/setup/globals/constants.const';
|
||||
import { ConfigurationService } from 'core-app/core/config/configuration.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BannersService {
|
||||
private readonly _banners:boolean = true;
|
||||
private readonly _bannersHidden:boolean = true;
|
||||
|
||||
constructor(@Inject(DOCUMENT) protected documentElement:Document) {
|
||||
this._banners = documentElement.body.classList.contains('ee-banners-visible');
|
||||
constructor(
|
||||
@Inject(DOCUMENT) protected documentElement:Document,
|
||||
protected configuration:ConfigurationService,
|
||||
) {
|
||||
this._bannersHidden = documentElement.body.classList.contains('ee-banners-hidden');
|
||||
}
|
||||
|
||||
public get eeShowBanners():boolean {
|
||||
return this._banners;
|
||||
public showBannerFor(feature:string):boolean {
|
||||
return !(this._bannersHidden || this.configuration.availableFeatures.includes(feature));
|
||||
}
|
||||
|
||||
public getEnterPriseEditionUrl({ referrer, hash }:{ referrer?:string, hash?:string } = {}) {
|
||||
@@ -55,11 +59,15 @@ export class BannersService {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
public conditional(bannersVisible?:() => void, bannersNotVisible?:() => void) {
|
||||
this._banners ? this.callMaybe(bannersVisible) : this.callMaybe(bannersNotVisible);
|
||||
public conditional(feature:string, bannersVisible?:() => void, bannersNotVisible?:() => void) {
|
||||
if (this.showBannerFor(feature)) {
|
||||
this.callMaybe(bannersVisible);
|
||||
} else {
|
||||
this.callMaybe(bannersNotVisible);
|
||||
}
|
||||
}
|
||||
|
||||
private callMaybe(func?:Function) {
|
||||
private callMaybe(func?:() => unknown) {
|
||||
func && func();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ export class GroupEditInPlaceComponent implements OnInit {
|
||||
|
||||
startEditing():void {
|
||||
this.bannerService.conditional(
|
||||
'work_package_query_relation_columns',
|
||||
() => this.bannerService.showEEOnlyHint(),
|
||||
() => {
|
||||
this.editing = true;
|
||||
|
||||
@@ -3,13 +3,19 @@ import { BannersService } from 'core-app/core/enterprise/banners.service';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { ConfirmDialogService } from 'core-app/shared/components/modals/confirm-dialog/confirm-dialog.service';
|
||||
import { ConfigurationService } from 'core-app/core/config/configuration.service';
|
||||
|
||||
@Injectable()
|
||||
export class TypeBannerService extends BannersService {
|
||||
constructor(@Inject(DOCUMENT) protected documentElement:Document,
|
||||
private confirmDialog:ConfirmDialogService,
|
||||
private I18n:I18nService) {
|
||||
super(documentElement);
|
||||
showBanners = this.showBannerFor('edit_attribute_groups');
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) protected documentElement:Document,
|
||||
protected confirmDialog:ConfirmDialogService,
|
||||
protected I18n:I18nService,
|
||||
protected configuration:ConfigurationService,
|
||||
) {
|
||||
super(documentElement, configuration);
|
||||
}
|
||||
|
||||
showEEOnlyHint():void {
|
||||
@@ -23,6 +29,7 @@ export class TypeBannerService extends BannersService {
|
||||
}).then(() => {
|
||||
window.location.href = 'https://www.openproject.org/enterprise-edition/?utm_source=unknown&utm_medium=community-edition&utm_campaign=form-configuration';
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ export class TypeFormConfigurationComponent extends UntilDestroyedMixin implemen
|
||||
|
||||
editQuery(group:TypeGroup):void {
|
||||
this.typeBanner.conditional(
|
||||
'edit_attribute_groups',
|
||||
() => this.typeBanner.showEEOnlyHint(),
|
||||
() => {
|
||||
// Disable display mode and timeline for now since we don't want users to enable it
|
||||
@@ -200,7 +201,8 @@ export class TypeFormConfigurationComponent extends UntilDestroyedMixin implemen
|
||||
}
|
||||
|
||||
deleteGroup(group:TypeGroup):void {
|
||||
this.typeBanner.conditional(
|
||||
void this.typeBanner.conditional(
|
||||
'edit_attribute_groups',
|
||||
() => this.typeBanner.showEEOnlyHint(),
|
||||
() => {
|
||||
if (group.type === 'attribute') {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="!typeBanner.eeShowBanners"
|
||||
*ngIf="!typeBanner.showBanners"
|
||||
class="toolbar-item drop-down">
|
||||
<a class="form-configuration--add-group button -primary" aria-haspopup="true">
|
||||
<op-icon icon-classes="button--icon icon-add"></op-icon>
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ export class BoardActionsRegistryService {
|
||||
icon: '',
|
||||
description: '',
|
||||
image: '',
|
||||
disabled: this.bannersService.eeShowBanners,
|
||||
disabled: this.bannersService.showBannerFor('board_view'),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -91,7 +91,7 @@ export class BoardListContainerComponent extends UntilDestroyedMixin implements
|
||||
|
||||
showHiddenListWarning:boolean = false;
|
||||
|
||||
needEnterpriseEdition = this.Banner.eeShowBanners;
|
||||
needEnterpriseEdition = this.Banner.showBannerFor('board_view');
|
||||
|
||||
private currentQueryUpdatedMonitoring:Subscription;
|
||||
|
||||
@@ -129,7 +129,7 @@ readonly I18n:I18nService,
|
||||
);
|
||||
|
||||
this.board$.subscribe((board) => {
|
||||
this.needEnterpriseEdition = this.Banner.eeShowBanners && !board.isFree;
|
||||
this.needEnterpriseEdition = this.Banner.showBannerFor('board_view') && !board.isFree;
|
||||
});
|
||||
|
||||
this.Boards.currentBoard$.next(id);
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ export class ProjectSelectionComponent implements OnInit {
|
||||
}
|
||||
|
||||
private setPlaceholderOption():void {
|
||||
if (this.bannersService.eeShowBanners) {
|
||||
if (this.bannersService.showBannerFor('placeholder_users')) {
|
||||
this.typeOptions.push({
|
||||
value: PrincipalType.Placeholder,
|
||||
title: this.I18n.t('js.invite_user_modal.type.placeholder.title_no_ee'),
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@ export class NotificationsSettingsPageComponent extends UntilDestroyedMixin impl
|
||||
|
||||
ngOnInit():void {
|
||||
this.form.disable();
|
||||
this.eeShowBanners = this.bannersService.eeShowBanners;
|
||||
this.eeShowBanners = this.bannersService.showBannerFor('date_alerts');
|
||||
|
||||
this
|
||||
.currentUserService
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ export class NotificationSettingsTableComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit():void {
|
||||
this.eeShowBanners = this.bannersService.eeShowBanners;
|
||||
this.eeShowBanners = this.bannersService.showBannerFor('date_alerts');
|
||||
}
|
||||
|
||||
projectLink(href:string) {
|
||||
|
||||
-2
@@ -93,7 +93,6 @@ export class QueryFilterComponent implements OnInit {
|
||||
readonly schemaCache:SchemaCacheService,
|
||||
readonly I18n:I18nService,
|
||||
readonly currentProject:CurrentProjectService,
|
||||
readonly bannerService:BannersService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -116,7 +115,6 @@ export class QueryFilterComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.eeShowBanners = this.bannerService.eeShowBanners;
|
||||
this.availableOperators = this.schemaCache.of(this.filter).availableOperators;
|
||||
this.showValuesInput = this.showValues();
|
||||
this.baselineIncompatibleFilter = this.wpTableBaseline.isActive() && this.wpTableBaseline.isIncompatibleFilter(this.filter.id);
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit {
|
||||
|
||||
public tooltipPosition = SpotDropAlignmentOption.TopRight;
|
||||
|
||||
eeShowBanners = this.Banner.eeShowBanners;
|
||||
eeShowBanners = this.Banner.showBannerFor('baseline_comparison');
|
||||
|
||||
public text = {
|
||||
toggle_title: this.I18n.t('js.baseline.toggle_title'),
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ import { CollectionResource } from 'core-app/features/hal/resources/collection-r
|
||||
export class WorkPackageShareButtonComponent extends UntilDestroyedMixin implements OnInit {
|
||||
@Input() public workPackage:WorkPackageResource;
|
||||
|
||||
showEnterpriseIcon = this.bannersService.eeShowBanners;
|
||||
showEnterpriseIcon = this.bannersService.showBannerFor('work_package_sharing');
|
||||
|
||||
shareCount$:Observable<number>;
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<ng-container
|
||||
*ngIf="!this.bannersService.eeShowBanners"
|
||||
*ngIf="showBanners"
|
||||
>
|
||||
<wp-custom-action
|
||||
*ngFor="let action of actions; trackBy: trackByHref"
|
||||
|
||||
+2
@@ -48,6 +48,8 @@ export class WpCustomActionsComponent extends UntilDestroyedMixin implements OnI
|
||||
|
||||
actions:CustomActionResource[] = [];
|
||||
|
||||
showBanners = this.bannersService.showBannerFor('custom_actions');
|
||||
|
||||
constructor(
|
||||
readonly apiV3Service:ApiV3Service,
|
||||
readonly cdRef:ChangeDetectorRef,
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ export class WpTableConfigurationColumnsTabComponent implements TabComponent, On
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.eeShowBanners = this.bannerService.eeShowBanners;
|
||||
this.eeShowBanners = this.bannerService.showBannerFor('work_package_query_relation_columns');
|
||||
this.selectedColumns.forEach((c:DraggableOption) => {
|
||||
this.selectedColumnMap[c.id] = true;
|
||||
});
|
||||
|
||||
+16
-9
@@ -1,16 +1,23 @@
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { TabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet';
|
||||
import { WorkPackageFiltersService } from 'core-app/features/work-packages/components/filters/wp-filters/wp-filters.service';
|
||||
import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service';
|
||||
import {
|
||||
TabComponent,
|
||||
} from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet';
|
||||
import {
|
||||
WorkPackageFiltersService,
|
||||
} from 'core-app/features/work-packages/components/filters/wp-filters/wp-filters.service';
|
||||
import {
|
||||
WorkPackageViewFiltersService,
|
||||
} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service';
|
||||
import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource';
|
||||
import { BannersService } from 'core-app/core/enterprise/banners.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './filters-tab.component.html',
|
||||
// eslint-disable-next-line @angular-eslint/component-selector
|
||||
selector: 'wp-table-config-filters-tab',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class WpTableConfigurationFiltersTab implements TabComponent {
|
||||
export class WpTableConfigurationFiltersTabComponent implements TabComponent, OnInit {
|
||||
public filters:QueryFilterInstanceResource[] = [];
|
||||
|
||||
public eeShowBanners = false;
|
||||
@@ -24,15 +31,15 @@ export class WpTableConfigurationFiltersTab implements TabComponent {
|
||||
upsaleRelationColumnsLink: this.I18n.t('js.modals.upsale_relation_columns_link'),
|
||||
};
|
||||
|
||||
constructor(readonly injector:Injector,
|
||||
constructor(
|
||||
readonly injector:Injector,
|
||||
readonly I18n:I18nService,
|
||||
readonly wpTableFilters:WorkPackageViewFiltersService,
|
||||
readonly wpFiltersService:WorkPackageFiltersService,
|
||||
readonly bannerService:BannersService) {
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.eeShowBanners = this.bannerService.eeShowBanners;
|
||||
this.wpTableFilters
|
||||
.onReady()
|
||||
.then(() => this.filters = this.wpTableFilters.current);
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ export class WpTableConfigurationHighlightingTabComponent implements TabComponen
|
||||
|
||||
this.setSelectedValues();
|
||||
|
||||
this.eeShowBanners = this.Banners.eeShowBanners;
|
||||
this.eeShowBanners = this.Banners.showBannerFor('conditional_highlighting');
|
||||
this.updateMode(this.wpTableHighlight.current.mode);
|
||||
|
||||
if (this.eeShowBanners) {
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { WpTableConfigurationDisplaySettingsTabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/display-settings-tab.component';
|
||||
import { TabInterface } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tab-portal-outlet';
|
||||
import { WpTableConfigurationColumnsTabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/columns-tab.component';
|
||||
import { WpTableConfigurationFiltersTab } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/filters-tab.component';
|
||||
import { WpTableConfigurationFiltersTabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/filters-tab.component';
|
||||
import { WpTableConfigurationSortByTabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component';
|
||||
import { WpTableConfigurationTimelinesTabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/timelines-tab.component';
|
||||
import { WpTableConfigurationHighlightingTabComponent } from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/highlighting-tab.component';
|
||||
@@ -21,7 +21,7 @@ export class WpTableConfigurationService {
|
||||
{
|
||||
id: 'filters',
|
||||
name: this.I18n.t('js.work_packages.query.filters'),
|
||||
componentClass: WpTableConfigurationFiltersTab,
|
||||
componentClass: WpTableConfigurationFiltersTabComponent,
|
||||
},
|
||||
{
|
||||
id: 'sort-by',
|
||||
|
||||
@@ -138,7 +138,7 @@ import {
|
||||
WpTableConfigurationDisplaySettingsTabComponent,
|
||||
} from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/display-settings-tab.component';
|
||||
import {
|
||||
WpTableConfigurationFiltersTab,
|
||||
WpTableConfigurationFiltersTabComponent,
|
||||
} from 'core-app/features/work-packages/components/wp-table/configuration-modal/tabs/filters-tab.component';
|
||||
import {
|
||||
WpTableConfigurationSortByTabComponent,
|
||||
@@ -604,7 +604,7 @@ import { OpWpDatePickerModalComponent } from 'core-app/shared/components/datepic
|
||||
WpTableConfigurationModalComponent,
|
||||
WpTableConfigurationColumnsTabComponent,
|
||||
WpTableConfigurationDisplaySettingsTabComponent,
|
||||
WpTableConfigurationFiltersTab,
|
||||
WpTableConfigurationFiltersTabComponent,
|
||||
WpTableConfigurationSortByTabComponent,
|
||||
WpTableConfigurationTimelinesTabComponent,
|
||||
WpTableConfigurationHighlightingTabComponent,
|
||||
@@ -661,7 +661,7 @@ import { OpWpDatePickerModalComponent } from 'core-app/shared/components/datepic
|
||||
|
||||
// Modals
|
||||
WpTableConfigurationModalComponent,
|
||||
WpTableConfigurationFiltersTab,
|
||||
WpTableConfigurationFiltersTabComponent,
|
||||
|
||||
// Needed so that e.g. IFC can access it.
|
||||
WorkPackageCreateButtonComponent,
|
||||
|
||||
+15
-8
@@ -6,14 +6,18 @@ import { BannersService } from 'core-app/core/enterprise/banners.service';
|
||||
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
|
||||
import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource';
|
||||
import { QuerySchemaResource } from 'core-app/features/hal/resources/query-schema-resource';
|
||||
import { WorkPackageViewHighlight } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-table-highlight';
|
||||
import {
|
||||
WorkPackageViewHighlight,
|
||||
} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-table-highlight';
|
||||
import { WorkPackageQueryStateService } from './wp-view-base.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkPackageViewHighlightingService extends WorkPackageQueryStateService<WorkPackageViewHighlight> {
|
||||
public constructor(readonly states:States,
|
||||
public constructor(
|
||||
readonly states:States,
|
||||
readonly Banners:BannersService,
|
||||
readonly querySpace:IsolatedQuerySpace) {
|
||||
readonly querySpace:IsolatedQuerySpace,
|
||||
) {
|
||||
super(querySpace);
|
||||
}
|
||||
|
||||
@@ -28,7 +32,7 @@ export class WorkPackageViewHighlightingService extends WorkPackageQueryStateSer
|
||||
*/
|
||||
public shouldHighlightInline(name:string):boolean {
|
||||
// 1. Are we in inline mode or unable to render?
|
||||
if (!this.isInline || this.Banners.eeShowBanners) {
|
||||
if (!this.isInline || this.Banners.showBannerFor('conditional_highlighting')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -82,10 +86,13 @@ export class WorkPackageViewHighlightingService extends WorkPackageQueryStateSer
|
||||
value.selectedAttributes = undefined;
|
||||
}
|
||||
|
||||
this.Banners.conditional(() => {
|
||||
value.mode = 'none';
|
||||
value.selectedAttributes = undefined;
|
||||
});
|
||||
void this.Banners.conditional(
|
||||
'conditional_highlighting',
|
||||
() => {
|
||||
value.mode = 'none';
|
||||
value.selectedAttributes = undefined;
|
||||
},
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class AddGridWidgetModalComponent extends OpModalComponent implements OnI
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.eeShowBanners = this.bannerService.eeShowBanners;
|
||||
this.eeShowBanners = this.bannerService.showBannerFor('grid_widget_wp_graph');
|
||||
this.fetchSchema();
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,11 @@ module API
|
||||
.map { |flag| flag.camelize(:lower) }
|
||||
}
|
||||
|
||||
property :available_features,
|
||||
getter: ->(*) {
|
||||
EnterpriseToken.current&.available_features || []
|
||||
}
|
||||
|
||||
property :allowed_link_protocols,
|
||||
getter: ->(*) { Setting::AllowedLinkProtocols.all }
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -82,13 +84,13 @@ module Redmine::MenuManager::TopMenu::HelpMenu
|
||||
controller.render_to_string(partial: "onboarding/menu_item")
|
||||
end
|
||||
|
||||
def render_help_and_support(result)
|
||||
def render_help_and_support(result) # rubocop:disable Metrics/AbcSize
|
||||
result << content_tag(:li, class: "op-menu--item") do
|
||||
content_tag :span, I18n.t("top_menu.help_and_support"),
|
||||
class: "op-menu--headline",
|
||||
title: I18n.t("top_menu.help_and_support")
|
||||
end
|
||||
if EnterpriseToken.show_banners?
|
||||
unless EnterpriseToken.hide_banners? && EnterpriseToken.active?
|
||||
result << static_link_item(:upsale,
|
||||
href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=ee-upsale-help-menu")
|
||||
end
|
||||
@@ -111,7 +113,7 @@ module Redmine::MenuManager::TopMenu::HelpMenu
|
||||
result << content_tag(:hr, "", class: "op-menu--separator")
|
||||
end
|
||||
|
||||
def render_additional_resources(result)
|
||||
def render_additional_resources(result) # rubocop:disable Metrics/AbcSize
|
||||
result << content_tag(:li, class: "op-menu--item") do
|
||||
content_tag :span,
|
||||
I18n.t("top_menu.additional_resources"),
|
||||
|
||||
@@ -51,9 +51,6 @@ module OpenProject
|
||||
# ```
|
||||
#
|
||||
# The href is inferred from `OpenProject::Static::Links.enterprise_docs[feature_key][:href]`.
|
||||
#
|
||||
# The value of `EnterpriseToken.show_banners?` is used to determine whether the banner should be shown. For this
|
||||
# example, that value is overwritten as the banner might otherwise not show up in the preview.
|
||||
def default
|
||||
render(
|
||||
::EnterpriseEdition::BannerComponent
|
||||
|
||||
@@ -81,7 +81,7 @@ module OpenProject::Plugins
|
||||
|
||||
def self.filtered_strategy?(_strategy_key, provider)
|
||||
name = provider[:name]&.to_s
|
||||
!EnterpriseToken.show_banners? || name == "developer"
|
||||
EnterpriseToken.allows_to?(:sso_auth_providers) || name == "developer"
|
||||
end
|
||||
|
||||
def self.strategy_key(strategy)
|
||||
|
||||
-1
@@ -59,7 +59,6 @@ RSpec.describe "Switching work package view",
|
||||
before do
|
||||
wp_1
|
||||
wp_2
|
||||
allow(EnterpriseToken).to receive(:show_banners?).and_return(false)
|
||||
|
||||
login_as(user)
|
||||
wp_page.visit!
|
||||
|
||||
@@ -36,7 +36,9 @@ module OpenProject::TeamPlanner
|
||||
author_url: "https://www.openproject.org",
|
||||
bundled: true,
|
||||
settings: {} do
|
||||
project_module :team_planner_view, dependencies: :work_package_tracking, enterprise_feature: true do
|
||||
project_module :team_planner_view,
|
||||
dependencies: :work_package_tracking,
|
||||
enterprise_feature: "team_planner_view" do
|
||||
permission :view_team_planner,
|
||||
{ "team_planner/team_planner": %i[index show upsale overview],
|
||||
"team_planner/menus": %i[show] },
|
||||
|
||||
@@ -67,9 +67,6 @@ RSpec.describe EnterpriseEdition::BannerComponent, type: :component do
|
||||
end
|
||||
|
||||
before do
|
||||
allow(EnterpriseToken)
|
||||
.to receive(:show_banners?)
|
||||
.and_return(ee_show_banners)
|
||||
allow(OpenProject::Static::Links)
|
||||
.to receive(:links)
|
||||
.and_return(static_links)
|
||||
@@ -175,24 +172,4 @@ RSpec.describe EnterpriseEdition::BannerComponent, type: :component do
|
||||
expect { render_component_in_mo }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
||||
context "if banners are hidden" do
|
||||
let(:ee_show_banners) { false }
|
||||
|
||||
it "hides the component" do
|
||||
render_component_in_mo
|
||||
|
||||
expect(page).to have_no_css ".op-ee-banner"
|
||||
end
|
||||
end
|
||||
|
||||
context "if banners are hidden but skip_render is overwritten" do
|
||||
let(:ee_show_banners) { false }
|
||||
let(:render_component) do
|
||||
render_inline(described_class.new(:some_enterprise_feature,
|
||||
skip_render: false))
|
||||
end
|
||||
|
||||
it_behaves_like "renders the component"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,7 +68,6 @@ RSpec.describe "Help menu items", :js do
|
||||
include_context "support links"
|
||||
|
||||
def fake_an_enterprise_token
|
||||
allow(EnterpriseToken).to receive(:show_banners?).and_return(false)
|
||||
allow(EnterpriseToken).to receive(:active?).and_return(true)
|
||||
end
|
||||
|
||||
|
||||
-1
@@ -46,7 +46,6 @@ RSpec.describe "Switching work package view on mobile", :js, :selenium do
|
||||
before do
|
||||
wp_1
|
||||
wp_2
|
||||
allow(EnterpriseToken).to receive(:show_banners?).and_return(false)
|
||||
|
||||
login_as(user)
|
||||
wp_table.visit!
|
||||
|
||||
@@ -53,7 +53,6 @@ RSpec.describe "Work Package highlighting fields",
|
||||
# Ensure Rails and Capybara caches are cleared
|
||||
Rails.cache.clear
|
||||
Capybara.reset!
|
||||
allow(EnterpriseToken).to receive(:show_banners?).and_return(false)
|
||||
login_as(user)
|
||||
wp_table.visit_query query
|
||||
wp_table.expect_work_package_listed wp_1, wp_2
|
||||
|
||||
@@ -245,6 +245,24 @@ RSpec.describe API::V3::Configuration::ConfigurationRepresenter do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "availableFeatures" do
|
||||
context "without any features" do
|
||||
it "is an empty array" do
|
||||
expect(subject)
|
||||
.to be_json_eql([].to_json)
|
||||
.at_path("availableFeatures")
|
||||
end
|
||||
end
|
||||
|
||||
context "with certain features allowed", with_ee: %i[some_value foobar] do
|
||||
it "is an array of strings of those flags" do
|
||||
expect(subject)
|
||||
.to be_json_eql(%w(some_value foobar).to_json)
|
||||
.at_path("availableFeatures")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "_embedded" do
|
||||
|
||||
@@ -1,10 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# 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 COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe EnterpriseToken do
|
||||
let(:object) { OpenProject::Token.new domain: Setting.host_name }
|
||||
let(:ee_manager_visible) { true }
|
||||
|
||||
subject { EnterpriseToken.new(encoded_token: "foo") }
|
||||
subject { described_class.new(encoded_token: "foo") }
|
||||
|
||||
before do
|
||||
RequestStore.delete :current_ee_token
|
||||
@@ -39,81 +69,20 @@ RSpec.describe EnterpriseToken do
|
||||
end
|
||||
end
|
||||
|
||||
describe ".show_banners?" do
|
||||
before do
|
||||
allow(described_class).to receive(:allows_to?).with(:active_feature).and_return(true)
|
||||
allow(described_class).to receive(:allows_to?).with(:inactive_feature).and_return(false)
|
||||
end
|
||||
|
||||
describe ".hide_banners?" do
|
||||
context "when ee manager is visible" do
|
||||
let(:ee_manager_visible) { true }
|
||||
|
||||
context "when token is active" do
|
||||
before { allow(described_class).to receive(:active?).and_return(true) }
|
||||
|
||||
it "returns false when requesting without a feature" do
|
||||
expect(described_class).not_to be_show_banners
|
||||
end
|
||||
|
||||
it "returns false when requesting with a feature that is enabled in the token" do
|
||||
expect(described_class).not_to be_show_banners(feature: :active_feature)
|
||||
end
|
||||
|
||||
it "returns true when requesting with a feature that is disabled in the token" do
|
||||
expect(described_class).to be_show_banners(feature: :inactive_feature)
|
||||
end
|
||||
end
|
||||
|
||||
context "when token is inactive" do
|
||||
before { allow(described_class).to receive(:active?).and_return(false) }
|
||||
|
||||
it "returns true when requesting without a feature" do
|
||||
expect(described_class).to be_show_banners
|
||||
end
|
||||
|
||||
it "returns true when requesting with a feature that is enabled in the token" do
|
||||
expect(described_class).to be_show_banners(feature: :active_feature)
|
||||
end
|
||||
|
||||
it "returns true when requesting with a feature that is disabled in the token" do
|
||||
expect(described_class).to be_show_banners(feature: :inactive_feature)
|
||||
end
|
||||
it "returns true" do
|
||||
expect(described_class).to be_show_banners
|
||||
end
|
||||
end
|
||||
|
||||
context "when ee manager is not visible" do
|
||||
let(:ee_manager_visible) { false }
|
||||
|
||||
context "when token is active" do
|
||||
before { allow(described_class).to receive(:active?).and_return(true) }
|
||||
|
||||
it "returns false when requesting without a feature" do
|
||||
expect(described_class).not_to be_show_banners
|
||||
end
|
||||
|
||||
it "returns false when requesting with a feature that is enabled in the token" do
|
||||
expect(described_class).not_to be_show_banners(feature: :active_feature)
|
||||
end
|
||||
|
||||
it "returns false when requesting with a feature that is disabled in the token" do
|
||||
expect(described_class).not_to be_show_banners(feature: :inactive_feature)
|
||||
end
|
||||
end
|
||||
|
||||
context "when token is inactive" do
|
||||
before { allow(described_class).to receive(:active?).and_return(false) }
|
||||
|
||||
it "returns false when requesting without a feature" do
|
||||
expect(described_class).not_to be_show_banners
|
||||
end
|
||||
|
||||
it "returns false when requesting with a feature that is enabled in the token" do
|
||||
expect(described_class).not_to be_show_banners(feature: :active_feature)
|
||||
end
|
||||
|
||||
it "returns false when requesting with a feature that is disabled in the token" do
|
||||
expect(described_class).not_to be_show_banners(feature: :inactive_feature)
|
||||
end
|
||||
it "returns false" do
|
||||
expect(described_class).not_to be_show_banners
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -157,39 +126,38 @@ RSpec.describe EnterpriseToken do
|
||||
|
||||
describe "existing token" do
|
||||
before do
|
||||
allow_any_instance_of(EnterpriseToken).to receive(:token_object).and_return(object)
|
||||
allow_any_instance_of(described_class).to receive(:token_object).and_return(object) # rubocop:disable RSpec/AnyInstance
|
||||
subject.save!(validate: false)
|
||||
end
|
||||
|
||||
context "when inner token is active" do
|
||||
it "has an active token" do
|
||||
expect(object).to receive(:expired?).and_return(false)
|
||||
expect(EnterpriseToken.count).to eq(1)
|
||||
expect(EnterpriseToken.current).to eq(subject)
|
||||
expect(EnterpriseToken.current.encoded_token).to eq("foo")
|
||||
expect(EnterpriseToken.show_banners?).to be(false)
|
||||
allow(object).to receive(:expired?).and_return(false)
|
||||
expect(described_class.count).to eq(1)
|
||||
expect(described_class.current).to eq(subject)
|
||||
expect(described_class.current.encoded_token).to eq("foo")
|
||||
|
||||
# Deleting it updates the current token
|
||||
EnterpriseToken.current.destroy!
|
||||
described_class.current.destroy!
|
||||
|
||||
expect(EnterpriseToken.count).to eq(0)
|
||||
expect(EnterpriseToken.current).to be_nil
|
||||
expect(described_class.count).to eq(0)
|
||||
expect(described_class.current).to be_nil
|
||||
end
|
||||
|
||||
it "delegates to the token object" do
|
||||
allow(object).to receive_messages(
|
||||
subscriber: "foo",
|
||||
mail: "bar",
|
||||
starts_at: Date.today,
|
||||
issued_at: Date.today,
|
||||
starts_at: Time.zone.today,
|
||||
issued_at: Time.zone.today,
|
||||
expires_at: "never",
|
||||
restrictions: { foo: :bar }
|
||||
)
|
||||
|
||||
expect(subject.subscriber).to eq("foo")
|
||||
expect(subject.mail).to eq("bar")
|
||||
expect(subject.starts_at).to eq(Date.today)
|
||||
expect(subject.issued_at).to eq(Date.today)
|
||||
expect(subject.starts_at).to eq(Time.zone.today)
|
||||
expect(subject.issued_at).to eq(Time.zone.today)
|
||||
expect(subject.expires_at).to eq("never")
|
||||
expect(subject.restrictions).to eq(foo: :bar)
|
||||
end
|
||||
@@ -198,38 +166,40 @@ RSpec.describe EnterpriseToken do
|
||||
let(:service_double) { Authorization::EnterpriseService.new(subject) }
|
||||
|
||||
before do
|
||||
expect(Authorization::EnterpriseService)
|
||||
.to receive(:new).twice.with(subject).and_return(service_double)
|
||||
allow(Authorization::EnterpriseService)
|
||||
.to receive(:new)
|
||||
.with(subject)
|
||||
.and_return(service_double)
|
||||
end
|
||||
|
||||
it "forwards to EnterpriseTokenService for checks" do
|
||||
expect(service_double)
|
||||
allow(service_double)
|
||||
.to receive(:call)
|
||||
.with(:forbidden_action)
|
||||
.and_return double("ServiceResult", result: false)
|
||||
expect(service_double)
|
||||
.and_return ServiceResult.success(result: true)
|
||||
allow(service_double)
|
||||
.to receive(:call)
|
||||
.with(:allowed_action)
|
||||
.and_return double("ServiceResult", result: true)
|
||||
.and_return ServiceResult.success(result: true)
|
||||
|
||||
expect(EnterpriseToken.allows_to?(:forbidden_action)).to be false
|
||||
expect(EnterpriseToken.allows_to?(:allowed_action)).to be true
|
||||
expect(described_class.allows_to?(:forbidden_action)).to be false
|
||||
expect(described_class.allows_to?(:allowed_action)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when inner token is expired" do
|
||||
before do
|
||||
expect(object).to receive(:expired?).and_return(true)
|
||||
allow(object).to receive(:expired?).and_return(true)
|
||||
end
|
||||
|
||||
it "has an expired token" do
|
||||
expect(EnterpriseToken.current).to eq(subject)
|
||||
expect(EnterpriseToken.show_banners?).to be(true)
|
||||
expect(described_class.current).to eq(subject)
|
||||
expect(described_class).not_to be_active
|
||||
end
|
||||
end
|
||||
|
||||
context "updating it with an invalid token" do
|
||||
context "when updating it with an invalid token" do
|
||||
it "fails validations" do
|
||||
subject.encoded_token = "bar"
|
||||
expect(subject.save).to be_falsey
|
||||
@@ -239,22 +209,22 @@ RSpec.describe EnterpriseToken do
|
||||
|
||||
describe "no token" do
|
||||
it do
|
||||
expect(EnterpriseToken.current).to be_nil
|
||||
expect(EnterpriseToken.show_banners?).to be(true)
|
||||
expect(described_class.current).to be_nil
|
||||
expect(described_class).not_to be_active
|
||||
end
|
||||
end
|
||||
|
||||
describe "invalid token" do
|
||||
it "appears as if no token is shown" do
|
||||
expect(EnterpriseToken.current).to be_nil
|
||||
expect(EnterpriseToken.show_banners?).to be(true)
|
||||
expect(described_class.current).to be_nil
|
||||
expect(described_class).not_to be_active
|
||||
end
|
||||
end
|
||||
|
||||
describe "Configuration file has `ee_manager_visible` set to false" do
|
||||
it "does not show banners promoting EE" do
|
||||
expect(OpenProject::Configuration).to receive(:ee_manager_visible?).and_return(false)
|
||||
expect(EnterpriseToken.show_banners?).to be_falsey
|
||||
allow(OpenProject::Configuration).to receive(:ee_manager_visible?).and_return(false)
|
||||
expect(described_class).to be_hide_banners
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Users::RegisterUserService do
|
||||
RSpec.describe Users::RegisterUserService, with_ee: %i[sso_auth_providers] do
|
||||
let(:user) { build(:user) }
|
||||
let(:instance) { described_class.new(user) }
|
||||
let(:call) { instance.call }
|
||||
@@ -83,9 +83,6 @@ RSpec.describe Users::RegisterUserService do
|
||||
before do
|
||||
allow(user).to receive(:activate)
|
||||
allow(user).to receive(:save).and_return true
|
||||
|
||||
# required so that the azure provider is visible (ee feature)
|
||||
allow(EnterpriseToken).to receive(:show_banners?).and_return false
|
||||
end
|
||||
|
||||
it "tries to activate that user regardless of settings" do
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -48,13 +50,22 @@ RSpec.configure do |config|
|
||||
if allowed.present?
|
||||
allowed = aggregate_parent_array(example, allowed.to_set)
|
||||
|
||||
token_double = instance_double(EnterpriseToken)
|
||||
token_object_double = instance_double(OpenProject::Token)
|
||||
allow(EnterpriseToken).to receive(:allows_to?).and_call_original
|
||||
allow(token_object_double).to receive(:has_feature?).and_return(false)
|
||||
allowed.each do |enterprise_feature|
|
||||
allow(EnterpriseToken).to receive(:allows_to?).with(enterprise_feature).and_return(true)
|
||||
allow(token_object_double).to receive(:has_feature?).with(enterprise_feature).and_return(true)
|
||||
end
|
||||
|
||||
# Also disable banners to signal the frontend we're on EE
|
||||
allow(EnterpriseToken).to receive(:show_banners?).and_return(allowed.empty?)
|
||||
# Also signal available features
|
||||
allow(EnterpriseToken).to receive(:current).and_return(token_double)
|
||||
allow(token_double)
|
||||
.to receive_messages(token_object: token_object_double,
|
||||
available_features: allowed.to_a,
|
||||
expired?: false,
|
||||
restrictions: {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user