I'm using react-ga4.
I wonder how i could send user properties using this library and set it up in the google analytics panel as I think that i'm doing something wrong.
This is how i initialize ReactGA4
ReactGA.initialize(
[
{
trackingId: id,
gaOptions: {
role: userRole,
}
},
]
)
any suggestion?
It depends what user properties that you want to send. You can send custom user properties that not reserved by Google.
For example, I want to send account_verified with boolean value and name with string value in user property.
You can use ReactGA.gtag(...args) in this library, and then you can use it directly or put it into utils/analytics.js and wrap it to export function with parameter, so you can use it whenever it needs.
import ReactGA from "react-ga4";
ReactGA.gtag("set", "user_properties", {
account_verified: true,
});
ReactGA.gtag("set", "user_properties", {
name: "John",
});
or
import ReactGA from "react-ga4";
export const setAccountProperty = (value: boolean) => {
ReactGA.gtag("set", "user_properties", {
account_verified: value,
});
};
export const setNameProperty = (value: string) => {
ReactGA.gtag("set", "user_properties", {
name: value,
});
};
After that, you can check in your Google Analytics in DebugView directly to ensure your user properties works well.
Update
The accepted answer bellow by Victor is the correct workaround.
I get in contact with AWS Support that confirmed the bug. They are aware of the issue and linked me to those 2 links to follow the case.
... During my investigation and replication of the issue, I noticed that this issue seems to be with how CDK handles the VPCEndpoint resource as its parameters include a list of DNS entries with them.
This is a known issue with the resource:
https://github.com/aws/aws-cdk/issues/5897
https://github.com/aws/aws-cdk/issues/9488
Until then if you face this problem, the solution is here.
Original question
I was expecting to be able to use Fn.select and Fn.split out of the box, but the bellow code have some very weird behavior, wondering if I'm doing something unexpected.
I expected the output to contains the declared functions fn::Select [1, fn:split [ fn:select [0, getAttr [ something ] ] ] ]
But actually what I see is just getAttr [something], what leads to Value of property Parameters must be an object with String (or simple type) properties during deployment
Any help is appreciated
Here's the code to reproduce the error
#!/usr/bin/env node
import 'source-map-support/register';
import {App, aws_ec2, aws_lambda, Fn, NestedStack, NestedStackProps, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
const app = new App();
export class MainStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
new Nested1(this, 'first', {})
}
}
export class Nested1 extends NestedStack {
constructor(scope: Construct, id: string, props: NestedStackProps) {
super(scope, id, props);
const vpc = new aws_ec2.Vpc(this, 'vpc', {
subnetConfiguration: [{
cidrMask: 28,
name: 'private-test-bug',
subnetType: aws_ec2.SubnetType.PRIVATE_ISOLATED,
}]
})
const apiEndpoint = new aws_ec2.InterfaceVpcEndpoint(this, `apiEndpoint`, {
service: {
name: 'com.amazonaws.eu-central-1.execute-api',
port: 443,
},
vpc: vpc,
subnets: {
subnets: vpc.isolatedSubnets,
onePerAz: true
},
open: true,
privateDnsEnabled: false,
});
// BUG IS HERE
new Nested2(this, 'nested2', { VpcEndpoint: apiEndpoint }) // CDK should handle and parse the reference, but it dont
}
}
export interface Nested2StackProps extends NestedStackProps { VpcEndpoint: aws_ec2.InterfaceVpcEndpoint; }
export class Nested2 extends NestedStack {
constructor(scope: Construct, id: string, props: Nested2StackProps) {
super(scope, id, props);
const lambda = new aws_lambda.Function(this, 'lambda-function', {
code: aws_lambda.Code.fromInline(`exports.handler = (event, context) => { console.log(process.env.dns_name) }`),
handler: 'index.handler',
runtime: aws_lambda.Runtime.NODEJS_16_X,
environment: { dns_name: Fn.select(1, Fn.split(':', Fn.select(0, props.VpcEndpoint.vpcEndpointDnsEntries))) } // USAGE IS HERE
})
}
}
new MainStack (app, 'TestStack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
In a nutshell, it looks like a bug in the CDK. The CDK tries to send an array as a parameter to the nested template. Instead, it should join the array and pass it as a string.
I suggest a workaround for the issue. I split my code into two parts. First, I explain the idea and show some code snippets. Finally, I show the complete solution.
We define parameter in the nested stack like this:
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
In the code, we create an object for this parameter and use it to get the parameter value.
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
// Read the value:
const strings = endpoints.valueAsList
In the final step, we pass parameters to the nested stack almost as usual, except that we join the string.
createNestedStack(stack, 'NestedStack',
{ VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
Note that the lambda function is not working, it throws a runtime exception. Otherwise, the stack and nested stack deploys normally.
Please find the complete code below.
Sorry, I am really in a hurry now. I plan to fix grammar and update the answer this evening.
import { App, CfnParameter, Fn, NestedStack, Stack } from 'aws-cdk-lib'
import { env } from 'process'
import { InterfaceVpcEndpoint, Vpc } from 'aws-cdk-lib/aws-ec2'
import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda'
function createMainStack (scope, id, props) {
const stack = new Stack(scope, id, props)
const vpc = Vpc.fromLookup(stack, 'Vpc', { vpcName: 'WarehousePrinters' })
const apiEndpoint = new InterfaceVpcEndpoint(stack, `ApiEndpoint`, {
service: {
name: 'com.amazonaws.eu-west-1.execute-api',
port: 443,
},
vpc,
subnets: { subnets: vpc.privateSubnets, onePerAz: true },
open: true,
privateDnsEnabled: false,
})
createNestedStack(stack, 'NestedStack', { VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
return stack
}
function createNestedStack (scope, id, props) {
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
new Function(stack, 'LambdaFunction', {
code: Code.fromInline(`export function handler = () => console.log(env.dns_name)`),
handler: 'index.handler',
runtime: Runtime.NODEJS_16_X,
environment: {
dns_name: Fn.select(1, Fn.split(':', Fn.select(0, endpoints.valueAsList)))
}
})
return stack
}
const app = new App()
createMainStack(app, 'MainStack', {
env: { account: env.CDK_DEFAULT_ACCOUNT, region: env.CDK_DEFAULT_REGION }
})
I'm using the AWS CDK. When I deploy, according to CloudFormation Events in the AWS console, resources(CfnTrigger) in createWorkflow are initialized before createDDBCrawler and createS3Crawler. Which is causing create_failed(Entity not found) since createWorkflow depends on resources in these two.
So I am wondering:
AWS CDK resource generating sequence (since I see no async or promise in functions, so TypeScript is handling the code in sequence. Then it's a CDK/CloudFormation behavior?)
How to avoid this or arrange resource create sequence, except creating two stack.
export class QuicksightGlue extends Construct {
constructor(scope: Construct, name: string, props: QuicksightGlueProps) {
super(scope, name);
this.createGlueDb(props.glueDb);
for (let zone of zones) {
const ddbCrawler = this.createDDBCrawler(...);
const etlJob = this.createEtlJob(...);
// crawler for processed data
this.createS3Crawler(...);
// create workflow that use crawler
this.createWorkflow(...);
}
}
private createS3Crawler(...) {
return new glue.CfnCrawler(this, 's3Crawler_' + zone, {
name: 's3Crawler_' + zone,
databaseName: glueDb,
role: roleArn,
targets: {
s3Targets: [{ path: s3 }]
}
});
}
private createWorkflow(...) {
const extracDdbWorkflow = new glue.CfnWorkflow(this, `ExtractDdb_` + zone, {
name: `udhcpExtractDdb_` + zone.toLowerCase(),
description: "Workflow to extract and process data from DDB"
});
const scheduledTriggerDdbCrawler = new glue.CfnTrigger(this, 'DdbTrigger_' + zone, {
workflowName: extracDdbWorkflow.name,
type: "SCHEDULED",
schedule: scheduleCronExpression, //"cron(0 * * * ? *)",
description: "Trigger to start the workflow every hour to update ddb data",
actions: [{
crawlerName: ddbCrawler,
}],
});
...
You can cause a construct to become dependent on another construct by calling addDependency on the construct's node property, like this:
// Normally these two constructs would be created in parallel
const construct1 = ...;
const construct2 = ...;
// But with this line, construct2 will not be created until construct 1 is
construct2.node.addDependency(construct1);
Here is a practical example.
Probably, you'd want to save the return value of createS3Crawler to a variable, and then pass that variable as an argument to createWorkflow. Then, createWorkflow will call .node.addDependency(createS3Crawler) on each construct that it creates internally which is dependent on the S3 crawler.
I have a statemachine.
const task1 = new sfn.Task(this, 'Assign Case', {
task: new tasks.InvokeFunction(Lambda1),
});
const task2 = new sfn.Task(this, 'Close Case', {
task: new tasks.InvokeFunction(Lambda2),
});
const chain = sfn.Chain.start(task1)
.next(task2);
const StateMachine = new sfn.StateMachine(this, `StateMachine`, {
definition: chain
});
And I need to call this statemachine from Api-gateway resource.I have used the below code and it throws an error like 'statemacine is not assignable to paramaeter of type AwsIntegrationProps'
const resource = this.api.root.addResource(path);
resource.addMethod(method, new apigw.AwsIntegration(handler), { apiKeyRequired: true });
//handler is above statemachine
My api gateway integration request looks like this in console.
You should use apigateway.LambdaIntegration which extends AwsIntegration.
export declare class LambdaIntegration extends AwsIntegration {
private readonly handler;
private readonly enableTest;
constructor(handler: lambda.IFunction, options?: LambdaIntegrationOptions);
bind(method: Method): void;
}
For example :
const getBookIntegration = new apigateway.LambdaIntegration(getBookHandler);
Later, use the lambdaIntegration when creating a new method:
book.addMethod('GET', getBookIntegration);
More about LambdaIntegration.
The error 'statemacine is not assignable to paramaeter of type AwsIntegrationProps' is referring to you instantiation.
The AwsIntegration class takes an AwsIntegrationProps struct as input.
new AwsIntegration(props: AwsIntegrationProps)
Getting API Gateway to kickoff Step Functions directly is a little strange. I found this Creating a Step Functions API Using API Gateway tutorial helpful. The State Machine ARN is passed in the request body of the call, so you need to pass a request template if you don't want to require the user to specify the State Machine.
resource.addMethod(
method,
new apigw.AwsIntegration({
handler: 'states',
action: 'StartExecution',
options: {
requestTemplates: {
'application/json': `{
"stateMachineArn": "${handler.ref}",
"input": "$util.escapeJavaScript($input.body)"
}`
},
},
}),
{ apiKeyRequired: true }
);
(Note: I'm translating my code from Python so I'm not 100% on the strings in TypeScript.)
I also filled in credentialsRole, passthroughBehavior, and integrationResponses to the options to get mine setup the way I wanted.
check here:
api to state
and here:
state machine
I cannot get my id parameter to show up in the createContainer block.
class Host extends Component {
render() {
return (
<div>
{console.log(this.props.routeParams.hostId)} //works great
</div>
)
}
}
export default createContainer(() => {
Meteor.subscribe('hosts');
return {
hosts: Hosts.findOne({_id: this.props.routeParams.hostId}), //returns undefined
};
}, Host);
I want to do a query by id here, but the parameters are only available in the class itself. Not in the create container block.
How do I get my parameter to show up in createContainer?
Scope of 'this' might cause a problem. Maybe defining the param outside of the return method might work. Can you give this a try
export default createContainer(({routeParams}) => {
const id = routeParams.hostId;
Meteor.subscribe('hosts');
return {
hosts: Hosts.findOne({_id: id}),
};
}, Host);