Skip to content
Draft
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
18 changes: 18 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This is a label that will appear as a grouping in the Looker admin page
ACTION_HUB_LABEL=My Local Hub
# This value is used to generate the authorization token - it is not the token itself.
# Run `yarn generate-api-key` to get the value you paste into Looker.
ACTION_HUB_SECRET=mysecret
# This is the URL that will be used to access Action Hub from the outside world.
# That is, the URL of the reverse proxy / load balancer if you are using one.
# So the port here might be different from the PORT param
ACTION_HUB_BASE_URL=localhost:4430
# This specifies the local port the service binds to.
PORT=8080
# This is used for encryption functions.
# Can be generated with something like `openssl rand -hex 32`
CIPHER_MASTER=281243e09385f19773569e4d99d306065efb0cf0f6e3ca4c705571c173722c99
# Use this in development to turn on debug logging
ACTION_HUB_DEBUG=1
# Use this in development to filter the list of actions exposed to Looker
ACTION_WHITELIST=pagerduty
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ CIPHER_MASTER=281243e09385f19773569e4d99d306065efb0cf0f6e3ca4c705571c173722c99
# Use this in development to turn on debug logging
ACTION_HUB_DEBUG=1
# Use this in development to filter the list of actions exposed to Looker
ACTION_WHITELIST=my_new_action_name,other_action_name
ACTION_WHITELIST=pagerduty
2 changes: 0 additions & 2 deletions .github/workflows/actions-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ name: Actions CI
on:
pull_request:
branches:
- main
- master

push:
branches:
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/scan-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build and scan image

on:
workflow_dispatch: # manual trigger

pull_request:
branches:

concurrency: scan

