Add additional property to products field call Spartacus - spartacus-storefront

I need to add an additional url parameter (which is dynamic and not fixed) to the fields calls that happen on the PDP page. I have tried extending the the product service but that doesnt fire any of my overide functions.
I have now ended up implementing the product adapter so I just want to confirm this is 100% correct.
export class MyProductAdapter implements ProductAdapter {

if you want to add a (fixed) value in your fields call you can override the default call and add your missing value. Create a file yourOccProductDetails.config.ts
export const yourOccProductDetailsConfig: OccConfig = {
backend: {
occ: {
endpoints: {
product: {
details: 'products/${productCode}?fields=averageRating,stock(DEFAULT),description,availableForPickup,code,url,price(DEFAULT),numberOfReviews,manufacturer,categories(FULL),priceRange,multidimensional,tags,images(FULL),yourParam',
},
},
},
},
},
And in your module add the config to your providers array
import { yourOccProductDetailsConfig } from './yourOccProductDetails.config'
#NgModule({
imports: [...],
declarations: [YourProductDetailsComponent],
exports: [YourProductDetailsComponent],
providers: [provideConfig(yourOccProductDetailsConfig)],
})
export class YourProductDetailsModule {}

Here is the documentation part for a problem you have faced:
https://sap.github.io/spartacus-docs/connecting-to-other-systems/#configuring-endpoints

Related

How can I dynamically inject different providers with Nest.js?

I'm facing the following issue. I have a service used by a controller. The service (in the snippets below QueueService) injects a provider imported from a package. I aim to reuse the QueueService across the controller methods, but I also need to dynamically specify which provider QueueService should use.
My question is, how can I achieve this behaviour?
import { PubsubService } from '#myorg/queue'
#Module({
imports: [
ConfigModule.forRoot({
SHARED_RESOURCES_PROJECT_ID: Joi.string().required()
})
})
],
controllers: [AppController],
providers: [
{
provide: 'PUBSUB',
useValue: new PubsubService()
},
{
provide: 'INTEGRATION_PUBSUB',
useValue: new PubsubService({ projectId: process.env.SHARED_RESOURCES_PROJECT_ID })
}
]
})
export class AppModule {}
#Controller()
export class AppController {
constructor(private queueService: QueueService) {}
#Post()
async create() {
...
// here I want to use queueService with `PUBSUB` injected
return this.queueService.sendMessage(...)
}
#Patch()
async update() {
...
// here I want to use queueService with `INTEGRATION_PUBSUB` injected
return this.queueService.sendMessage(...)
}
}
#Injectable()
export class QueueService {
constructor(
// how can I dynamically change `#Inject('PUBSUB')` to `#Inject('INTEGRATION_PUBSUB')`?
#Inject('PUBSUB') private readonly pubsubService: PubsubService
) {}
async sendMessage(payload): Promise<void> {
return this.pubsubService.sendMessage(payload)
}
}
dynamic inject is not possible after object(in this case controller) created . so you have two option
1- create two QueueService (one for PUBSUB and another for INTEGRATION_PUBSUB) and inject both to controller. use those in your controller functions. (i recommend this)
2- inject both PUBSUB and INTEGRATION_PUBSUB into QueueService and pass another param in sendMessage function . so check this param to choose between PUBSUB and INTEGRATION_PUBSUB

How to test dynamic modules in Nest.js?

