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

Ai bubbles #21

Merged
merged 33 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4216522
adding support for Gemini AI bubbles
Nov 28, 2024
08d9f6d
adding insights to dgpulse_ads_bq instead of a separate dataset to si…
Nov 28, 2024
255d09e
fixing function env var setup
Nov 28, 2024
804838c
adding more permissions and API enablement for generativelanguage
Nov 29, 2024
a770d4b
adding tests to bq and separating gemini logic
Nov 29, 2024
4079b50
splitting up installation into multiple logical bash files
Dec 17, 2024
bc9bc07
setting up permissions on new bash files
Dec 17, 2024
f506a5c
migrating instructions to install-or-upgrade instead of setup-wfs
Dec 17, 2024
ec471fa
moving bash to separate folder
Dec 17, 2024
bea151e
folder rename for consistency
Dec 17, 2024
6387eaa
moving bash to separate folder
Dec 17, 2024
c9cad9a
deprecating old upgrade bash
Dec 17, 2024
1354db4
deprecating old upgrade bash
Dec 17, 2024
ad7b72d
fixing path typo
Dec 17, 2024
c3b0625
adding "headline" to the process
Dec 23, 2024
5ae042a
adding "headline" to the process
Dec 23, 2024
b0024b4
cleaning up ai bubbles install bash
Dec 23, 2024
3e57354
fixing headline prompt
Dec 23, 2024
cc28a33
fixing headline prompt
Dec 23, 2024
ee84c7c
fixing headline prompt
Dec 23, 2024
411ca5c
fixing headline prompt
Dec 23, 2024
bc62f95
fixing headline prompt
Dec 23, 2024
31476f3
fixing headline prompt
Dec 23, 2024
e3ffd12
fixing headline prompt
Dec 23, 2024
3e34266
fixing headline prompt
Dec 23, 2024
61db351
fixing headline prompt
Dec 23, 2024
f86fb17
addind campaigns_assets_count bubbles
Dec 23, 2024
53fe322
addind campaigns_assets_count bubbles
Dec 24, 2024
b552f12
addind campaigns_assets_count bubbles
Dec 24, 2024
c83da5f
addind campaigns_assets_count bubbles
Dec 24, 2024
531cf87
addind campaigns_assets_count bubbles
Dec 24, 2024
150546f
addind campaigns_assets_count bubbles
Dec 24, 2024
5b1490f
Merge branch 'main' into ai-bubbles
betocollin Dec 24, 2024
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ cd dgpulse
```

```
./upgrade-version.sh
./install-or-upgrade.sh
```

Notice that this will **not** change the Looker Studio template. Only the code. In order to get the latest version of the template, go to this [link](https://lookerstudio.google.com/c/u/0/reporting/7ae6081d-c69a-4f29-ad02-f9c1aa16a052/page/i5YsC), make a copy of it and point the data sources to your own GCP's BigQuery.
Expand Down
16 changes: 16 additions & 0 deletions ai_bubbles/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This file specifies files that are *not* uploaded to Google Cloud
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

node_modules
154 changes: 154 additions & 0 deletions ai_bubbles/bq.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const { BigQuery } = require("@google-cloud/bigquery");

// TODO: solutionName should be dynamic.
const solutionName = "dgpulse";
const datasetId = `${solutionName}_ads`;
let projectId = process.env.GCP_PROJECT_ID;

const bigquery = new BigQuery({
projectId,
});

const lighterQueries = {
"campaign_data": `
SELECT
campaign_name,
account_name,
date,
MAX(budget_amount) AS budget_amount,
SUM(cost) AS cost,
SUM(conversions) AS conversions,
AVG(tcpa) AS cpa
FROM
${projectId}.${datasetId}_bq.campaign_data
GROUP BY 1, 2, 3
ORDER BY date DESC`,

"campaigns_assets_count": `
SELECT
campaign_name,
account_name,
has_product_feed,
dmaa_descriptions_count AS descriptions,
aga_headlines_count AS headlines,
dmaa_square_mkt_imgs_count AS square_images,
dmaa_mkt_imgs_count AS landscape_images,
dmaa_logo_imgs_count AS logos,
dmaa_portrait_mkt_imgs_count AS portrait_images,
landscape_video_count AS landscape_videos,
square_video_count AS square_videos,
portrait_video_count AS portrait_videos,
has_image_plus_video
FROM
${projectId}.${datasetId}_bq.campaigns_assets_count
`
}

async function getData(table) {
console.log("Querying BQ: ", table);
const query = lighterQueries[table];
return await executeQuery(query);
}

function escapeSQLString(str) {
if (typeof str != 'string') {
return str;
}
return str.replace(/[\0\x08\x09\x1a\n\r"'\\\%]/g, function (char) {
switch (char) {
case "\0":
return "\\0";
case "\x08":
return "\\b";
case "\x09":
return "\\t";
case "\x1a":
return "\\z";
case "\n":
return "\\n";
case "\r":
return "\\r";
case '"':
case "'":
case "\\":
case "%":
return "\\" + char; // prepends a backslash to backslash, percent,
// and double/single quotes
default:
return char;
}
});
}


function getInsertQueryForInsights(data) {
let finalUpdateQuery = "";
for (let i = 0; i < data.length; i++) {
const record = data[i];
if (!record.insights || !record.table || !record.headline)
throw "missing data for insert";

const escapedInsights = escapeSQLString(record.insights);
const escapedHeadline = escapeSQLString(record.headline);

finalUpdateQuery += `
INSERT INTO
\`${projectId}.${datasetId}_bq.insights\`
(insights, headline, table, date)
VALUES
("${escapedInsights}",
"${escapedHeadline}",
"${record.table}",
CURRENT_DATE());`;
}
console.log(finalUpdateQuery);
return finalUpdateQuery;
}