jobs:
build-image:
timeout-minutes: 20
runs-on: ubuntu-latest
env:
IMAGE: tapad/looker-pd-action
steps:
- uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build Docker image
uses: docker/build-push-action@v3
with:
push: false
load: true
tags: ${{ env.IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Scan image
uses: Tapad/gha-container-scanner@1.0.0
with:
container: ${{ env.IMAGE }}:latest
wiz_key_id: ${{ secrets.WIZ_KEY_ID }}
wiz_key_secret: ${{ secrets.WIZ_KEY_SECRET }}
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
FROM node:14.18.0
FROM node:14.18.0-slim

RUN mkdir -p /code
WORKDIR /code

COPY . /code

RUN yarn install --production && yarn cache clean
# RUN yarn config set "strict-ssl" false
RUN yarn install --production
RUN yarn cache clean
RUN yarn build

CMD ["yarn","start"]
Expand Down
3 changes: 3 additions & 0 deletions lib/actions/facebook/facebook_custom_audiences.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,6 @@ if (process.env.FACEBOOK_CLIENT_ID
const fcma = new FacebookCustomAudiencesAction(process.env.FACEBOOK_CLIENT_ID, process.env.FACEBOOK_CLIENT_SECRET);
Hub.addAction(fcma);
}
else {
winston.warn(`[Facebook Custom Audiences] Action not registered because required environment variables are missing.`);
}
9 changes: 3 additions & 6 deletions lib/actions/facebook/lib/api.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export declare const API_VERSION = "v12.0";
export declare const API_VERSION = "v14.0";
export declare const API_BASE_URL: string;
export declare const CUSTOMER_LIST_SOURCE_TYPES: {
USER_PROVIDED_ONLY: string;
Expand Down Expand Up @@ -36,12 +36,9 @@ export declare const validFacebookHashCombinations: [(f: UserFields) => string,
export default class FacebookCustomAudiencesApi {
readonly accessToken: string;
constructor(accessToken: string);
pagingResults(url: string): Promise<any>;
me(): Promise<any>;
getBusinessAccountIds(): Promise<{
name: string;
id: string;
}[]>;
getAdAccountsForBusiness(businessId: string): Promise<{
getAdAccounts(): Promise<{
name: string;
id: string;
}[]>;
Expand Down
86 changes: 48 additions & 38 deletions lib/actions/facebook/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exports.validFacebookHashCombinations = exports.CUSTOMER_LIST_SOURCE_TYPES = exp
const gaxios = require("gaxios");
const winston = require("winston");
const util_1 = require("./util");
exports.API_VERSION = "v12.0";
exports.API_VERSION = "v14.0";
exports.API_BASE_URL = `https://graph.facebook.com/${exports.API_VERSION}/`;
exports.CUSTOMER_LIST_SOURCE_TYPES = {
// Used by Facebook for unknown purposes.
Expand Down Expand Up @@ -53,38 +53,32 @@ class FacebookCustomAudiencesApi {
constructor(accessToken) {
this.accessToken = accessToken;
}
me() {
pagingResults(url) {
return __awaiter(this, void 0, void 0, function* () {
return this.apiCall("GET", "me");
let data = [];
let hasNext = true;
while (hasNext) {
const response = yield this.apiCall("GET", url);
data = [...data, ...response.data];
if (!response.paging || !response.paging.next) {
hasNext = false;
}
else {
url = response.paging.next
.replace(exports.API_BASE_URL, "")
.replace(/access_token=.+?&/, "");
}
}
return data;
});
}
/*Sample response:
{
"businesses": {
"data": [
{
"id": "496949287383810",
"name": "Cool Guys Moving LLC"
},
{
"id": "104000277081747",
"name": "Western Analytics"
}
],
}
"paging": ...
"id": "106332305032035"
}*/
getBusinessAccountIds() {
me() {
return __awaiter(this, void 0, void 0, function* () {
const response = yield this.apiCall("GET", "me?fields=businesses");
const namesAndIds = response.businesses.data.map((businessMetadata) => ({ name: businessMetadata.name, id: businessMetadata.id }));
return namesAndIds;
return this.apiCall("GET", "me");
});
}
/*
Sample response:

{
"data": [
{
Expand All @@ -93,34 +87,50 @@ class FacebookCustomAudiencesApi {
"id": "act_114108701688636"
}
],
"paging": {}
"paging": {
"cursors": {
"before": "abcdef123",
"after": "abcdef456"
},
"previous": "https://graph.facebook.com...&before=abcdef123",
"next": "https://graph.facebook.com...&after=abcdef456"
}
}
*/
getAdAccountsForBusiness(businessId) {
getAdAccounts() {
return __awaiter(this, void 0, void 0, function* () {
const addAcountsForBusinessUrl = `${businessId}/owned_ad_accounts?fields=name,account_id`;
const response = yield this.apiCall("GET", addAcountsForBusinessUrl);
const namesAndIds = response.data.map((adAccountMetadata) => ({ name: adAccountMetadata.name, id: adAccountMetadata.account_id }));
const addAcountsUrl = `me/adaccounts?fields=name,account_id`;
const data = yield this.pagingResults(addAcountsUrl);
const namesAndIds = data
.map((adAccountMetadata) => ({ name: adAccountMetadata.name, id: adAccountMetadata.account_id }))
.sort(util_1.sortCompare);
return namesAndIds;
});
}
/*
Sample response:
{
"data": [
{
"name": "My new Custom Audience",
"id": "23837492450850533"
"data": [
{
"name": "My new Custom Audience",
"id": "23837492450850533"
}
],
"paging": {
"cursors": {
"before": "abcdef123",
"after": "abcdef456"
},
"previous": "https://graph.facebook.com...&before=abcdef123",
"next": "https://graph.facebook.com...&after=abcdef456"
}
],
"paging":...
}
*/
getCustomAudiences(adAccountId) {
return __awaiter(this, void 0, void 0, function* () {
const customAudienceUrl = `act_${adAccountId}/customaudiences?fields=name`;
const response = yield this.apiCall("GET", customAudienceUrl);
const namesAndIds = response.data
const data = yield this.pagingResults(customAudienceUrl);
const namesAndIds = data
.map((customAudienceMetadata) => ({ name: customAudienceMetadata.name, id: customAudienceMetadata.id }))
.sort(util_1.sortCompare);
return namesAndIds;
Expand Down
4 changes: 2 additions & 2 deletions lib/actions/facebook/lib/executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ class FacebookCustomAudiencesExecutor {
throw new Error("Cannot execute action without choosing an operation type.");
}
this.operationType = operationType;
if (!actionRequest.formParams.choose_business || !actionRequest.formParams.choose_ad_account) {
throw new Error("Cannot execute action without business id or ad account id.");
if (!actionRequest.formParams.choose_ad_account) {
throw new Error("Cannot execute action without ad account id.");
}
if (!actionRequest.formParams.choose_custom_audience && (operationType === "update_audience" || operationType === "replace_audience")) {
throw new Error("Cannot update or replace without a custom audience id.");
Expand Down
23 changes: 2 additions & 21 deletions lib/actions/facebook/lib/form_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,11 @@ const Hub = require("../../../hub");
class FacebookFormBuilder {
generateActionForm(actionRequest, facebookApi) {
return __awaiter(this, void 0, void 0, function* () {
let businesses = [];
let adAccounts = [];
let customAudiences = [];
businesses = yield facebookApi.getBusinessAccountIds();
if (actionRequest.formParams.choose_business === "reset") {
actionRequest.formParams = {};
}
adAccounts = yield facebookApi.getAdAccounts();
const form = new Hub.ActionForm();
form.fields = [{
label: "Choose a business",
name: "choose_business",
description: "You can start over by choosing \"Start over\" from this list.",
required: true,
interactive: true,
type: "select",
options: [
{ name: "reset", label: "Start over" },
...(yield this.generateOptionsFromNamesAndIds(businesses)),
],
}];
if (actionRequest.formParams.choose_business) {
adAccounts = yield facebookApi.getAdAccountsForBusiness(actionRequest.formParams.choose_business);
form.fields.push({
label: "Choose a Facebook ad account",
name: "choose_ad_account",
required: true,
Expand All @@ -45,8 +27,7 @@ class FacebookFormBuilder {
options: [
...(yield this.generateOptionsFromNamesAndIds(adAccounts)),
],
});
}
}];
if (actionRequest.formParams.choose_ad_account) {
form.fields.push({
label: "Would you like to create a new audience, update existing, or replace existing?",
Expand Down
2 changes: 1 addition & 1 deletion lib/actions/facebook/lib/util.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export declare function getDayOfMonth(date: string | number): string;
export declare function getYear(date: string | number): string;
export declare function formatFullDate(dayOfMonth: string, month: string, year: string): string;
export declare function isNullOrUndefined(a: any): true | undefined;
export declare function sortCompare(a: any, b: any): 1 | 0 | -1;
export declare function sortCompare(a: any, b: any): 1 | -1 | 0;
27 changes: 0 additions & 27 deletions lib/actions/firebase/firebase.d.ts

This file was deleted.

Loading