Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAS-133894 / 25.04.0 / TrueNAS enterprise hint (by AlexKarpov98) #11661

Merged
merged 5 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<a
ixTest="use-enterprise-marketing-link"
target="_blank"
[href]="currentMessageHref()"
>
<span class="text-above">
{{ currentMessage() }}
</span>

<span class="text-below">
{{ 'Explore TrueNAS Enterprise' | translate }}
</span>
</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@import 'scss-imports/variables';

:host {
margin: 0 auto 5px;
}

a {
cursor: pointer;
display: block;
margin-bottom: 2px;
text-align: center;
text-decoration: none;

span {
color: var(--primary-txt);
display: block;

&.text-above {
color: var(--primary);
margin-bottom: 2px;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { UseEnterpriseMarketingLinkComponent } from './use-enterprise-marketing-link.component';

const lastShownDate = 'marketingMessageLastShownDate';
const lastMessageHash = 'marketingMessageLastHash';

describe('UseEnterpriseMarketingLinkComponent', () => {
let spectator: Spectator<UseEnterpriseMarketingLinkComponent>;

const createComponent = createComponentFactory({
component: UseEnterpriseMarketingLinkComponent,
});

beforeEach(() => {
spectator = createComponent();
jest.spyOn(global.Date.prototype, 'toDateString').mockReturnValue('2025-02-26');
localStorage.clear();
});

afterEach(() => {
jest.restoreAllMocks();
});

it('should display the first message by default', () => {
const message = spectator.component.currentMessage();
expect(message).toBe('Optimize Your Storage');
});

it('should rotate to the next message on a new day', () => {
localStorage.setItem(lastShownDate, '2025-02-25');
localStorage.setItem(lastMessageHash, spectator.component.hashMessage('Optimize Your Storage'));

const nextMessage = spectator.component.getTodaysMessage();
expect(nextMessage).toBe('More Performance, More Protection');
});

it('should loop to the first message after the last message', () => {
localStorage.setItem(lastShownDate, '2025-02-25');
localStorage.setItem(lastMessageHash, spectator.component.hashMessage('5 Nines of Uptime with HA'));

const nextMessage = spectator.component.getTodaysMessage();
expect(nextMessage).toBe('Optimize Your Storage');
});

it('should update localStorage with new date and hash', () => {
spectator.component.getTodaysMessage();

expect(localStorage.getItem(lastShownDate)).toBe('2025-02-26');
expect(localStorage.getItem(lastMessageHash)).toBe(spectator.component.hashMessage('Optimize Your Storage'));
});

it('should maintain consistent message even if array order changes', () => {
const originalHash = spectator.component.hashMessage('Boost Performance & Support');
localStorage.setItem('marketingMessageLastShownDate', '2025-02-25');
localStorage.setItem('marketingMessageLastHash', originalHash);

spectator.component.messages = [
'Boost Performance & Support',
'Optimize Your Storage',
'More Performance, More Protection',
];

const nextMessage = spectator.component.getTodaysMessage();
expect(nextMessage).toBe('Optimize Your Storage');
});

it('should return the first message if hash is not found', () => {
localStorage.setItem(lastShownDate, '2025-02-26');
localStorage.setItem(lastMessageHash, 'unknownHash');

const currentMessage = spectator.component.getTodaysMessage();
expect(currentMessage).toBe('Optimize Your Storage');
});

it('should not change message within the same day', () => {
localStorage.setItem(lastShownDate, '2025-02-26');
localStorage.setItem(lastMessageHash, spectator.component.hashMessage('Optimize Your Storage'));

const currentMessage = spectator.component.getTodaysMessage();
expect(currentMessage).toBe('Optimize Your Storage');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
Component, ChangeDetectionStrategy, computed,
} from '@angular/core';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { TestDirective } from 'app/modules/test-id/test.directive';

@Component({
selector: 'ix-use-enterprise-marketing-link',
templateUrl: './use-enterprise-marketing-link.component.html',
styleUrls: ['./use-enterprise-marketing-link.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
TestDirective,
TranslateModule,
],
})
export class UseEnterpriseMarketingLinkComponent {
protected readonly targetUrl = 'https://truenas.com/explore-truenas-enterprise/';

messages = [
this.translate.instant('Optimize Your Storage'),
this.translate.instant('More Performance, More Protection'),
this.translate.instant('Boost Performance & Support'),
this.translate.instant('Unlock High Performance Solutions'),
this.translate.instant('Expert Support When You Need It'),
this.translate.instant('5 Nines of Uptime with HA'),
];

currentMessage = computed(() => this.getTodaysMessage());
currentMessageHref = computed(() => `${this.targetUrl}?m=${this.hashMessage(this.currentMessage())}`);

constructor(
private translate: TranslateService,
) {}

getTodaysMessage(): string {
const today = new Date().toDateString();
const lastShownDate = localStorage.getItem('marketingMessageLastShownDate');
const lastMessageHash = localStorage.getItem('marketingMessageLastHash');

if (lastShownDate !== today) {
const nextMessage = this.getNextMessage(lastMessageHash);

localStorage.setItem('marketingMessageLastShownDate', today);
localStorage.setItem('marketingMessageLastHash', this.hashMessage(nextMessage));

return nextMessage;
}

return this.getCurrentMessage(lastMessageHash);
}

getNextMessage(lastMessageHash: string | null): string {
const lastIndex = this.messages.findIndex((message) => this.hashMessage(message) === lastMessageHash);
const nextIndex = lastIndex >= 0 ? (lastIndex + 1) % this.messages.length : 0;
return this.messages[nextIndex];
}

getCurrentMessage(lastMessageHash: string | null): string {
const currentIndex = this.messages.findIndex((message) => this.hashMessage(message) === lastMessageHash);
return currentIndex >= 0 ? this.messages[currentIndex] : this.messages[0];
}

hashMessage(message: string): string {
return btoa(encodeURIComponent(message));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ <h3>{{ 'System Information' | translate }}</h3>
></ngx-skeleton-loader>
}
</mat-list-item>

@if (!isEnterprise()) {
<mat-list-item class="use-enterprise">
<ix-use-enterprise-marketing-link></ix-use-enterprise-marketing-link>
</mat-list-item>
}
</mat-list>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
}
}
}

.use-enterprise {
height: calc(var(--mdc-list-list-item-one-line-container-height, 48px) * 2);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { IxIconComponent } from 'app/modules/ix-icon/ix-icon.component';
import { selectUpdateJobForActiveNode } from 'app/modules/jobs/store/job.selectors';
import { LocaleService } from 'app/modules/language/locale.service';
import { TestDirective } from 'app/modules/test-id/test.directive';
import { UseEnterpriseMarketingLinkComponent } from 'app/modules/use-enterprise-marketing-link/use-enterprise-marketing-link.component';
import { WidgetResourcesService } from 'app/pages/dashboard/services/widget-resources.service';
import { SlotSize } from 'app/pages/dashboard/types/widget.interface';
import { ProductImageComponent } from 'app/pages/dashboard/widgets/system/common/product-image/product-image.component';
Expand Down Expand Up @@ -52,6 +53,7 @@ import {
CopyButtonComponent,
TranslateModule,
UptimePipe,
UseEnterpriseMarketingLinkComponent,
],
})
export class WidgetSysInfoActiveComponent {
Expand Down
7 changes: 7 additions & 0 deletions src/assets/i18n/af.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"3 weeks ago": "",
"4 days ago": "",
"4 months ago": "",
"5 Nines of Uptime with HA": "",
"5 days ago": "",
"5 months ago": "",
"6 months": "",
Expand Down Expand Up @@ -630,6 +631,7 @@
"Block I/O read and writes": "",
"Block Size": "",
"Block size": "",
"Boost Performance & Support": "",
"Boot": "",
"Boot Environment": "",
"Boot Environment Read": "",
Expand Down Expand Up @@ -1835,9 +1837,11 @@
"Expand pool to fit all available disk space.": "",
"Expander Status": "",
"Experimental": "",
"Expert Support When You Need It": "",
"Expiration Date": "",
"Expires On": "",
"Expires on {date}": "",
"Explore TrueNAS Enterprise": "",
"Export": "",
"Export All Keys": "",
"Export As {fileType}": "",
Expand Down Expand Up @@ -2755,6 +2759,7 @@
"Month(s)": "",
"Months": "",
"More Options": "",
"More Performance, More Protection": "",
"More info...": "",
"Move all items to the left side list": "",
"Move all items to the right side list": "",
Expand Down Expand Up @@ -3096,6 +3101,7 @@
"Opened at": "",
"Operation": "",
"Operation will change permissions on path: {path}": "",
"Optimize Your Storage": "",
"Optional IP": "",
"Optional IP of 2nd Redfish management interface.": "",
"Optional description. Portals are automatically assigned a numeric group.": "",
Expand Down Expand Up @@ -4775,6 +4781,7 @@
"Unlock": "",
"Unlock Child Encrypted Roots": "",
"Unlock Datasets": "",
"Unlock High Performance Solutions": "",
"Unlock Pool": "",
"Unlock with Key file": "",
"Unlocked": "",
Expand Down
7 changes: 7 additions & 0 deletions src/assets/i18n/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"3 weeks ago": "",
"4 days ago": "",
"4 months ago": "",
"5 Nines of Uptime with HA": "",
"5 days ago": "",
"5 months ago": "",
"6 months": "",
Expand Down Expand Up @@ -630,6 +631,7 @@
"Block I/O read and writes": "",
"Block Size": "",
"Block size": "",
"Boost Performance & Support": "",
"Boot": "",
"Boot Environment": "",
"Boot Environment Read": "",
Expand Down Expand Up @@ -1835,9 +1837,11 @@
"Expand pool to fit all available disk space.": "",
"Expander Status": "",
"Experimental": "",
"Expert Support When You Need It": "",
"Expiration Date": "",
"Expires On": "",
"Expires on {date}": "",
"Explore TrueNAS Enterprise": "",
"Export": "",
"Export All Keys": "",
"Export As {fileType}": "",
Expand Down Expand Up @@ -2755,6 +2759,7 @@
"Month(s)": "",
"Months": "",
"More Options": "",
"More Performance, More Protection": "",
"More info...": "",
"Move all items to the left side list": "",
"Move all items to the right side list": "",
Expand Down Expand Up @@ -3096,6 +3101,7 @@
"Opened at": "",
"Operation": "",
"Operation will change permissions on path: {path}": "",
"Optimize Your Storage": "",
"Optional IP": "",
"Optional IP of 2nd Redfish management interface.": "",
"Optional description. Portals are automatically assigned a numeric group.": "",
Expand Down Expand Up @@ -4775,6 +4781,7 @@
"Unlock": "",
"Unlock Child Encrypted Roots": "",
"Unlock Datasets": "",
"Unlock High Performance Solutions": "",
"Unlock Pool": "",
"Unlock with Key file": "",
"Unlocked": "",
Expand Down
7 changes: 7 additions & 0 deletions src/assets/i18n/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"3 weeks ago": "",
"4 days ago": "",
"4 months ago": "",
"5 Nines of Uptime with HA": "",
"5 days ago": "",
"5 months ago": "",
"6 months": "",
Expand Down Expand Up @@ -630,6 +631,7 @@
"Block I/O read and writes": "",
"Block Size": "",
"Block size": "",
"Boost Performance & Support": "",
"Boot": "",
"Boot Environment": "",
"Boot Environment Read": "",
Expand Down Expand Up @@ -1835,9 +1837,11 @@
"Expand pool to fit all available disk space.": "",
"Expander Status": "",
"Experimental": "",
"Expert Support When You Need It": "",
"Expiration Date": "",
"Expires On": "",
"Expires on {date}": "",
"Explore TrueNAS Enterprise": "",
"Export": "",
"Export All Keys": "",
"Export As {fileType}": "",
Expand Down Expand Up @@ -2755,6 +2759,7 @@
"Month(s)": "",
"Months": "",
"More Options": "",
"More Performance, More Protection": "",
"More info...": "",
"Move all items to the left side list": "",
"Move all items to the right side list": "",
Expand Down Expand Up @@ -3096,6 +3101,7 @@
"Opened at": "",
"Operation": "",
"Operation will change permissions on path: {path}": "",
"Optimize Your Storage": "",
"Optional IP": "",
"Optional IP of 2nd Redfish management interface.": "",
"Optional description. Portals are automatically assigned a numeric group.": "",
Expand Down Expand Up @@ -4775,6 +4781,7 @@
"Unlock": "",
"Unlock Child Encrypted Roots": "",
"Unlock Datasets": "",
"Unlock High Performance Solutions": "",
"Unlock Pool": "",
"Unlock with Key file": "",
"Unlocked": "",
Expand Down
Loading
Loading