async function insertIntoInsights(data) {
const query = getInsertQueryForInsights(data);
console.log(
`Inserting into insights: ${data.length} records`
);
return await executeQuery(query);
}


async function executeQuery(query) {
const options = {
configuration: {
query: {
query,
useLegacySql: false,
},
},
};

// Run the query as a job
const response = await bigquery.createJob(options);
const job = response[0];
const [rows] = await job.getQueryResults(job);

console.log(`${rows.length} returned`);
return rows;
}

module.exports = {
getData,
insertIntoInsights,
getInsertQueryForInsights
};
84 changes: 84 additions & 0 deletions ai_bubbles/bq.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const impl = require("./bq");

describe("getInsertQueryForInsights", () => {
test(`GIVEN 2 data objects
WHEN function is called with them
THEN it should return a string
with exactly 2 INSERT queries
to the insights table`, () => {
//Arrange: GIVEN
const data = [
{
insights: "test1",
table: "anything1"
},
{
insights: "test2",
table: "anything2"
}
]

//Act: WHEN
const result =
impl.getInsertQueryForInsights(data)

//Assert: THEN
expect(result.match(/\.insights/g).length).toBe(2)
expect(result.match(/INSERT/g).length).toBe(2)
})

test(`GIVEN 2 data objects
WHEN function is called with them
THEN it should return a string
with exactly 2 INSERT queries
to the insights table`, () => {
//Arrange: GIVEN
const data = [
{
insights: "test1"
// "table" prop missing
}
]

//Act: WHEN
const result = () => impl.getInsertQueryForInsights(data);

//Assert: THEN
expect(result).toThrow('missing data for insert');
})

test(`GIVEN 2 data objects
WHEN function is called with them
THEN it should return a string
with exactly 2 INSERT queries
to the insights table`, () => {
//Arrange: GIVEN
const data = [
{
table: "test1"
// "insights" prop missing
}
]

//Act: WHEN
const result = () => impl.getInsertQueryForInsights(data);

//Assert: THEN
expect(result).toThrow('missing data for insert');
})

})
Loading
Loading