My implementation is based on this article: https://dev.to/nestjs/advanced-nestjs-how-to-build-completely-dynamic-nestjs-modules-1370
I want to test my generic, Twilio-based SMS sender service that I share between multiple parts of my application. I want to configure it when I'm importing it from somewhere else, so I'm writing it as a dynamic module. On top of that, the options that I pass to the dynamic module are themselves constructed dynamically, they are read from my .env file. I'm using the factory pattern when I'm registering my provider:
// app.module.ts
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [
'.env',
],
validationSchema,
}),
SharedSmsModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService<EnvironmentVariables>) => {
return {
accountSid: configService.get('TWILIO_ACCOUNT_SID'),
authToken: configService.get('TWILIO_AUTH_TOKEN'),
smsSenderPhoneNumber: configService.get(
'TWILIO_SMS_SENDER_PHONE_NUMBER'
),
};
},
}),
],
})
export class AppModule {}
My shared-sms module calls the function provided in the registerAsync method in app.module.ts:
// shared-sms.module.ts
export interface SharedSmsModuleOptions {
accountSid: string;
authToken: string;
smsSenderPhoneNumber: string;
}
export interface SharedSmsModuleAsyncOptions extends ModuleMetadata {
imports: any[];
inject: any[];
useFactory?: (
...args: any[]
) => Promise<SharedSmsModuleOptions> | SharedSmsModuleOptions;
}
#Module({})
export class SharedSmsModule {
static registerAsync(
sharedSmsModuleAsyncOptions: SharedSmsModuleAsyncOptions
): DynamicModule {
return {
global: true,
module: SharedSmsModule,
imports: sharedSmsModuleAsyncOptions.imports,
providers: [
{
provide: 'SHARED_SMS_OPTIONS',
useFactory: sharedSmsModuleAsyncOptions.useFactory,
inject: sharedSmsModuleAsyncOptions.inject || [],
},
SharedSmsService,
],
exports: [SharedSmsService],
};
}
}
Now I have access to the options variables in my shared-sms.service:
// shared-sms.service
#Injectable()
export class SharedSmsService {
private twilioClient: Twilio;
constructor(
#Inject('SHARED_SMS_OPTIONS') private options: SharedSmsModuleOptions
) {
this.twilioClient = new Twilio(
this.options.accountSid,
this.options.authToken
);
}
async sendSms(sendSmsDto: SendSmsDto): Promise<MessageInstance> {
await validateOrReject(plainToInstance(SendSmsDto, sendSmsDto));
const smsData = {
from: this.options.smsSenderPhoneNumber,
to: sendSmsDto.to,
body: sendSmsDto.body,
};
return await this.twilioClient.messages.create(smsData);
}
}
So long everything seems to be working. But I'm having issues when I'm trying to test the service's sendSms function. I can write tests that work when I'm providing hardcoded Twilio test account values in my test file. But I don't want to commit them to the repository, so I would want to get them from my .env file. I have tried providing everything to the Test.createTestingModule function when I'm creating my moduleRef, based on what I did in the code that I already wrote, but I couldn't specify the Twilio test account values dynamically. As I don't see documentation regarding this issue, I feel like that I'm either missing a conceptual point (providing so many things in the test seems like an overkill) or there is a trivial work-around. Please help me figure out how to pass those values to my tests from my .env file

Spartacus : Access Occ url on the NgModule inside Spartacus.configuration.module.ts file

I have experience on Jquery but Angular is very new to me and so is spartacus. I have been trying to configure our Spartacus to be used across multiple environments. The only solution that I could find was to use the meta tag on index.html and then grab the config based on url as mentioned by janwidmer and CarlEricLavoie. I did make a slight change from these 2 solutions though, and that was to use the environment.{env}.ts files instead of a new Backend service or keeping the config in a map in the same file.
The issue that I am facing is that the spartacus.cofniguration.module.ts file needs these values in #NgModule and I've tried everything that I could think of, but am unable to use the value of this.config.backend?.occ?.baseUrl inside the NgModule, where I need to give the value for base URL and Base site.
Below is what I am trying to do. i have written the logic that would expose the correct config object according to the OCC base url that I receive on the environment. Now over here, the console log inside the constructor prints a proper object with all the properties I need, but then the conf object right before the #NgModule, it prints undefined and the values inside the NgModule are undefined as well then.
I also tried to create a new class and import it here so I could maybe create an instance, but couldn't do that as well, as it would then need the variable/method to be static for me to be able to access it inside NgModule.
import { Injectable, NgModule } from '#angular/core';
import { FacetChangedEvent, FeaturesConfig, I18nConfig, OccConfig, provideConfig, SiteContextConfig } from "#spartacus/core";
import { environment } from '../../environments/environment';
import { defaultB2bCheckoutConfig, defaultB2bOccConfig } from "#spartacus/setup";
import { defaultCmsContentProviders, layoutConfig, mediaConfig } from "#spartacus/storefront";
import { translations } from 'src/assets/i18n-translations/translations';
import { translationChunksConfig } from 'src/assets/i18n-translations/translation-chunks-config';
import { CdcConfig, CdcRootModule, CDC_FEATURE } from '#spartacus/cdc/root';
import { EnvironmentConfigurationModule } from "../../environments/environment-configuration.module"
import { environment as envdev} from '../../environments/environment';
import { environment as envstag} from '../../environments/environment.stage';
import { environment as envprod} from '../../environments/environment.prod';
let conf : any;
console.log("before ng");
console.log(conf);
#NgModule({
declarations: [],
imports: [
],
providers: [provideConfig(layoutConfig), provideConfig(mediaConfig), ...defaultCmsContentProviders, provideConfig(<OccConfig><unknown>{
backend: {
occ: {
baseUrl: environment.baseUrl,
}
},
}), provideConfig(<SiteContextConfig>{
context: {
urlParameters: ['baseSite', 'language', 'currency'],
baseSite: [environment?.baseSite],
currency: ['USD', 'GBP',]
},
}),
provideConfig(<I18nConfig>{
i18n: {
backend:{
loadPath:'assets/i18n-translations/{{lng}}/{{ns}}.ts',
},
resources: translations,
chunks: translationChunksConfig
,fallbackLang: 'en'
},
}
), provideConfig(<FeaturesConfig>{
features: {
level: '4.2'
}
}),
provideConfig(defaultB2bOccConfig), provideConfig(defaultB2bCheckoutConfig),
]
})
export class SpartacusConfigurationModule {
urlValue : string | undefined;
env: any;
constructor(private config: OccConfig) {
this.urlValue = this.config.backend?.occ?.baseUrl;
console.log("baseurl : " + this.config.backend?.occ?.baseUrl);
if(this.urlValue?.includes('s1'))
{
this.env=envstag;
}
else if(this.urlValue?.includes('p1'))
{
this.env=envprod;
}
else{
this.env=envdev;
}
conf = this.env;
console.log("conf");
console.log(conf);
}
getConfig() {
return conf;
}
}
Apart from these solutions, we have also tried to use window.location and location.href to find the base url and work based on that. This works amazing on local, but as soon as you deploy it to the server, it says that the reference window not found/reference location not found. We tried to do this right before the NgModule, inside spartacus-configuration.module.ts
import { environment as envDev } from "../../environments/environment";
import { environment as envStage } from "../../environments/environment.stage";
import { environment as envProd } from "../../environments/environment.prod";
let loc=location.hostname;
let env;
if(loc.includes('s1'))
{
env=envStage;
}
else if(loc.includes('p1'))
{
env=envProd;
}
else{
env=envDev;
}
console.log("before ng===>>>",loc);
With environment imports, your standard build configuration will replace the environment.ts variables with the ones set by your server's environment (eg. process.env) before deployment. Therefore, you should only import from environment.ts in your code and let the server handle overriding the staging environment variables.
With location and window objects, they are not accessible on the server-side of the build because Angular Universal initially delivers a pre-rendered page readable by bots (usually html-only) for SEO purposes. The location and window objects do not exist in this instance. In Angular, window and location objects should be imported into your classes.
Use Window: https://stackoverflow.com/a/52620181/12566149
Use Location: https://stackoverflow.com/a/43093554/12566149

