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

New Action: Google Ads Offline Conversion Import action #501

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
152 changes: 152 additions & 0 deletions src/actions/google/ads/conversion_import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import * as winston from "winston"
import * as Hub from "../../../hub"
import { makeBetterErrorMessage, sanitizeError } from "../common/error_utils"
import { MissingAuthError } from "../common/missing_auth_error"
import { GoogleOAuthHelper, UseGoogleOAuthHelper } from "../common/oauth_helper"
import { WrappedResponse } from "../common/wrapped_response"
import { GoogleAdsConversionImportActionRequest } from "./lib/conversion_import/conversion_import_request"

const LOG_PREFIX = "[G Ads Conversion Import]"

export class GoogleAdsConversionImport
extends Hub.OAuthAction
implements UseGoogleOAuthHelper {

/******** Core action properties ********/

readonly name = "google_ads_conversion_import"
readonly label = "Google Ads Conversion Import"
readonly iconName = "google/ads/google_ads_icon.svg"
readonly description = "Upload conversions to Google Ads"
readonly supportedActionTypes = [Hub.ActionType.Query]
readonly supportedFormats = [Hub.ActionFormat.JsonLabel]
readonly supportedFormattings = [Hub.ActionFormatting.Unformatted]
readonly supportedVisualizationFormattings = [Hub.ActionVisualizationFormatting.Noapply]
readonly supportedDownloadSettings = [Hub.ActionDownloadSettings.Url]
readonly usesStreaming = true
readonly requiredFields = []
readonly params = []

/******** Other fields + OAuth stuff ********/

readonly redirectUri = `${process.env.ACTION_HUB_BASE_URL}/actions/${encodeURIComponent(this.name)}/oauth_redirect`
readonly developerToken: string
readonly oauthClientId: string
readonly oauthClientSecret: string
readonly oauthScopes = [
"https://www.googleapis.com/auth/adwords",
]
readonly oauthHelper: GoogleOAuthHelper

/******** Constructor & Helpers ********/

constructor(oauthClientId: string, oauthClientSecret: string, developerToken: string) {
super()
this.developerToken = developerToken
this.oauthClientId = oauthClientId
this.oauthClientSecret = oauthClientSecret
this.oauthHelper = new GoogleOAuthHelper(this, this.makeLogger("oauth"))
}

makeLogger(webhookId = "") {
return (level: string, ...rest: any[]) => {
return winston.log(level, LOG_PREFIX, `[webhookID=${webhookId}]`, ...rest)
}
}

makeOAuthClient() {
return this.oauthHelper.makeOAuthClient(this.redirectUri)
}

/******** OAuth Endpoints ********/

async oauthUrl(redirectUri: string, encryptedState: string) {
return this.oauthHelper.oauthUrl(redirectUri, encryptedState)
}

async oauthFetchInfo(urlParams: { [key: string]: string }, redirectUri: string) {
return this.oauthHelper.oauthFetchInfo(urlParams, redirectUri)
}

async oauthCheck(_request: Hub.ActionRequest) {
// This part of Hub.OAuthAction is deprecated and unused
return true
}

/******** Action Endpoints ********/

async execute(hubReq: Hub.ActionRequest) {
const wrappedResp = new WrappedResponse(Hub.ActionResponse)
const log = this.makeLogger(hubReq.webhookId)
try {
const adsRequest = await GoogleAdsConversionImportActionRequest.fromHub(hubReq, this, log)
await adsRequest.execute()
log("info", "Execution complete")
return wrappedResp.returnSuccess(adsRequest.userState)
} catch (err) {
sanitizeError(err)
makeBetterErrorMessage(err, hubReq.webhookId)
log("error", "Execution error toString:", err.toString())
log("error", "Execution error JSON:", JSON.stringify(err))
return wrappedResp.returnError(err)
}
}

async form(hubReq: Hub.ActionRequest) {
const wrappedResp = new WrappedResponse(Hub.ActionForm)
const log = this.makeLogger(hubReq.webhookId)
try {
const adsWorker = await GoogleAdsConversionImportActionRequest.fromHub(hubReq, this, log)
wrappedResp.form = await adsWorker.makeForm()
return wrappedResp.returnSuccess(adsWorker.userState)
// Use this code if you need to force a state reset and redo oauth login
// wrappedResp.form = await this.oauthHelper.makeLoginForm(hubReq)
// wrappedResp.resetState()
// return wrappedResp.returnSuccess()
} catch (err) {
sanitizeError(err)
const loginForm = await this.oauthHelper.makeLoginForm(hubReq)
// Token errors that we can detect ahead of time
if (err instanceof MissingAuthError) {
return loginForm
}
log("error", "Form error toString:", err.toString())
log("error", "Form error JSON:", JSON.stringify(err))

// AuthorizationError from API client - this occurs when request contains bad loginCid or targetCid
if (err.code === "403") {
wrappedResp.errorPrefix = `Error loading target account with request: ${err.response.request.responseURL}. `
+ `${err.response.data[0].error.details[0].errors[0].message}`
+ ` Please retry loading the form again with the correct login account. `
return wrappedResp.returnError(err)
}

// Other errors from the API client - typically an auth problem
if (err.code) {
loginForm.fields[0].label =
`Received error code ${err.code} from the API, so your credentials have been discarded.`
+ " Please reauthenticate and try again."
return loginForm
}
// All other errors
wrappedResp.errorPrefix = "Form generation error: "
return wrappedResp.returnError(err)
}
}
}

/******** Register with Hub if prereqs are satisfied ********/

if (process.env.GOOGLE_ADS_CLIENT_ID
&& process.env.GOOGLE_ADS_CLIENT_SECRET
&& process.env.GOOGLE_ADS_DEVELOPER_TOKEN
) {
const action = new GoogleAdsConversionImport(
process.env.GOOGLE_ADS_CLIENT_ID,
process.env.GOOGLE_ADS_CLIENT_SECRET,
process.env.GOOGLE_ADS_DEVELOPER_TOKEN,
)
Hub.addAction(action)
} else {
winston.warn(`${LOG_PREFIX} Action not registered because required environment variables are missing.`)
}
Loading