Create multiple target groups and listeners using for each - foreach

I am trying to create multiple application target groups & listeners in Terraform using For Each. I have somewhat of a complex setup dealing with listener rules to route traffic to target groups based off HTTP headers. Below are the resources I wrote:
resource "aws_lb_listener" "app_listener_forward" {
for_each = var.listeners
load_balancer_arn = aws_lb.app_alb.arn
port = each.value.listeners
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
certificate_arn = var.ssl_cert
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app_tg[each.key].arn
}
}
resource "aws_lb_listener_rule" "app_https_listener_rule" {
for_each = var.listeners
listener_arn = aws_lb_listener.app_listener_forward[each.key].arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.app_tg[each.key].arn
}
condition {
path_pattern {
values = each.value.paths
}
}
}
resource "aws_lb_target_group" "app_tg" {
for_each = var.listeners
name = each.key
port = each.value.app_port
protocol = "HTTP"
target_type = "ip"
vpc_id = aws_vpc.app_vpc.id
health_check {
interval = 130
timeout = 120
healthy_threshold = 10
unhealthy_threshold = 10
}
stickiness {
type = "lb_cookie"
cookie_duration = 86400
}
}
Below is the variable declaration:
variable "listeners" {
type = map(object({
app_port = number
paths = set(string)
listeners = set(number)
}))
default = {
"app_one" = {
app_port = 3000
paths = [
"/appOne",
"/appOne/*"
]
listeners = [
80, 443, 22, 7999, 8999
]
}
"app_two" = {
app_port = 4000
paths = [
"/appTwo",
"/appTwo/*"
]
listeners = [
80, 443, 22, 7999, 8999
]
}
}
}
Upon trying to execute, I am getting an error dealing with the port attribute of the aws_lb_listener resource. Below is the error:
Error: Incorrect attribute value type
│
│ on alb.tf line 38, in resource "aws_lb_listener" "app_listener_forward":
│ 38: port = each.value.listeners
│ ├────────────────
│ │ each.value.listeners is set of string with 5 elements
│
│ Inappropriate value for attribute "port": number required.
I tried setting the listeners attribute of the variable to a set (number) and a set (string) due to the list of numbers, but I'm still getting this error.
Any ideas on how to fix this error would be helpful.
Thanks!

