{"id":22353574,"url":"https://github.com/dp6/media-quality","last_synced_at":"2025-07-30T08:34:15.374Z","repository":{"id":36980089,"uuid":"471069749","full_name":"DP6/media-quality","owner":"DP6","description":null,"archived":false,"fork":false,"pushed_at":"2023-11-07T18:08:38.000Z","size":14981,"stargazers_count":2,"open_issues_count":20,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-03-26T08:33:31.003Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DP6.png","metadata":{"files":{"readme":"README-CLOUD-FUNCTION.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-03-17T17:00:15.000Z","updated_at":"2024-01-16T16:10:00.000Z","dependencies_parsed_at":"2023-11-07T19:38:20.648Z","dependency_job_id":null,"html_url":"https://github.com/DP6/media-quality","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":"DP6/template-default-initiative-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DP6%2Fmedia-quality","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DP6%2Fmedia-quality/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DP6%2Fmedia-quality/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DP6%2Fmedia-quality/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DP6","download_url":"https://codeload.github.com/DP6/media-quality/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228114906,"owners_count":17871742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-04T13:09:02.740Z","updated_at":"2024-12-04T13:09:03.551Z","avatar_url":"https://github.com/DP6.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Configuração com o uso da Cloud Function\n\nPara enviar os dados dos eventos para o Big Query utilizando Cloud Functions é necessário realizar os passos a seguir:\n\n- Criação de dataset e tabela no Big Query;\n- Criação de Cloud Function;\n- Adequação do custom template para envio de requisições para a Cloud Function.\n\n## Criação de dataset e tabela no Big Query\n\nPara criar a tabela acesse o GCP (Google Cloud Plataform) e crie um dataset com o nome `dp6_media_quality` e uma tabela com o nome `media-quality-raw`.\n\nAs colunas criadas na tabela são:\n\n| Nome da Coluna  | Descrição                                     |\n| --------------- | --------------------------------------------- |\n| client_id       | Client id do Google Analytics                 |\n| media_name      | Nome da mídia que foi disparada               |\n| tracking_id     | Id de acompanhamento da mídia disparada       |\n| media_event     | Nome do evento disparado                      |\n| tag_name        | Nome completo da tag disparada no GTM         |\n| status          | Status de disparo da tag                      |\n| datalayer_event | Nome do evento do DataLayer que acionou a tag |\n| timestamp       | Data e hora do registro                       |\n\nAo criar a tabela selecione a opção para realizar o particionamento diário dos dados utilizando a coluna `timestamp`. O código abaixo contém um JSON com o esquema da tabela criada.\n\n```javascript\n// Esquema da tabela criada no Big Query\n[\n  {\n    name: 'client_id',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'Client id do Google Analytics',\n    maxLength: '100',\n  },\n  {\n    name: 'media_name',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'Nome da midia que foi disparada',\n    maxLength: '100',\n  },\n  {\n    name: 'tracking_id',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'Id de acompanhamento da midia disparada',\n    maxLength: '100',\n  },\n  {\n    name: 'media_event',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'Nome do evento disparado',\n    maxLength: '100',\n  },\n  {\n    name: 'tag_id',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'ID da tag disparada no GTM',\n    maxLength: '100',\n  ,{\n    name: 'tag_name',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'Nome completo da tag disparada no GTM',\n    maxLength: '100',\n  },\n  {\n    name: 'status',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'Status de disparo da tag',\n    maxLength: '50',\n  },\n  {\n    name: 'datalayer_event',\n    type: 'STRING',\n    mode: 'NULLABLE',\n    description: 'Nome do evento do DataLayer que acionou a tag',\n    maxLength: '100',\n  },\n  {\n    name: 'timestamp',\n    type: 'TIMESTAMP',\n    mode: 'REQUIRED',\n    description: 'Data e hora do registro',\n  },\n];\n```\n\n\u003cbr\u003e\n\n## Criação de Cloud Function\n\nPara criar a Cloud Function acesse o [GCP](https://console.cloud.google.com/functions) (Google Cloud Plataform) e utilize código diponibilizado abaixo (index.js e package.json). Foram utilizados `Runtime: Node.js 16` e `Entry point: gtm_monitor`. É importante verificar se a Cloud Function está acessível, portanto, verifique a secção `Permissions` para habilitar as permissões necessárias. Para a criação da function foram usados os arquivos `index.js` e `package.json`.\n\nA function recebe uma requisição HTTP que pode conter dados em JSON ou uma URL com query params. Para selecionar uma das opções é preciso alterar o valor da constante `input_option` localizada nas primeiras linhas de código (no arquivo index.js).\n\nOs dados em formato JSON recebidos pela function estão no seguinte formato:\n\n```javascript\n{\n    \"client_id\": \"1101944939.1645464696\"\n    \"media_name\": \"media_name\",\n    \"tracking_id\": \"123\",\n    \"media_event\": \"media_event\",\n    \"tag_id\": \"3\"\n    \"tag_name\": \"tag_name\",\n    \"status\": \"status\",\n    \"datalayer_event\": \"datalayer_event\",\n    \"timestamp\": 1652359111.576\n}\n```\n\nCaso os dados recebidos pelo Cloud Function seja uma URL ela será do seguinte formato:\n\n```\nhttps://{{URL da Cloud Function}}/?client_id={{client_id}}\u0026media_name={{media_name}}\u0026tracking_id={{tracking_id}}\u0026media_event={{media_event}} ...\n```\n\nAs informações provenientes da URL são organizadas em um dicionário após a extração por meio de expressões regulares. Posteriormente os dados são enviados para o Big Query.\n\n**index.js**\n\n```javascript\n// Import the Google Cloud client library\nconst { BigQuery } = require('@google-cloud/bigquery');\nconst bigquery = new BigQuery();\n// Request origin allowed in cloud function\nvar request_origin = process.env.REQUEST_ORIGIN;\nrequest_origin = request_origin.split(',');\n\n// Select what kind of data req.body contains. If the data\n// comes from sendPixel method (used on GTM custom template) use \"url\" else use \"json\"\nconst input_option = 'json'; // url ou json\n\nasync function insertRowsAsStream(request, input_option) {\n  const datasetId = 'dp6_media_quality';\n  const tableId = 'media-quality-raw';\n  var json_data;\n  var json_data_raw;\n\n  if (input_option == 'url') {\n    const url = decodeURI(request.protocol + '://' + request.get('host') + request.originalUrl);\n\n    json_data = {\n      client_id: url.match('client_id=([^\u0026]+)')[1],\n      media_name: url.match('media_name=([^\u0026]+)')[1],\n      tracking_id: url.match('tracking_id=([^\u0026]+)')[1],\n      media_event: url.match('media_event=([^\u0026]+)')[1],\n      tag_id: url.match('tag_id=([^\u0026]+)')[1],\n      tag_name: url.match('tag_name=([^\u0026]+)')[1],\n      status: url.match('status=([^\u0026]+)')[1],\n      datalayer_event: url.match('datalayer_event=([^\u0026]+)')[1],\n      timestamp: Date.now() / 1000,\n      page: url.match('page=([^\u0026]+)')[1],\n      container_version: url.match('container_version=([^\u0026]+)')[1],\n    };\n  }\n\n  if (input_option == 'json') {\n    try {\n      // Parse a JSON\n      json_data_raw = JSON.parse(request.body);\n    } catch (e) {\n      json_data_raw = request.body;\n    }\n\n    json_data_raw['timestamp'] = Date.now() / 1000;\n\n    lst_allowed_fields = [\n      'client_id',\n      'media_name',\n      'tracking_id',\n      'media_event',\n      'tag_id',\n      'tag_name',\n      'status',\n      'datalayer_event',\n      'timestamp',\n      'page',\n      'container_version',\n    ];\n    json_data = Object.fromEntries(Object.entries(json_data_raw).filter(([key]) =\u003e lst_allowed_fields.includes(key)));\n  }\n\n  // Insert data into a table\n  await bigquery.dataset(datasetId).table(tableId).insert(json_data);\n}\n\nexports.gtm_monitor = (req, res) =\u003e {\n  if (req.body \u0026\u0026 request_origin.includes(req.headers.origin)) {\n    insertRowsAsStream(req, input_option);\n    res.sendStatus(200);\n  } else {\n    console.log('Requisição inválida. Verifique o payload ou a variável REQUEST_ORIGIN...');\n    res.sendStatus(403);\n  }\n};\n```\n\n**package.json**\n\n```javascript\n{\n    \"name\": \"dp6-cf-media-quality\",\n    \"version\": \"1.0.0\",\n    \"description\": \"envia dados para o bigquery atraves de cloud function\",\n    \"author\": \"dp6\",\n    \"dependencies\": {\n      \"@google-cloud/bigquery\": \"^2.1.0\"\n    },\n    \"license\": \"ISC\"\n  }\n```\n\n\u003cbr\u003e\n\n## Adequação do custom template para envio de requisições para a Cloud Function\n\nExistem duas maneiras de enviar os dados para a Cloud Function, uma utilizando o método `sendPixel` e a outra utizando `Fetch`.\n\n### Opção 1: sendPixel\n\nO sendPixel é utilizado para realizar requisições do tipo GET. Ela recebe como parâmetro uma URL que é composta por `URL = endpoint + query params`. O endpoint é a URL da Cloud Function enquanto que os query params contém os dados de mídia que serão enviados para a Cloud Function.\n\n### Opção 2: Fetch\n\nO `fetch` permite realizar requisições do tipo POST e o envio de dados no formato JSON. No GTM deve-se criar uma variável do tipo custom javascript e inserir a função responsável pela requisição. Na tag do GTM o campo `sendFetchReference` deve ser preenchido com a variável criada.\n\n**Código javascript utilizado no template de Media Quality (GTM)**\n\n```javascript\n...\n\nconst encodeUri = require('encodeUri');\nconst sendPixel = require('sendPixel');\nconst sendRequestFetch = data.sendFetchReference;\n\n...\nfunction sendToCF(method) {\n\n    const endpoint = data.cfEndpoint;\n    const event = readFromDataLayer('event');\n    const fetch = data.fetchReference;\n\n    addEventCallback(function(containerId, eventData) {\n\n        const tagData = eventData.tags.filter(t =\u003e t.exclude === 'false');\n        for (let i in tagData) {\n\n            let entry = tagData[i];\n            let body = {\n                client_id: data.clientId,\n                media_name: data.autoCollect ? entry.media_name : entry.name.split(' - ')[0].split(' (')[0],\n                tracking_id: entry.tracking_id,\n                media_event: data.autoCollect ? entry.media_event : entry.name.split(' - ')[1],\n                tag_name: entry.name,\n                status: entry.status,\n                datalayer_event: event\n            };\n            for (let j in data.params) {\n                let name = data.params[j].param;\n                let value = data.params[j].value;\n                body[name] = value;\n            }\n\n            //Send data via GET method\n            if (method == 'get') {\n                var url = \"\";\n                for (let item in body) {\n                    url += '\u0026' + item + '=' + body[item];\n                }\n                url = endpoint+ \"/?\" + encodeUri(url);\n                sendPixel(url,null,null);\n            }\n            //Send data via POST method\n            else if (method == 'post') {\n                fetch(endpoint, body);\n            }\n        }\n    });\n}\n\n...\n```\n\n**Função Fetch (utilizada na custom javascript variable do GTM)**\n\n```javascript\nfunction(){\n  function CustomFetch(endpoint, payload){\n    fetch(endpoint, {\n    method: \"POST\",\n    mode: 'no-cors',\n    body: JSON.stringify(payload),\n    headers: {'Content-Type': 'application/json'}\n    });\n  }\n  return CustomFetch;\n}\n```\n\n\u003cbr\u003e\n\n## Imagens da Implementação da Cloud Function\n\nPara criar a Cloud function acesse o console do Google Cloud e clique em `Create Function` (Figura 1).\n\u003cimg src=\"./documentation-images/nova-cloud-function.PNG\" height=\"auto\" /\u003e\n\n\u003cfigcaption\u003eFigura 1 - Preenchimento do campo Endpoint com URL da Cloud Function\u003c/figcaption\u003e\n\u003c/div\u003e\n\nNa etapa de configuração selecione `Allow unauthenticated invocations` e marque `Require HTTPS`\n\n\u003cimg src=\"./documentation-images/requesttype-cloud-function.PNG\" height=\"auto\" /\u003e\n\u003cfigcaption\u003eFigura 2 - Preenchimento do campo Endpoint com URL da Cloud Function\u003c/figcaption\u003e\n\u003c/div\u003e\n\nCrie uma variável de ambiente com o nome `REQUEST_ORIGIN` e adicione as URLs das página web separadas por vígula (ex.: https://dp6.com.br,https://dp6.github.io). A Cloud Function apenas será disparada se a requisição for proveniente dos sites listados na variável `REQUEST_ORIGIN`.\n\n\u003cimg src=\"./documentation-images/requestorigin-cloud-function.PNG\" height=\"auto\" /\u003e\n\u003cfigcaption\u003eFigura 3 - Preenchimento do campo Endpoint com URL da Cloud Function\u003c/figcaption\u003e\n\u003c/div\u003e\n\nNa aba de permissões, `allUsers` deve possuir o papel `Cloud Functions Invoker`\n\n\u003cimg src=\"./documentation-images/permission-cloud-function.PNG\" height=\"auto\" /\u003e\n\u003cfigcaption\u003eFigura 4 - Preenchimento do campo Endpoint com URL da Cloud Function\u003c/figcaption\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n## Imagens da Implementação no GTM\n\n### Passo 1: Criação da variável javascript\n\nCriação de variável javascript com o código responsável pelas requisições HTTP. Caso seja utilizada a outra forma de envio de dados (sendPixel) não é necessário criar essa variável.\n\n\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"./documentation-images/custom-javascript-fetch.PNG\" height=\"auto\" /\u003e\n\u003cfigcaption\u003eFigura 5 - Criação de custom javascript com a função fetch.\u003c/figcaption\u003e\n\u003c/div\u003e\n\n### Passo 2: Configuração da TAG no GTM\n\nApós habilitar na tag o \"Endpoint de destino\" como Cloud Function deve-se inserir a URL de trigger da Cloud Function, a variável javascript criada anteriormente e um segredo (é o secret da cloud function) a ser adicionado na requisição HTTP, conforme o exemplo abaixo (Figura 2).\n\n\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"./documentation-images/tag-configuration-2.png\" height=\"auto\" /\u003e\n\u003cfigcaption\u003eFigura 6 - Preenchimento do campo Endpoint com URL da Cloud Function\u003c/figcaption\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdp6%2Fmedia-quality","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdp6%2Fmedia-quality","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdp6%2Fmedia-quality/lists"}