How to add forms to test in angular 5

I created an app in angular 5. Now I want to add some tests.
At the moment almost all the specs are failing even though I didnt even add anything.
For my first component it says:
Can't bind to 'formGroup' since it isn't a known property of 'form'.
How do i inject the necessary dependencys in the component?
Right now I have this:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ],
providers: [{
provide: Router,
useClass: class { navigate = jasmine.createSpy("navigate"); }
}, AuthentificationService, NotificationService, FormBuilder]
})
.compileComponents();
}));
This is the constructor of the component:
constructor(private router: Router, private auth: AuthentificationService, private ns: NotificationService, private fb: FormBuilder) {}
At the moment almost all the specs are failing even though I didnt even add anything.
Seems like you are using something like this or this.
For my first component it says: Can't bind to 'formGroup' since it isn't a known property of 'form'.
Something like this should take care of this particular error, note the imports-statement after declarations:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ],
imports: [ReactiveFormsModule],
providers: [{
provide: Router,
useClass: class { navigate = jasmine.createSpy("navigate"); }
}, AuthentificationService, NotificationService, FormBuilder]
})
.compileComponents();
}));
You'll of course have to add it to the imports at the file level as well but it seems you got those details already.

How does routing work with a feature in Aurelia?

Let's say I want to use Aurelia's feature... feature, to make a "user" feature to manage users, and I have a different view for the various crud operations. How do I organize the routing for create read update delete? is that done in the feature? is it done in the app? some combination thereof? please provide an example.
One does not simply add routes through a feature.
However, if you are building a larger application, you can use dependency injection container to expose a "route" object to your application, which features can leverage and extend.
main.js
import { MyRouteConfig } from 'myRouteConfig';
#inject(MyRouteConfig)
export class App {
constructor(config) {
this.routes = config.routes;
}
configureRouter(config, router) {
config.map(this.routes);
}
}
A feature is pulled in through a configure function which is passed the Aurelia object. You can use the Aurelia object DI container to get an instance of your MyRouteConfig object;
feature/index.js
import { MyRouteConfig } from '../myRouteConfig';
export function configure(aurelia) {
let config = aurelia.container.get(MyRouteConfig);
Array.prototype.push.apply(config.routes, [
{ route: 'feature', moduleId: 'feature/home' },
{ route: 'feature/edit', moduleId: 'feature/edit' }
])
}
However, this is not a best practice at all. It is at best a clever use of the tools and at worst a hack. There are lots of pitfalls to this approach, including difficulty loading the correct module paths and overlapping routes, and you should only use this strategy if you really know what you are doing. That said, it can be done.
What you can do is this:
feature/index.js
import { FrameworkConfiguration } from 'aurelia-framework';
import { Router, RouteConfig } from 'aurelia-router';
export function configure(config: FrameworkConfiguration) {
var router = config.aurelia.container.get(Router) as Router;
let newRoute: RouteConfig = {
route: 'login',
name: 'login',
moduleId: 'login/login',
nav: true,
title: 'Login'
};
router.addRoute(newRoute);
}