Error message state that the value for port in resource "aws_lb_listener" "app_listener_forward" {..} is incorrect as it seems to be.
As you are already looping for the whole resource using for_each it can not loop inside the values available in the variable used for looping.
One solution would be to separate the variable listeners into two.
Step 1: Separate the Variable into 2 variables
Feel free to use any names which make more sense to you.
variable "listner_ports" {
type = list(string)
description = "(optional) listeners port numbers"
default = [
80, 443, 22, 7999, 8999
]
}
variable "listeners" {
type = map(object({
app_port = number
paths = set(string)
}))
default = {
"app-one" = {
app_port = 3000
paths = [
"/appOne",
"/appOne/*"
]
}
"app-two" = {
app_port = 4000
paths = [
"/appTwo",
"/appTwo/*"
]
}
}
}
NOTE: I have changed app_{one,two} to app-{one,two} because only alphanumeric characters and hyphens allowed in aws_lb_target_group "name"
Step 2: Use the terraform dynamic block in aws_lb_listener to use looping for different variables in same resource.
resource "aws_lb_listener" "app_listener_forward" {
for_each = toset(var.listner_ports)
load_balancer_arn = data.aws_lb.test.arn ## Use aws_lb.app_alb.arn as per your usecase
port = each.value
protocol = "HTTP" # you might need to use a more complex variable to support ports and protocols but it gives an idea.
## Uncommend in your case.
# ssl_policy = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
# certificate_arn = var.ssl_cert
dynamic "default_action" {
for_each = var.listeners
content {
type = "forward"
target_group_arn = aws_lb_target_group.app_tg[default_action.key].arn
}
}
}
resource "aws_lb_target_group" "app_tg" {
for_each = var.listeners
name = each.key
port = each.value.app_port
protocol = "HTTP"
target_type = "ip"
vpc_id = local.vpc_id
health_check {
interval = 130
timeout = 120
healthy_threshold = 10
unhealthy_threshold = 10
}
stickiness {
type = "lb_cookie"
cookie_duration = 86400
}
}
Just to show you how it works below is the attached plan with only aws_lb_listener and aws_lb_target_group resources.
Terraform will perform the following actions:
# aws_lb_listener.app_listener_forward["22"] will be created
+ resource "aws_lb_listener" "app_listener_forward" {
+ arn = (known after apply)
+ id = (known after apply)
+ load_balancer_arn = "arn:aws:elasticloadbalancing:eu-central-1:xxxxxxxxxxxxxx:loadbalancer/app/stackoverflow/be9c11ed9c543788"
+ port = 22
+ protocol = "HTTP"
+ ssl_policy = (known after apply)
+ tags_all = (known after apply)
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
}
# aws_lb_listener.app_listener_forward["443"] will be created
+ resource "aws_lb_listener" "app_listener_forward" {
+ arn = (known after apply)
+ id = (known after apply)
+ load_balancer_arn = "arn:aws:elasticloadbalancing:eu-central-1:xxxxxxxxxxxxxx:loadbalancer/app/stackoverflow/be9c11ed9c543788"
+ port = 443
+ protocol = "HTTP"
+ ssl_policy = (known after apply)
+ tags_all = (known after apply)
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
}
# aws_lb_listener.app_listener_forward["7999"] will be created
+ resource "aws_lb_listener" "app_listener_forward" {
+ arn = (known after apply)
+ id = (known after apply)
+ load_balancer_arn = "arn:aws:elasticloadbalancing:eu-central-1:xxxxxxxxxxxxxx:loadbalancer/app/stackoverflow/be9c11ed9c543788"
+ port = 7999
+ protocol = "HTTP"
+ ssl_policy = (known after apply)
+ tags_all = (known after apply)
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
}
# aws_lb_listener.app_listener_forward["80"] will be created
+ resource "aws_lb_listener" "app_listener_forward" {
+ arn = (known after apply)
+ id = (known after apply)
+ load_balancer_arn = "arn:aws:elasticloadbalancing:eu-central-1:xxxxxxxxxxxxxx:loadbalancer/app/stackoverflow/be9c11ed9c543788"
+ port = 80
+ protocol = "HTTP"
+ ssl_policy = (known after apply)
+ tags_all = (known after apply)
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
}
# aws_lb_listener.app_listener_forward["8999"] will be created
+ resource "aws_lb_listener" "app_listener_forward" {
+ arn = (known after apply)
+ id = (known after apply)
+ load_balancer_arn = "arn:aws:elasticloadbalancing:eu-central-1:xxxxxxxxxxxxxx:loadbalancer/app/stackoverflow/be9c11ed9c543788"
+ port = 8999
+ protocol = "HTTP"
+ ssl_policy = (known after apply)
+ tags_all = (known after apply)
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
+ default_action {
+ order = (known after apply)
+ target_group_arn = (known after apply)
+ type = "forward"
}
}
# aws_lb_target_group.app_tg["app-one"] will be created
+ resource "aws_lb_target_group" "app_tg" {
+ arn = (known after apply)
+ arn_suffix = (known after apply)
+ connection_termination = false
+ deregistration_delay = "300"
+ id = (known after apply)
+ ip_address_type = (known after apply)
+ lambda_multi_value_headers_enabled = false
+ load_balancing_algorithm_type = (known after apply)
+ name = "app-one"
+ port = 3000
+ preserve_client_ip = (known after apply)
+ protocol = "HTTP"
+ protocol_version = (known after apply)
+ proxy_protocol_v2 = false
+ slow_start = 0
+ tags_all = (known after apply)
+ target_type = "ip"
+ vpc_id = "vpc-063017c6abb96eab6"
+ health_check {
+ enabled = true
+ healthy_threshold = 10
+ interval = 130
+ matcher = (known after apply)
+ path = (known after apply)
+ port = "traffic-port"
+ protocol = "HTTP"
+ timeout = 120
+ unhealthy_threshold = 10
}
+ stickiness {
+ cookie_duration = 86400
+ enabled = true
+ type = "lb_cookie"
}
+ target_failover {
+ on_deregistration = (known after apply)
+ on_unhealthy = (known after apply)
}
}
# aws_lb_target_group.app_tg["app-two"] will be created
+ resource "aws_lb_target_group" "app_tg" {
+ arn = (known after apply)
+ arn_suffix = (known after apply)
+ connection_termination = false
+ deregistration_delay = "300"
+ id = (known after apply)
+ ip_address_type = (known after apply)
+ lambda_multi_value_headers_enabled = false
+ load_balancing_algorithm_type = (known after apply)
+ name = "app-two"
+ port = 4000
+ preserve_client_ip = (known after apply)
+ protocol = "HTTP"
+ protocol_version = (known after apply)
+ proxy_protocol_v2 = false
+ slow_start = 0
+ tags_all = (known after apply)
+ target_type = "ip"
+ vpc_id = "vpc-063017c6abb96eab6"
+ health_check {
+ enabled = true
+ healthy_threshold = 10
+ interval = 130
+ matcher = (known after apply)
+ path = (known after apply)
+ port = "traffic-port"
+ protocol = "HTTP"
+ timeout = 120
+ unhealthy_threshold = 10
}
+ stickiness {
+ cookie_duration = 86400
+ enabled = true
+ type = "lb_cookie"
}
+ target_failover {
+ on_deregistration = (known after apply)
+ on_unhealthy = (known after apply)
}
}
Plan: 7 to add, 0 to change, 0 to destroy.
Hope it helps.
and, Yes I have masked the account ID :).
EDITED
This section was added later after reading the comment from #David where he mentioned another issue
ERROR Message : "InvalidLoadBalancerAction: You cannot specify multiple of the following action type: 'forward'" when trying to create the listener.
Use this as base code for your aws_lb_listener and I strongly encourage you to modify as per your best possible solution.[Nested Dynamic Blocks --> for documentation refer here ]
resource "aws_lb_listener" "app_listener_forward" {
for_each = toset(var.listner_ports)
load_balancer_arn = aws_lb.test.arn
port = each.value
protocol = "HTTP" # you might need to use a more complex variable to support ports and protocols but it gives an idea.
## Uncommend in your case.
# ssl_policy = "ELBSecurityPolicy-TLS-1-2-Ext-2018-06"
# certificate_arn = var.ssl_cert
default_action {
type = "forward"
forward {
dynamic "target_group" {
for_each = var.listeners
content {
arn = aws_lb_target_group.app_tg[target_group.key].arn
}
}
stickiness {
enabled = true
duration = 86400
}
}
}
}
Conclusion: Apply did work with the above code as expected (multiple listeners were created for both target groups with different ports) but can not confirm if that's what you require, however, the solution can be adjusted as per the requirement.

Related

Iterate through multiple lists using locals in Terraform code

I have the following list of Resource Group names and Virtual Machine Names, I will like to create a map using locals so I can iterate through it using each.key and each.value in my code.
VARIABLES:
variable "vm_names" {
default = [
"vm1",
"vm2",
"vm3",
"vm4",
"vm5"
]
variable "rg_names" {
default = [
"rg1",
"rg2",
"rg3",
"rg4",
"rg5"
]
LOCALS:
locals {
vm = var.vm_names
rg = var.rg_names
vmrg=[for k,v in zipmap(local.vm, local.rg):{
vm = k
rg = v
}]
}
RESULT:
+ vmrg = [
+ {
+ rg = "rg1"
+ vm = "vm1"
},
+ {
+ rg = "rg2"
+ vm = "vm2"
},
+ {
+ rg = "rg3"
+ vm = "vm3"
},
+ {
+ rg = "rg4"
+ vm = "vm4"
},
+ {
+ rg = "rg5"
+ vm = "vm5"
},
]
DESIRED RESULT:
vmrg = {
vm1 = "rg1"
vm2 = "rg2"
vm3 = "rg3"
vm4 = "rg4"
vm5 = "rg5"
}
Actually, it is much simpler and you were already close to figuring it out. The only thing you need is the zipmap built-in function:
vmrg = zipmap(local.vm, local.rg)
will yield:
> local.vmrg
{
"vm1" = "rg1"
"vm2" = "rg2"
"vm3" = "rg3"
"vm4" = "rg4"
"vm5" = "rg5"
}

Unit root test on lagged variables

guys!
I'm using this sintaxe but when I use the last part ("modelo = ...") it shows the following: "Error in merge.zoo(difetanol, detanol, mes2, mes3, mes4, mes5, mes6, mes7, :
all(sapply(args, function(x) is.zoo(x) || !is.plain(x) || (is.plain(x) && .... is not TRUE"
ethidra = (tsrelatorio[,"etanol"])
ethidra
length(ethidra)
acuavhp = (tsrelatorio[,"avhp"])
acuavhp
length(acuavhp)```
matr <- matrix(0, nr = 12, nc = 12)
matr[row(matr) == col(matr)] <- 1
matr
Djt <- rbind(matr,matr,matr,matr); Djt
dim(Djt)
names(Djt)
dimnames(Djt) = list(c(),c("M1", "M2", "M3", "M4","M5","M6","M7","M8","M9","M10","M11","M12"))
Djt
dim(Djt)```
teste=data.frame(Djt)
attach(teste)
mes2= teste[1:47,2]
mes2
length(mes2)
mes3= teste[1:47,3]
mes3
length(mes3)
mes4= teste[1:47,4]
mes4
length(mes4)
mes5= teste[1:47,5]
mes5
length(mes5)
mes6= teste[1:47,6]
mes6
length(mes6)
mes7= teste[1:47,7]
mes7
length(mes7)
mes8= teste[1:47,8]
mes8
length(mes8)
mes9= teste[1:47,9]
mes9
length(mes9)
mes10= teste[1:47,10]
mes10
length(mes10)
mes11= teste[1:47,11]
mes11
length(mes11)
mes12= teste[1:47,12]
mes12
length(mes12)
detanol = (ethidra[1:47])
detanol
length(detanol)
difetanol=diff(detanol)
difetanol
length(difetanol)
tempo = tempo <- seq(1:47)
modelo = dynlm(difetanol ~ detanol + mes2 +mes3 +mes4 + mes5 + mes6 + mes7 + mes8 + mes9 + mes10 + mes11 + mes12 + tempo + L(difetanol,1) + L(difetanol,2))
summary(modelo)

Send Vote options with Microsoft Graph API

Is there a way, in Microsoft Graph API, using singleValueExtendedProperties or multiValueExtendedProperties, to send an email with voting options?
I can do it with using Microsoft.Exchange.WebServices and the following code, but I need a way to do it in Microsoft Graph API
public byte[] StringToByteArray(string hex)
{
if (hex.Length % 2 == 1)
throw new Exception("The binary key cannot have an odd number of digits");
byte[] arr = new byte[hex.Length >> 1];
for (int i = 0; i < hex.Length >> 1; ++i)
{
arr[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
}
return arr;
}
public int GetHexVal(char hex)
{
int val = (int)hex;
//For uppercase A-F letters:
//return val - (val < 58 ? 48 : 55);
//For lowercase a-f letters:
//return val - (val < 58 ? 48 : 87);
//Or the two combined, but a bit slower:
return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
}
public void TestEmail()
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials("xxxxxx#xxxxxx.xxx", "xxxxxxxxx");
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
EmailMessage email = new EmailMessage(service);
email.ToRecipients.Add("xxx#xxxxxxx.xxx");
email.Subject = "Approval test from C#";
email.ReplyTo.Add("xxx#xxxxxxx.xxx");
string Header = "02010600000000000000";
string ReplyToAllHeader = "055265706C790849504D2E4E6F7465074D657373616765025245050000000000000000";
string ReplyToAllFooter = "0000000000000002000000660000000200000001000000";
string ReplyToHeader = "0C5265706C7920746F20416C6C0849504D2E4E6F7465074D657373616765025245050000000000000000";
string ReplyToFooter = "0000000000000002000000670000000300000002000000";
string ForwardHeader = "07466F72776172640849504D2E4E6F7465074D657373616765024657050000000000000000";
string ForwardFooter = "0000000000000002000000680000000400000003000000";
string ReplyToFolderHeader = "0F5265706C7920746F20466F6C6465720849504D2E506F737404506F737400050000000000000000";
string ReplyToFolderFooter = "00000000000000020000006C00000008000000";
string ApproveOption = "0400000007417070726F76650849504D2E4E6F74650007417070726F766500000000000000000001000000020000000200000001000000FFFFFFFF";
string RejectOtion = "040000000652656A6563740849504D2E4E6F7465000652656A65637400000000000000000001000000020000000200000002000000FFFFFFFF";
string VoteOptionExtras = "0401055200650070006C00790002520045000C5200650070006C007900200074006F00200041006C006C0002520045000746006F007200770061007200640002460057000F5200650070006C007900200074006F00200046006F006C00640065007200000741007000700072006F00760065000741007000700072006F007600650006520065006A0065006300740006520065006A00650063007400";
string DisableReplyAllVal = "00";
string DisableReplyVal = "00";
string DisableForwardVal = "00";
string DisableReplyToFolderVal = "00";
email.Body = new MessageBody();
email.Body.BodyType = BodyType.HTML;
email.Body.Text = "Body";
ExtendedPropertyDefinition VOTE_DEF = new ExtendedPropertyDefinition(Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet.Common, 0x8520, Microsoft.Exchange.WebServices.Data.MapiPropertyType.Binary);
byte[] bytes = StringToByteArray(Header + ReplyToAllHeader + DisableReplyAllVal + ReplyToAllFooter + ReplyToHeader + DisableReplyVal + ReplyToFooter + ForwardHeader + DisableForwardVal + ForwardFooter + ReplyToFolderHeader + DisableReplyToFolderVal + ReplyToFolderFooter + ApproveOption + RejectOtion + VoteOptionExtras);
email.SetExtendedProperty(VOTE_DEF, bytes);
email.SendAndSaveCopy();
}
Yes using EWS API you can do this. But i failed to notice any documentation how to do so. Being said that i remember a related thread talks about this and see how you can use MAPI extended properties. If nothing works, consider filing an uservoice item(feature request) with Microsoft Graph team, so that they can consider implementing it.

Opportunity Send Email

I tried to find a solution to my problem but I couldn't.
I have written some code to send an automatic email from Opportunity Salesforce and I write a test class but the code coverage doesn't go over 39%.
This is a code:
public class OpportunityEmail {
public static Boolean sendOppEmail(Opportunity opp){
OrgWideEmailAddress iOrgWideEmailAddress;
OrgWideEmailAddress iOrgWideEmailAddressAlert;
iOrgWideEmailAddress = OrgWideEmailAddressHelper.getByAddress(Label.SendPartnerOppEmail);
iOrgWideEmailAddressAlert = OrgWideEmailAddressHelper.getByAddress(Label.SendPartnerOppEmailAlert);
Boolean hasError = false;
Map<Id, Opportunity> mpOpp = new Map<Id, Opportunity>();
String msg = Label.OpportunitySendEmailSuccess;
Boolean myRet = false ;
System.debug(opp);
try {
Opportunity lstOpp = [select AccountId,Primary_Contact__c,CustomerAccount__c,SendEmailDone__c from Opportunity where id =: opp.Id];
Account accPrt = [select CompanyEmail__c from Account where id =: opp.AccountId];
String primContcSped = '';
if(opp.Primary_Contact__c != null){
Contact primContc = [select Email from Contact where id =: opp.Primary_Contact__c];
primContcSped = primContc.Email;
}
Account accCust = [select Account.Name,
Account.VATNumber__c,
Account.FiscalCode__c,
Account.Phone,
Account.Mobile__c,
Account.CompanyEmail__c,
Account.SDICode__c,
Account.BillingStreet,
Account.BillingPostalCode,
Account.BillingCity,
Account.BillingStateCode,
Account.BillingState,
Account.ShippingStreet,
Account.ShippingPostalCode,
Account.ShippingCity,
Account.ShippingStateCode,
Account.ShippingState from Account where id =: opp.CustomerAccount__c];
String Template = OpportunityEmail__c.getValues('Template1').TemplateEmail__c;
EmailTemplate eT = [select id, DeveloperName, Body, HtmlValue, Subject from EmailTemplate where Name =: Template];
Id TemplateId = eT.Id ;
String subject = eT.Subject;
String HtmlBody = eT.HtmlValue;
Id account = opp.Id;
String address = accPrt.CompanyEmail__c;
String ccaddress = '';
if(primContcSped != null){
ccaddress = primContcSped;
}
String ReplyToEmail = OpportunityEmail__c.getValues('Operation').ReplyToEmail__c;
String Customer = accCust.Name + '<br>' +
'P.Iva: ' + accCust.VATNumber__c + '<br>' +
'Cod.Fiscale: ' + accCust.FiscalCode__c + '<br>' +
'Telefono: ' + accCust.Phone + '<br>' +
'Mobile: ' + accCust.Mobile__c + '<br>' +
'Email: ' + accCust.CompanyEmail__c + '<br>' +
'SDI: ' + accCust.SDICode__c + '<br><br>' +
'Sede Legale: ' + '<br>' +
accCust.BillingStreet + ', ' + accCust.BillingPostalCode + ' ' + accCust.BillingCity + ' (' + accCust.BillingStateCode + ') ' + '<br><br>' +
'Sede Operativa: ' + '<br>' +
accCust.ShippingStreet + ', ' + accCust.ShippingPostalCode + ' ' + accCust.ShippingCity + ' (' + accCust.ShippingStateCode + ') ';
String Description = '';
if(opp.Description != null){
Description = opp.Description;
}
String ItemTable = '';
if(opp.HardwareBundle__c != null){
ItemTable = '<table style="width:30%;border: 1px solid black;"> <tr> <th style="text-align: left;border: 1px solid black;"> Service/Products </th> </tr>';
List<Schema.PicklistEntry> fieldResult = Opportunity.HardwareBundle__c.getDescribe().getPicklistValues();
Opportunity lstBund = [select HardwareBundle__c from Opportunity where id =: opp.Id];
String[] tmpBund = lstBund.HardwareBundle__c.split(';');
for(String s : tmpBund){
for(Schema.PicklistEntry f : fieldResult) {
if (f.getValue() == s){
ItemTable = ItemTable + '<tr> <td style = "text-align: left;">'+ f.getLabel()+'</td></tr>';
break;
}
}
}
ItemTable = ItemTable + '</table>';
}
subject = subject.replace('CustomerAccount', accCust.Name);
HtmlBody = HtmlBody.replace('{Customer1}', Customer);
HtmlBody = HtmlBody.replace('ItemTable', ItemTable);
HtmlBody = HtmlBody.replace('{!Description}', Description);
EmailManager.sendMailCcAdd(TemplateId, account, address, ccaddress, iOrgWideEmailAddress, ReplyToEmail, subject, HtmlBody);
lstOpp.SendEmailDone__c = true;
update lstOpp;
myRet = true ;
} catch (Exception e) {
String data = 'STACK TRACE:\n' +e.getStackTraceString() + '\nMESSAGE:\n' + e.getMessage()+ '\nLine:\n' + e.getLineNumber();
CustomLogHelper.addNewLog('OpportunityEmail.sendOppEmail',data);
CustomLogHelper.saveCurrentCustomLog();
hasError = true;
mpOpp.put(opp.id, opp);
msg = Label.OpportunitySendEmailFail +', Error: '+ data;
}
return myRet;
}
}
And this is a test class code
#isTest
private class OpportunityEmailTest2 {
#testSetup static void setup(){
EmailTemplate validEmailTemplate = new EmailTemplate();
validEmailTemplate.isActive = true;
validEmailTemplate.Name = 'PartnerEmail';
validEmailTemplate.DeveloperName = 'PartnerEmail';
validEmailTemplate.TemplateType = 'custom';
validEmailTemplate.FolderId = UserInfo.getUserId();
validEmailTemplate.IsActive = true;
validEmailTemplate.HtmlValue = 'Test email';
validEmailTemplate.Subject = 'Soggetto Email Test';
insert validEmailTemplate;
}
#isTest static void OpportunityEmailTest1() {
RecordType aRT = new RecordType();
aRT = [SELECT id, Name from RecordType where Name ='Company'];
Account accCust = new Account();
accCust.SDIPec__c = '1901837#gmail.com';
accCust.SDICode__c = '0000000';
accCust.Phone = '0233498737';
accCust.Mobile__c = '3452229458';
accCust.CompanyEmail__c = 'comp#gmail.com';
accCust.Name = 'Cuenta test1';
accCust.CommercialName__c = 'Cuenta test1';
accCust.VATNumber__c = '03442194837';
accCust.FiscalCode__c = 'RBNMHL70D15A059E';
accCust.BillingStreet = 'Via Pesciatina 129 Fraz. Lunata';
accCust.BillingPostalCode = '00187';
accCust.BillingCity = 'Legnano';
accCust.BillingStateCode = 'MI';
accCust.BillingState = 'Milan';
accCust.BillingCountryCode = 'IT';
accCust.ShippingStreet = 'Via Pesciatina 129 Fraz. Lunata';
accCust.ShippingPostalCode = '00187';
accCust.ShippingCity = 'Legnano';
accCust.ShippingStateCode = 'MI';
accCust.ShippingState = 'Milan';
accCust.RecordType = aRT;
insert accCust;
Account accPart = new Account();
accPart.SDIPec__c = '20190130#gmail.com';
accPart.SDICode__c = '0000000';
accPart.VATNumber__c = '03446283948';
accPart.FiscalCode__c = 'BRTLMN83C16B406T';
accPart.Name = 'Cuenta test2';
accPart.CommercialName__c = 'Cuenta test2';
accPart.CompanyEmail__c = 'CompanyEmail__2#gmail.com';
accPart.BillingStreet = 'VIA DE FLAGILLA 24';
accPart.BillingPostalCode = '00187';
accPart.BillingCity = 'ROMA';
accPart.BillingStateCode = 'RM';
accPart.BillingCountryCode = 'IT';
accPart.ShippingStreet = 'VIA DE FLAGILLA 24';
accPart.ShippingPostalCode = '00187';
accPart.ShippingCity = 'ROMA';
accPart.ShippingStateCode = 'RM';
accPart.RecordType = aRT;
insert accPart;
Contact pryCont = new Contact();
pryCont.LastName = 'Contact Test';
pryCont.MobilePhone = '3452229384';
pryCont.Email = 'contactemail#email.it';
insert pryCont;
Opportunity iOpport = new Opportunity();
iOpport.Name = 'Test Opportunity Email';
iOpport.StageName = 'Proposal';
iOpport.CloseDate = Date.today();
//iOpport.RecordTypeId = '0120N000000RUeXQAW'; //PROD
//iOpport.RecordTypeId = '0121w0000009fgJAAQ'; //PARTIAL COPY
iOpport.RecordTypeId = '0121w0000006dPnAAI'; //MYDEV
iOpport.AccountId = accPart.Id;
iOpport.CustomerAccount__c = accCust.Id;
iOpport.Primary_Contact__c = pryCont.Id;
iOpport.Description = 'Descrizione test';
iOpport.HardwareBundle__c = 'BD05';
iOpport.OpportunityHardware__c = true;
iOpport.SendEmailDone__c = false;
insert iOpport;
OpportunityEmail.sendOppEmail(iOpport);
OrgWideEmailAddress iOrgWideEmailAddress;
iOrgWideEmailAddress = OrgWideEmailAddressHelper.getByAddress(Label.SendPartnerOppEmail);
String Template = 'PartnerEmail';
EmailTemplate eT = [select id, DeveloperName, Body, HtmlValue, Subject from EmailTemplate where Name =: Template LIMIT 1];
Id TemplateId = eT.Id ;
String subject = eT.Subject;
String HtmlBody = eT.HtmlValue;
Id account = iOpport.Id;
String address = accPart.CompanyEmail__c;
String ccaddress = pryCont.Email;
String ReplyToEmail = 'replayemail#email.com';
String Customer = accCust.Name + '<br>' +
'P.Iva: ' + accCust.VATNumber__c + '<br>' +
'Cod.Fiscale: ' + accCust.FiscalCode__c + '<br>' +
'Telefono: ' + accCust.Phone + '<br>' +
'Mobile: ' + accCust.Mobile__c + '<br>' +
'Email: ' + accCust.CompanyEmail__c + '<br>' +
'SDI: ' + accCust.SDICode__c + '<br><br>' +
'Sede Legale: ' + '<br>' +
accCust.BillingStreet + ', ' + accCust.BillingPostalCode + ' ' + accCust.BillingCity + ' (' + accCust.BillingStateCode + ') ' + '<br><br>' +
'Sede Operativa: ' + '<br>' +
accCust.ShippingStreet + ', ' + accCust.ShippingPostalCode + ' ' + accCust.ShippingCity + ' (' + accCust.ShippingStateCode + ') ';
String Description = iOpport.Description;
String ItemTable = iOpport.HardwareBundle__c;
EmailManager.sendMailCcAdd(TemplateId, account, address, ccaddress, iOrgWideEmailAddress, ReplyToEmail, subject, HtmlBody);
}
}
I understood that the part of code not cover is this:
String Template = OpportunityEmail__c.getValues('Template1').TemplateEmail__c;
EmailTemplate eT = [select id, DeveloperName, Body, HtmlValue, Subject from EmailTemplate where Name =: Template];
Id TemplateId = eT.Id ;
String subject = eT.Subject;
String HtmlBody = eT.HtmlValue;
Id account = opp.Id;
String address = accPrt.CompanyEmail__c;
String ccaddress = '';
if(primContcSped != null){
ccaddress = primContcSped;
}
String ReplyToEmail = OpportunityEmail__c.getValues('Operation').ReplyToEmail__c;
String Customer = accCust.Name + '<br>' +
'P.Iva: ' + accCust.VATNumber__c + '<br>' +
'Cod.Fiscale: ' + accCust.FiscalCode__c + '<br>' +
'Telefono: ' + accCust.Phone + '<br>' +
'Mobile: ' + accCust.Mobile__c + '<br>' +
'Email: ' + accCust.CompanyEmail__c + '<br>' +
'SDI: ' + accCust.SDICode__c + '<br><br>' +
'Sede Legale: ' + '<br>' +
accCust.BillingStreet + ', ' + accCust.BillingPostalCode + ' ' + accCust.BillingCity + ' (' + accCust.BillingStateCode + ') ' + '<br><br>' +
'Sede Operativa: ' + '<br>' +
accCust.ShippingStreet + ', ' + accCust.ShippingPostalCode + ' ' + accCust.ShippingCity + ' (' + accCust.ShippingStateCode + ') ';
String Description = '';
if(opp.Description != null){
Description = opp.Description;
}
String ItemTable = '';
if(opp.HardwareBundle__c != null){
ItemTable = '<table style="width:30%;border: 1px solid black;"> <tr> <th style="text-align: left;border: 1px solid black;"> Service/Products </th> </tr>';
List<Schema.PicklistEntry> fieldResult = Opportunity.HardwareBundle__c.getDescribe().getPicklistValues();
Opportunity lstBund = [select HardwareBundle__c from Opportunity where id =: opp.Id];
String[] tmpBund = lstBund.HardwareBundle__c.split(';');
for(String s : tmpBund){
for(Schema.PicklistEntry f : fieldResult) {
if (f.getValue() == s){
ItemTable = ItemTable + '<tr> <td style = "text-align: left;">'+ f.getLabel()+'</td></tr>';
break;
}
}
}
ItemTable = ItemTable + '</table>';
}
subject = subject.replace('CustomerAccount', accCust.Name);
HtmlBody = HtmlBody.replace('{Customer1}', Customer);
HtmlBody = HtmlBody.replace('ItemTable', ItemTable);
HtmlBody = HtmlBody.replace('{!Description}', Description);
EmailManager.sendMailCcAdd(TemplateId, account, address, ccaddress, iOrgWideEmailAddress, ReplyToEmail, subject, HtmlBody);
lstOpp.SendEmailDone__c = true;
update lstOpp;'
But I don't understand which is a solution.
Thank you so much for your help!
String Template = OpportunityEmail__c.getValues('Template1').TemplateEmail__c;
In this line, I see that you are trying to access a Custom Setting OpportunityEmail__c. Custom Settings behave just like data, which means that you need to insert it in the test method before you can use it.
Your other option is to use a Custom Metadata Type instead, which can be retrieved from the environment via a SOQL query.

Getting all videos of a channel using youtube API

I want to get all videos of a single channel that i have its Id. The problem that I am getting only the channel informations.
this is the link that I am using:
https://gdata.youtube.com/feeds/api/users/UCdCiB_pNQpR0M_KkDG4Dz5A?v=2&alt=json&q=goal&orderby=published&max-results=10
That link is for the now-retired V2 API, so it will not return any data. Instead, you'll want to use V3 of the API. The first thing you'll need to do is register for an API key -- you can do this by creating a project at console.developers.google.com, setting the YouTube data API to "on," and creating a public access key.
Since you have your user channel ID already, you can jump right into getting the videos from it; note, however, that if you ever don't know the channel ID, you can get it this way:
https://www.googleapis.com/youtube/v3/channels?part=snippet&forUsername={username}&key={YOUR_API_KEY}
With the channel ID, you can get all the videos from the channel with the search endpoint, like this:
https://www.googleapis.com/youtube/v3/search?order=date&part=snippet&channelId={channel id here}&maxResults=25&key={YOUR_API_KEY}
In this case, ordering by date is the same as the old V2 parameter for ordering by "published."
There are also a lot of other parameters you can use to retrieve videos while searching a channel; see https://developers.google.com/youtube/v3/docs/search/list for more details.
I thought I would share my final result using JavaScript. It uses the Google YouTube API key and UserName to get the channel ID, then pulls the videos and displays in a list to a given div tag.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>YouTube Channel Listing</title>
<script type="text/javascript">
function getJSONData(yourUrl) {
var Httpreq = new XMLHttpRequest();
try {
Httpreq.open("GET", yourUrl, false);
Httpreq.send(null);
} catch (ex) {
alert(ex.message);
}
return Httpreq.responseText;
}
function showVideoList(username, writediv, maxnumbervideos, apikey) {
try {
document.getElementById(writediv).innerHTML = "";
var keyinfo = JSON.parse(getJSONData("https://www.googleapis.com/youtube/v3/channels?part=snippet&forUsername=" + username + "&key=" + apikey));
var userid = keyinfo.items[0].id;
var channeltitle = keyinfo.items[0].snippet.title;
var channeldescription = keyinfo.items[0].snippet.description;
var channelthumbnail = keyinfo.items[0].snippet.thumbnails.default.url; // default, medium or high
//channel header
document.getElementById(writediv).innerHTML += "<div style='width:100%;min-height:90px;'>"
+ "<a href='https://www.youtube.com/user/" + username + "' target='_blank'>"
+ "<img src='" + channelthumbnail + "' style='border:none;float:left;margin-right:10px;' alt='" + channeltitle + "' title='" + channeltitle + "' /></a>"
+ "<div style='width:100%;text-align:center;'><h1><a href='https://www.youtube.com/user/" + username + "' target='_blank'>" + channeltitle + "</a></h1>" + channeldescription + "</div>"
+ "</div>";
var videoinfo = JSON.parse(getJSONData("https://www.googleapis.com/youtube/v3/search?order=date&part=snippet&channelId=" + userid + "&maxResults=" + maxnumbervideos + "&key=" + apikey));
var videos = videoinfo.items;
var videocount = videoinfo.pageInfo.totalResults;
// video listing
for (var i = 0; i < videos.length; i++) {
var videoid = videos[i].id.videoId;
var videotitle = videos[i].snippet.title;
var videodescription = videos[i].snippet.description;
var videodate = videos[i].snippet.publishedAt; // date time published
var videothumbnail = videos[i].snippet.thumbnails.default.url; // default, medium or high
document.getElementById(writediv).innerHTML += "<hr /><div style='width:100%;min-height:90px;'>"
+ "<a href='https://www.youtube.com/watch?v=" + videoid + "' target='_blank'>"
+ "<img src='" + videothumbnail + "' style='border:none;float:left;margin-right:10px;' alt='" + videotitle + "' title='" + videotitle + "' /></a>"
+ "<h3><a href='https://www.youtube.com/watch?v=" + videoid + "' target='_blank'>" + videotitle + "</a></h3>" + videodescription + ""
+ "</div>";
}
} catch (ex) {
alert(ex.message);
}
}
</script>
</head>
<body>
<div id="videos"></div>
<script type="text/javascript">
showVideoList("USER_NAME", "videos", 25, "YOUR_API_KEY");
</script>
</body>
</html>
ADDITION - I also wrote a function to handle if you are using a channel ID instead of a UserName based account.
Here is that code:
function showVideoListChannel(channelid, writediv, maxnumbervideos, apikey) {
try {
document.getElementById(writediv).innerHTML = "";
var vid = getJSONData("https://www.googleapis.com/youtube/v3/search?order=date&part=snippet&channelId=" + channelid + "&maxResults=" + (maxnumbervideos + 1) + "&key=" + apikey);
var videoinfo = JSON.parse(vid);
var videos = videoinfo.items;
var videocount = videoinfo.pageInfo.totalResults;
var content = "<div style='height:600px;overflow-y:auto;'>";
for (var i = 0; i < videos.length - 1; i++) {
var videoid = videos[i].id.videoId;
var videotitle = videos[i].snippet.title;
var videodescription = videos[i].snippet.description;
var videodate = videos[i].snippet.publishedAt; // date time published
var newdate = new Date(Date.parse((videodate + " (ISO 8601)").replace(/ *\(.*\)/, "")));
var min = newdate.getMinutes();
if (min < 10) {
min = "0" + min;
}
if (newdate.getHours() > 12) {
newdate = newdate.getMonth() + 1 + "/" + newdate.getDate() + "/" + newdate.getFullYear() + " " + (newdate.getHours() - 12) + ":" + min + " PM";
} else if (newdate.getHours() == 12) {
newdate = newdate.getMonth() + 1 + "/" + newdate.getDate() + "/" + newdate.getFullYear() + " " + newdate.getHours() + ":" + min + " PM";
} else {
newdate = newdate.getMonth() + 1 + "/" + newdate.getDate() + "/" + newdate.getFullYear() + " " + newdate.getHours() + ":" + min + " AM";
}
var videothumbnail = videos[i].snippet.thumbnails.default.url; // default, medium or high
content += "<hr /><div style='width:100%;min-height:90px;'>"
+ "<a href='https://www.youtube.com/watch?v=" + videoid + "' target='_blank'>"
+ "<img src='" + videothumbnail + "' style='border:none;float:left;margin-right:10px;' alt='" + videotitle + "' title='" + videotitle + "' /></a>"
+ "<h3><a href='https://www.youtube.com/watch?v=" + videoid + "' target='_blank'>" + videotitle + "</a></h3>" + videodescription + "<br />"
+ "<span style='color:#738AAD;font-size:Small;'>" + newdate + "</span>"
+ "</div>";
}
content += "</div>";
document.getElementById(writediv).innerHTML = content;
} catch (ex) {
alert(ex.message);
}
}
It is very easy method to get channel videos using your channel API key:
Step 1: You must have an YouTube account.
Step 2: Create your YouTube channel API key
Step 3: Create project console.developers.google.com,
<?php
$API_key = 'Your API key'; //my API key dei;
$channelID = 'Your Channel ID'; //my channel ID
$maxResults = 5;
$video_list =
json_decode(file_get_contents('https://www.googleapis.com/youtube/v3/search?
order=date&part=snippet&channelId='.$channelID.
'&maxResults='.$maxResults.'&key='.$API_key.''));
?>
Example : https://www.googleapis.com/youtube/v3/channelspart=snippet&forUsername=
{username}&key={YOUR_API_KEY}
Here is the way to get all videos with only 2 quotas using YouTube Data API (v3)
First of all do a list on channels with part=contentDetails (1 quota) :
https://youtube.googleapis.com/youtube/v3/channels?part=contentDetails&id=[CHANNEL_ID]&key=[YOUR_API_KEY]
You will get this result :
{
...
"items": [
{
...
"contentDetails": {
"relatedPlaylists": {
"likes": "",
"uploads": "UPLOADS_PLAYLIST_ID"
}
}
}
]
}
Then take UPLOADS_PLAYLIST_ID and do a list on playlistItems with part=contentDetails (1 quota):
https://youtube.googleapis.com/youtube/v3/playlistItems?part=contentDetails&playlistId=[UPLOADS_PLAYLIST_ID]&key=[YOUR_API_KEY]
You will get this result:
{
...
"items": [
{
...
"contentDetails": {
"videoId": "VIDEO_ID",
"videoPublishedAt": "2022-10-27T16:00:08Z"
}
},
...
],
"pageInfo": {
"totalResults": 5648,
"resultsPerPage": 5
}
}
You got the list of the videos under items
You can of course change the size of this list by adding maxResults=50 (max value is 50)

Resources