diff --git a/action.yml b/action.yml index 790625b..f7f487a 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,5 @@ name: "Atlan dbt Action" -description: "Whenever you make a change to a dbt model, Atlan will add downstream lienage impact context right in your pull requests." +description: "Whenever you make a change to a dbt model, Atlan will add downstream lineage impact context right in your pull requests." author: "Atlan" inputs: diff --git a/dist/index.js b/dist/index.js index a58951e..0db6e1f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -17510,273 +17510,6 @@ function fixResponseChunkedTransferBadEnding(request, errorCallback) { }); } -;// CONCATENATED MODULE: ./src/api/get-downstream-assets.js - - - - -main.config(); - -const ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; -const ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; - -async function getDownstreamAssets(guid) { - var myHeaders = { - authorization: `Bearer ${ATLAN_API_TOKEN}`, - "content-type": "application/json", - }; - - var raw = JSON.stringify({ - depth: 21, - guid: guid, - hideProcess: true, - allowDeletedProcess: false, - entityFilters: { - attributeName: "__state", - operator: "eq", - attributeValue: "ACTIVE", - }, - attributes: [ - "name", - "description", - "userDescription", - "sourceURL", - "qualifiedName", - "connectorName", - "certificateStatus", - "certificateUpdatedBy", - "certificateUpdatedAt", - "ownerUsers", - "ownerGroups", - "classificationNames", - "meanings", - ], - direction: "OUTPUT", - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; - - var response = await fetch( - `${ATLAN_INSTANCE_URL}/api/meta/lineage/getlineage`, - requestOptions - ).then((e) => e.json()); - - if (!response.relations) return null; - - const relations = response.relations.map(({ toEntityId }) => toEntityId); - - return relations - .filter((id, index) => relations.indexOf(id) === index) - .map((id) => response.guidEntityMap[id]); -} - -;// CONCATENATED MODULE: ./src/api/get-asset.js - - - - -main.config(); - -const get_asset_ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; -const get_asset_ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; - -async function getAsset({ name }) { - var myHeaders = { - Authorization: `Bearer ${get_asset_ATLAN_API_TOKEN}`, - "Content-Type": "application/json", - }; - - var raw = JSON.stringify({ - dsl: { - from: 0, - size: 1, - query: { - bool: { - must: [ - { - match: { - __state: "ACTIVE", - }, - }, - { - match: { - "__typeName.keyword": "DbtModel", - }, - }, - { - match: { - "name.keyword": name, - }, - }, - ], - }, - }, - }, - attributes: [ - "name", - "description", - "userDescription", - "sourceURL", - "qualifiedName", - "connectorName", - "certificateStatus", - "certificateUpdatedBy", - "certificateUpdatedAt", - "ownerUsers", - "ownerGroups", - "classificationNames", - "meanings", - "sqlAsset", - ], - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; - - var response = await fetch( - `${get_asset_ATLAN_INSTANCE_URL}/api/meta/search/indexsearch#findAssetByExactName`, - requestOptions - ).then((e) => e.json()); - - if (response?.entities?.length > 0) return response.entities[0]; - return null; -} - -// EXTERNAL MODULE: ./node_modules/uuid/dist/index.js -var uuid_dist = __nccwpck_require__(5840); -;// CONCATENATED MODULE: ./node_modules/uuid/wrapper.mjs - -const v1 = uuid_dist.v1; -const v3 = uuid_dist.v3; -const v4 = uuid_dist.v4; -const v5 = uuid_dist.v5; -const NIL = uuid_dist/* NIL */.zR; -const version = uuid_dist/* version */.i8; -const validate = uuid_dist/* validate */.Gu; -const stringify = uuid_dist/* stringify */.Pz; -const parse = uuid_dist/* parse */.Qc; - -;// CONCATENATED MODULE: ./src/api/create-resource.js - - - - - -main.config(); - -const create_resource_ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; -const create_resource_ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; - -async function createResource(guid, name, link) { - var myHeaders = { - Authorization: `Bearer ${create_resource_ATLAN_API_TOKEN}`, - "Content-Type": "application/json", - }; - - var raw = JSON.stringify({ - entities: [ - { - typeName: "Link", - attributes: { - qualifiedName: v4(), - name, - link, - tenantId: "default", - }, - relationshipAttributes: { - asset: { - guid, - }, - }, - }, - ], - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; - - var response = await fetch( - `${create_resource_ATLAN_INSTANCE_URL}/api/meta/entity/bulk`, - requestOptions - ).then((e) => e.json()); - - return response; -} - -;// CONCATENATED MODULE: ./src/api/segment.js - - - - - -main.config(); - -const segment_ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; -const segment_ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; - -async function sendSegmentEvent(action, properties) { - var myHeaders = { - authorization: `Bearer ${segment_ATLAN_API_TOKEN}`, - "content-type": "application/json", - }; - - var domain = new URL(segment_ATLAN_INSTANCE_URL).hostname; - - var raw = JSON.stringify({ - category: "integrations", - object: "github", - action, - properties: { - ...properties, - github_action_id: `https://github.com/${github.context.payload.repository.full_name}/actions/runs/${github.context.runId}`, - domain, - }, - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; - - var response = await fetch( - `${segment_ATLAN_INSTANCE_URL}/api/service/segment/track`, - requestOptions - ) - .then(() => { - console.log("send segment event", action, raw); - }) - .catch((err) => { - console.log("couldn't send segment event", err); - }); - - return response; -} - -;// CONCATENATED MODULE: ./src/api/index.js - - - - - ;// CONCATENATED MODULE: ./src/utils/get-image-url.js @@ -17920,57 +17653,57 @@ function getCertificationImage(certificationStatus) { main.config(); -const { IS_DEV } = process.env; -const create_comment_ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; +const {IS_DEV} = process.env; +const ATLAN_INSTANCE_URL = + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; async function createComment( - octokit, - context, - asset, - downstreamAssets + octokit, + context, + asset, + downstreamAssets ) { - const { pull_request } = context.payload; - - const rows = downstreamAssets.map( - ({ displayText, guid, typeName, attributes, meanings }) => { - const connectorImage = getConnectorImage(attributes.connectorName), - certificationImage = attributes?.certificateStatus - ? getCertificationImage(attributes?.certificateStatus) - : "", - readableTypeName = typeName - .toLowerCase() - .replace(attributes.connectorName, "") - .toUpperCase(); - - return [ - `${connectorImage} [${displayText}](${create_comment_ATLAN_INSTANCE_URL}/assets/${guid}) ${certificationImage}`, - `\`${readableTypeName}\``, - attributes?.userDescription || attributes?.description || "--", - attributes?.ownerUsers?.join(", ") || "--", - meanings - .map( - ({ displayText, termGuid }) => - `[${displayText}](${create_comment_ATLAN_INSTANCE_URL}/assets/${termGuid})` - ) - ?.join(", ") || "--", - attributes?.sourceURL || "--", - ]; - } - ); + const {pull_request} = context.payload; + + const rows = downstreamAssets.map( + ({displayText, guid, typeName, attributes, meanings}) => { + const connectorImage = getConnectorImage(attributes.connectorName), + certificationImage = attributes?.certificateStatus + ? getCertificationImage(attributes?.certificateStatus) + : "", + readableTypeName = typeName + .toLowerCase() + .replace(attributes.connectorName, "") + .toUpperCase(); + + return [ + `${connectorImage} [${displayText}](${ATLAN_INSTANCE_URL}/assets/${guid}) ${certificationImage}`, + `\`${readableTypeName}\``, + attributes?.userDescription || attributes?.description || "--", + attributes?.ownerUsers?.join(", ") || "--", + meanings + .map( + ({displayText, termGuid}) => + `[${displayText}](${ATLAN_INSTANCE_URL}/assets/${termGuid})` + ) + ?.join(", ") || "--", + attributes?.sourceURL || "--", + ]; + } + ); - const comment = ` + const comment = ` ## ${getConnectorImage(asset.attributes.connectorName)} [${ - asset.displayText - }](${create_comment_ATLAN_INSTANCE_URL}/assets/${asset.guid}) ${ - asset.attributes?.certificateStatus - ? getCertificationImage(asset.attributes.certificateStatus) - : "" - } + asset.displayText + }](${ATLAN_INSTANCE_URL}/assets/${asset.guid}) ${ + asset.attributes?.certificateStatus + ? getCertificationImage(asset.attributes.certificateStatus) + : "" + } \`${asset.typeName - .toLowerCase() - .replace(asset.attributes.connectorName, "") - .toUpperCase()}\` + .toLowerCase() + .replace(asset.attributes.connectorName, "") + .toUpperCase()}\` There are ${downstreamAssets.length} downstream asset(s). Name | Type | Description | Owners | Terms | Source URL @@ -17978,93 +17711,468 @@ async function createComment( ${rows.map((row) => row.join(" | ")).join("\n")} ${getImageURL( - "atlan-logo" - )} [View asset on Atlan.](${create_comment_ATLAN_INSTANCE_URL}/assets/${asset.guid})`; + "atlan-logo" + )} [View asset on Atlan.](${ATLAN_INSTANCE_URL}/assets/${asset.guid})`; - const commentObj = { - ...context.repo, - issue_number: pull_request.number, - body: comment, - }; + const commentObj = { + ...context.repo, + issue_number: pull_request.number, + body: comment, + }; + + console.log(comment) - if (IS_DEV) return comment; - return octokit.rest.issues.createComment(commentObj); + if (IS_DEV) return comment; + return octokit.rest.issues.createComment(commentObj); } async function createCustomComment(octokit, context, content) { - const { pull_request } = context.payload; - const commentObj = { - ...context.repo, - issue_number: pull_request.number, - body: content, - }; + const {pull_request} = context.payload; + const commentObj = { + ...context.repo, + issue_number: pull_request.number, + body: content, + }; - if (IS_DEV) return content; - return octokit.rest.issues.createComment(commentObj); -} + console.log(content) + if (IS_DEV) return content; + return octokit.rest.issues.createComment(commentObj); +} ;// CONCATENATED MODULE: ./src/utils/file-system.js async function getFileContents(octokit, context, filePath) { - const { repository, pull_request } = context.payload, - owner = repository.owner.login, - repo = repository.name, - head_sha = pull_request.head.sha; - - const res = await octokit.request( - `GET /repos/${owner}/${repo}/contents/${filePath}?ref=${head_sha}`, - { - owner, - repo, - path: filePath, - } - ), - buff = Buffer.from(res.data.content, "base64"); + const {repository, pull_request} = context.payload, + owner = repository.owner.login, + repo = repository.name, + head_sha = pull_request.head.sha; + + const res = await octokit.request( + `GET /repos/${owner}/${repo}/contents/${filePath}?ref=${head_sha}`, + { + owner, + repo, + path: filePath, + } + ), + buff = Buffer.from(res.data.content, "base64"); - return buff.toString("utf8"); + return buff.toString("utf8"); } async function getChangedFiles(octokit, context) { - const { repository, pull_request } = context.payload, - owner = repository.owner.login, - repo = repository.name, - pull_number = pull_request.number; - - const res = await octokit.request( - `GET /repos/${owner}/${repo}/pulls/${pull_number}/files`, - { - owner, - repo, - pull_number, + const {repository, pull_request} = context.payload, + owner = repository.owner.login, + repo = repository.name, + pull_number = pull_request.number; + + const res = await octokit.request( + `GET /repos/${owner}/${repo}/pulls/${pull_number}/files`, + { + owner, + repo, + pull_number, + } + ); + + return res.data + .map(({filename}) => { + const fileNameRegEx = /.*\/models\/.*\/(.*)\.sql/gm, + matches = fileNameRegEx.exec(filename); + if (matches) { + return { + fileName: matches[1], + filePath: filename, + }; + } + }) + .filter((i) => i !== undefined); +} + +async function getAssetName({octokit, context, fileName, filePath}) { + var regExp = /config\(.*alias=\'([^']+)\'.*\)/im; + var fileContents = await getFileContents(octokit, context, filePath); + var matches = regExp.exec(fileContents); + + if (matches) { + return matches[1]; } - ); - return res.data - .map(({ filename }) => { - const fileNameRegEx = /.*\/models\/.*\/(.*)\.sql/gm, - matches = fileNameRegEx.exec(filename); - if (matches) { - return { - name: matches[1], - filePath: filename, - }; - } + return fileName; +} + +;// CONCATENATED MODULE: ./src/utils/auth.js + + + + + +main.config(); + +const auth_ATLAN_INSTANCE_URL = + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; +const ATLAN_API_TOKEN = + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + +async function auth(octokit, context) { + var myHeaders = { + authorization: `Bearer ${ATLAN_API_TOKEN}`, + "content-type": "application/json", + }; + + var requestOptions = { + method: "POST", + headers: myHeaders, + }; + + var response = await fetch( + `${auth_ATLAN_INSTANCE_URL}/api/meta`, + requestOptions + ).catch((err) => { + }); + + if (response?.status === 401) { + await + createCustomComment(octokit, context, `We couldn't connect to your Atlan Instance, please make sure to set the valid Atlan Bearer Token as \`ATLAN_API_TOKEN\` as this repository's action secret. +Atlan Instance URL: ${auth_ATLAN_INSTANCE_URL} + +Set your repository action secrets [here](https://github.com/${context.payload.repository.full_name}/settings/secrets/actions). For more information on how to setup the Atlan dbt Action, please read the [setup documentation here](https://github.com/atlanhq/dbt-action/blob/main/README.md).`) + return false + } + + if (response === undefined) { + await + createCustomComment(octokit, context, `We couldn't connect to your Atlan Instance, please make sure to set the valid Atlan Instance URL as \`ATLAN_INSTANCE_URL\` as this repository's action secret. +Atlan Instance URL: ${auth_ATLAN_INSTANCE_URL} + +Make sure your Atlan Instance URL is set in the following format. +\`https://tenant.atlan.com\` + +Set your repository action secrets [here](https://github.com/${context.payload.repository.full_name}/settings/secrets/actions). For more information on how to setup the Atlan dbt Action, please read the [setup documentation here](https://github.com/atlanhq/dbt-action/blob/main/README.md).`) + return false + } + + return true +} +;// CONCATENATED MODULE: ./src/utils/index.js + + + + + + +;// CONCATENATED MODULE: ./src/api/get-downstream-assets.js + + + + + + +main.config(); + +const get_downstream_assets_ATLAN_INSTANCE_URL = + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; +const get_downstream_assets_ATLAN_API_TOKEN = + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + +async function getDownstreamAssets(asset, guid, octokit, context) { + var myHeaders = { + authorization: `Bearer ${get_downstream_assets_ATLAN_API_TOKEN}`, + "content-type": "application/json", + }; + + var raw = JSON.stringify({ + depth: 21, + guid: guid, + hideProcess: true, + allowDeletedProcess: false, + entityFilters: { + attributeName: "__state", + operator: "eq", + attributeValue: "ACTIVE", + }, + attributes: [ + "name", + "description", + "userDescription", + "sourceURL", + "qualifiedName", + "connectorName", + "certificateStatus", + "certificateUpdatedBy", + "certificateUpdatedAt", + "ownerUsers", + "ownerGroups", + "classificationNames", + "meanings", + ], + direction: "OUTPUT", + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; + + var handleError = async (err) => { + const comment = `## ${getConnectorImage(asset.attributes.connectorName)} [${ + asset.displayText + }](${get_downstream_assets_ATLAN_INSTANCE_URL}/assets/${asset.guid}) ${ + asset.attributes?.certificateStatus + ? getCertificationImage(asset.attributes.certificateStatus) + : "" + } + \`${asset.typeName + .toLowerCase() + .replace(asset.attributes.connectorName, "") + .toUpperCase()}\` + + ❌ Failed to fetch downstream impacted assets. + + [See lineage on Atlan.](${get_downstream_assets_ATLAN_INSTANCE_URL}/assets/${asset.guid}/lineage)\``; + + createCustomComment(octokit, context, comment) + + sendSegmentEvent("dbt_ci_action_failure", { + reason: 'failed_to_fetch_lineage', + asset_guid: asset.guid, + asset_name: asset.name, + asset_typeName: asset.typeName, + msg: err + }); + } + + var response = await fetch( + `${get_downstream_assets_ATLAN_INSTANCE_URL}/api/meta/lineage/getlineage`, + requestOptions + ).then((e) => e.json()).catch((err) => { + handleError(err) + }); + + if (!!response.error) { + handleError(response.error) + } + + if (!response?.relations) return []; + + const relations = response.relations.map(({toEntityId}) => toEntityId); + + return relations + .filter((id, index) => relations.indexOf(id) === index) + .map((id) => response.guidEntityMap[id]); +} + +;// CONCATENATED MODULE: ./src/api/get-asset.js + + + + + +main.config(); + +const get_asset_ATLAN_INSTANCE_URL = + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; +const get_asset_ATLAN_API_TOKEN = + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + +async function getAsset({name}) { + var myHeaders = { + Authorization: `Bearer ${get_asset_ATLAN_API_TOKEN}`, + "Content-Type": "application/json", + }; + + var raw = JSON.stringify({ + dsl: { + from: 0, + size: 1, + query: { + bool: { + must: [ + { + match: { + __state: "ACTIVE", + }, + }, + { + match: { + "__typeName.keyword": "DbtModel", + }, + }, + { + match: { + "name.keyword": name, + }, + }, + ], + }, + }, + }, + attributes: [ + "name", + "description", + "userDescription", + "sourceURL", + "qualifiedName", + "connectorName", + "certificateStatus", + "certificateUpdatedBy", + "certificateUpdatedAt", + "ownerUsers", + "ownerGroups", + "classificationNames", + "meanings", + "sqlAsset", + ], + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; + + var response = await fetch( + `${get_asset_ATLAN_INSTANCE_URL}/api/meta/search/indexsearch#findAssetByExactName`, + requestOptions + ).then((e) => e.json()).catch(err => { + sendSegmentEvent("dbt_ci_action_failure", { + reason: 'failed_to_get_asset', + asset_name: name, + msg: err + }); + }); + + if (response?.entities?.length > 0) return response.entities[0]; + return null; +} + +// EXTERNAL MODULE: ./node_modules/uuid/dist/index.js +var uuid_dist = __nccwpck_require__(5840); +;// CONCATENATED MODULE: ./node_modules/uuid/wrapper.mjs + +const v1 = uuid_dist.v1; +const v3 = uuid_dist.v3; +const v4 = uuid_dist.v4; +const v5 = uuid_dist.v5; +const NIL = uuid_dist/* NIL */.zR; +const version = uuid_dist/* version */.i8; +const validate = uuid_dist/* validate */.Gu; +const stringify = uuid_dist/* stringify */.Pz; +const parse = uuid_dist/* parse */.Qc; + +;// CONCATENATED MODULE: ./src/api/create-resource.js + + + + + + +main.config(); + +const create_resource_ATLAN_INSTANCE_URL = + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; +const create_resource_ATLAN_API_TOKEN = + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + +async function createResource(guid, name, link) { + var myHeaders = { + Authorization: `Bearer ${create_resource_ATLAN_API_TOKEN}`, + "Content-Type": "application/json", + }; + + var raw = JSON.stringify({ + entities: [ + { + typeName: "Link", + attributes: { + qualifiedName: v4(), + name, + link, + tenantId: "default", + }, + relationshipAttributes: { + asset: { + guid, + }, + }, + }, + ], + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; + + var response = await fetch( + `${create_resource_ATLAN_INSTANCE_URL}/api/meta/entity/bulk`, + requestOptions + ).then((e) => e.json()).catch(err => { + console.log(err) + sendSegmentEvent("dbt_ci_action_failure", { + reason: 'failed_to_create_resource', + asset_name: name, + msg: err + }); }) - .filter((i) => i !== undefined); + + return response; } -async function getAssetName(octokit, context, fileName, filePath) { - var regExp = /config\(.*alias=\'([^']+)\'.*\)/im; - var fileContents = await getFileContents(octokit, context, filePath); - var matches = regExp.exec(fileContents); +;// CONCATENATED MODULE: ./src/api/segment.js - if (matches) { - return matches[1]; - } - return fileName; + + + +main.config(); + +const segment_ATLAN_INSTANCE_URL = + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; +const segment_ATLAN_API_TOKEN = + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + +async function sendSegmentEvent(action, properties) { + var myHeaders = { + authorization: `Bearer ${segment_ATLAN_API_TOKEN}`, + "content-type": "application/json", + }; + + var domain = new URL(segment_ATLAN_INSTANCE_URL).hostname; + + var raw = JSON.stringify({ + category: "integrations", + object: "github", + action, + properties: { + ...properties, + github_action_id: `https://github.com/${github.context.payload.repository.full_name}/actions/runs/${github.context.runId}`, + domain, + }, + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; + + var response = await fetch( + `${segment_ATLAN_INSTANCE_URL}/api/service/segment/track`, + requestOptions + ) + .then(() => { + console.log("send segment event", action, raw); + }) + .catch((err) => { + console.log("couldn't send segment event", err); + }); + + return response; } -;// CONCATENATED MODULE: ./src/utils/index.js +;// CONCATENATED MODULE: ./src/api/index.js @@ -18074,80 +18182,89 @@ async function getAssetName(octokit, context, fileName, filePath) { -async function printDownstreamAssets({ octokit, context }) { - const changedFiles = await getChangedFiles(octokit, context); +async function printDownstreamAssets({octokit, context}) { + const changedFiles = await getChangedFiles(octokit, context); + var totalChangedFiles = 0 - if (changedFiles.length === 0) return; + if (changedFiles.length === 0) return; - for (const { name, filePath } of changedFiles) { - const assetName = await getAssetName(octokit, context, name, filePath); - const asset = await getAsset({ name: assetName }); + for (const {fileName, filePath} of changedFiles) { + const assetName = await getAssetName({octokit, context, fileName, filePath}); + const asset = await getAsset({name: assetName}); - if (!asset) return; + if (!asset) return; - const { guid } = asset.attributes.sqlAsset; - const timeStart = Date.now(); - const downstreamAssets = await getDownstreamAssets(guid); + const {guid} = asset.attributes.sqlAsset; + const timeStart = Date.now(); + const downstreamAssets = await getDownstreamAssets(asset, guid, octokit, context); - sendSegmentEvent("dbt_ci_action_downstream_unfurl", { - asset_guid: asset.guid, - asset_type: asset.typeName, - downstream_count: downstreamAssets.length, - total_fetch_time: Date.now() - timeStart, - }); + if (downstreamAssets.length === 0) continue; - const comment = await createComment( - octokit, - context, - asset, - downstreamAssets - ); - console.log(comment); - } + sendSegmentEvent("dbt_ci_action_downstream_unfurl", { + asset_guid: asset.guid, + asset_type: asset.typeName, + downstream_count: downstreamAssets.length, + total_fetch_time: Date.now() - timeStart, + }); + + await createComment( + octokit, + context, + asset, + downstreamAssets + ); + + totalChangedFiles++ + } - return changedFiles.length; + return totalChangedFiles; } ;// CONCATENATED MODULE: ./src/main/set-resource-on-asset.js -async function setResourceOnAsset({ octokit, context }) { - const changedFiles = await getChangedFiles(octokit, context); - const { pull_request } = context.payload; +async function setResourceOnAsset({octokit, context}) { + const changedFiles = await getChangedFiles(octokit, context); + const {pull_request} = context.payload; + var totalChangedFiles = 0 - if (changedFiles.length === 0) return; + if (changedFiles.length === 0) return; - changedFiles.forEach(async ({ name, filePath }) => { - const assetName = await getAssetName(octokit, context, name, filePath); - const asset = await getAsset({ name: assetName }); + changedFiles.forEach(async ({fileName, filePath}) => { + const assetName = await getAssetName(octokit, context, fileName, filePath); + const asset = await getAsset({name: assetName}); - if (!asset) return; + if (!asset) return; - const { guid: modelGuid } = asset; - const { guid: tableAssetGuid } = asset.attributes.sqlAsset; + const {guid: modelGuid} = asset; + const {guid: tableAssetGuid} = asset.attributes.sqlAsset; - await createResource( - modelGuid, - "Pull Request on GitHub", - pull_request.html_url - ); - await createResource( - tableAssetGuid, - "Pull Request on GitHub", - pull_request.html_url - ); - }); + await createResource( + modelGuid, + "Pull Request on GitHub", + pull_request.html_url + ); + await createResource( + tableAssetGuid, + "Pull Request on GitHub", + pull_request.html_url + ); - const comment = await createCustomComment( - octokit, - context, - `🎊 Congrats on the merge! + totalChangedFiles++ + }); + + const comment = await createCustomComment( + octokit, + context, + `🎊 Congrats on the merge! This pull request has been added as a resource to all the assets modified. ✅ ` - ); - console.log(comment); + ); + console.log(comment); + + return totalChangedFiles } ;// CONCATENATED MODULE: ./src/main/index.js @@ -18162,33 +18279,39 @@ This pull request has been added as a resource to all the assets modified. ✅ + main.config(); const GITHUB_TOKEN = core.getInput("GITHUB_TOKEN") || process.env.GITHUB_TOKEN; async function run() { - const timeStart = Date.now(); - const { context } = github; - const octokit = github.getOctokit(GITHUB_TOKEN); - const { pull_request } = context.payload; - const { state, merged } = pull_request; - - let total_assets = 0; - - if (state === "open") { - total_assets = await printDownstreamAssets({ octokit, context }); - } else if (state === "closed") { - if (merged) await setResourceOnAsset({ octokit, context }); - } + const timeStart = Date.now(); + const {context} = github; + const octokit = github.getOctokit(GITHUB_TOKEN); + const {pull_request} = context.payload; + const {state, merged} = pull_request; - sendSegmentEvent("dbt_ci_action_run", { - asset_count: total_assets, - total_time: Date.now() - timeStart, - }); + if (!await auth(octokit, context)) throw {message: 'Atlan Action Secrets not set properly.'} + + let total_assets = 0; + + if (state === "open") { + total_assets = await printDownstreamAssets({octokit, context}); + } else if (state === "closed") { + if (merged) total_assets = await setResourceOnAsset({octokit, context}); + } + + if (total_assets !== 0) + sendSegmentEvent("dbt_ci_action_run", { + asset_count: total_assets, + total_time: Date.now() - timeStart, + }); } -run().catch((e) => { - core.setFailed(e.message); +run().catch((err) => { + sendSegmentEvent("dbt_ci_action_failure", err); + + core.setFailed(err.message); }); })(); diff --git a/src/api/create-resource.js b/src/api/create-resource.js index 31ad385..86ad536 100644 --- a/src/api/create-resource.js +++ b/src/api/create-resource.js @@ -1,50 +1,58 @@ -import { v4 as uuidv4 } from "uuid"; +import {v4 as uuidv4} from "uuid"; import fetch from "node-fetch"; import core from "@actions/core"; import dotenv from "dotenv"; +import {sendSegmentEvent} from "./index.js"; dotenv.config(); const ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; const ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; export default async function createResource(guid, name, link) { - var myHeaders = { - Authorization: `Bearer ${ATLAN_API_TOKEN}`, - "Content-Type": "application/json", - }; + var myHeaders = { + Authorization: `Bearer ${ATLAN_API_TOKEN}`, + "Content-Type": "application/json", + }; - var raw = JSON.stringify({ - entities: [ - { - typeName: "Link", - attributes: { - qualifiedName: uuidv4(), - name, - link, - tenantId: "default", - }, - relationshipAttributes: { - asset: { - guid, - }, - }, - }, - ], - }); + var raw = JSON.stringify({ + entities: [ + { + typeName: "Link", + attributes: { + qualifiedName: uuidv4(), + name, + link, + tenantId: "default", + }, + relationshipAttributes: { + asset: { + guid, + }, + }, + }, + ], + }); - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; - var response = await fetch( - `${ATLAN_INSTANCE_URL}/api/meta/entity/bulk`, - requestOptions - ).then((e) => e.json()); + var response = await fetch( + `${ATLAN_INSTANCE_URL}/api/meta/entity/bulk`, + requestOptions + ).then((e) => e.json()).catch(err => { + console.log(err) + sendSegmentEvent("dbt_ci_action_failure", { + reason: 'failed_to_create_resource', + asset_name: name, + msg: err + }); + }) - return response; + return response; } diff --git a/src/api/get-asset.js b/src/api/get-asset.js index 17260b8..2748242 100644 --- a/src/api/get-asset.js +++ b/src/api/get-asset.js @@ -1,75 +1,82 @@ import fetch from "node-fetch"; import core from "@actions/core"; import dotenv from "dotenv"; +import {sendSegmentEvent} from "./index.js"; dotenv.config(); const ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; const ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; -export default async function getAsset({ name }) { - var myHeaders = { - Authorization: `Bearer ${ATLAN_API_TOKEN}`, - "Content-Type": "application/json", - }; +export default async function getAsset({name}) { + var myHeaders = { + Authorization: `Bearer ${ATLAN_API_TOKEN}`, + "Content-Type": "application/json", + }; - var raw = JSON.stringify({ - dsl: { - from: 0, - size: 1, - query: { - bool: { - must: [ - { - match: { - __state: "ACTIVE", - }, + var raw = JSON.stringify({ + dsl: { + from: 0, + size: 1, + query: { + bool: { + must: [ + { + match: { + __state: "ACTIVE", + }, + }, + { + match: { + "__typeName.keyword": "DbtModel", + }, + }, + { + match: { + "name.keyword": name, + }, + }, + ], + }, }, - { - match: { - "__typeName.keyword": "DbtModel", - }, - }, - { - match: { - "name.keyword": name, - }, - }, - ], }, - }, - }, - attributes: [ - "name", - "description", - "userDescription", - "sourceURL", - "qualifiedName", - "connectorName", - "certificateStatus", - "certificateUpdatedBy", - "certificateUpdatedAt", - "ownerUsers", - "ownerGroups", - "classificationNames", - "meanings", - "sqlAsset", - ], - }); + attributes: [ + "name", + "description", + "userDescription", + "sourceURL", + "qualifiedName", + "connectorName", + "certificateStatus", + "certificateUpdatedBy", + "certificateUpdatedAt", + "ownerUsers", + "ownerGroups", + "classificationNames", + "meanings", + "sqlAsset", + ], + }); - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; - var response = await fetch( - `${ATLAN_INSTANCE_URL}/api/meta/search/indexsearch#findAssetByExactName`, - requestOptions - ).then((e) => e.json()); + var response = await fetch( + `${ATLAN_INSTANCE_URL}/api/meta/search/indexsearch#findAssetByExactName`, + requestOptions + ).then((e) => e.json()).catch(err => { + sendSegmentEvent("dbt_ci_action_failure", { + reason: 'failed_to_get_asset', + asset_name: name, + msg: err + }); + }); - if (response?.entities?.length > 0) return response.entities[0]; - return null; + if (response?.entities?.length > 0) return response.entities[0]; + return null; } diff --git a/src/api/get-downstream-assets.js b/src/api/get-downstream-assets.js index 789b9ae..27fa88c 100644 --- a/src/api/get-downstream-assets.js +++ b/src/api/get-downstream-assets.js @@ -1,64 +1,100 @@ import fetch from "node-fetch"; import core from "@actions/core"; import dotenv from "dotenv"; +import {sendSegmentEvent} from "./index.js"; +import {createCustomComment, getConnectorImage, getCertificationImage} from "../utils/index.js"; dotenv.config(); const ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; const ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; -export default async function getDownstreamAssets(guid) { - var myHeaders = { - authorization: `Bearer ${ATLAN_API_TOKEN}`, - "content-type": "application/json", - }; +export default async function getDownstreamAssets(asset, guid, octokit, context) { + var myHeaders = { + authorization: `Bearer ${ATLAN_API_TOKEN}`, + "content-type": "application/json", + }; - var raw = JSON.stringify({ - depth: 21, - guid: guid, - hideProcess: true, - allowDeletedProcess: false, - entityFilters: { - attributeName: "__state", - operator: "eq", - attributeValue: "ACTIVE", - }, - attributes: [ - "name", - "description", - "userDescription", - "sourceURL", - "qualifiedName", - "connectorName", - "certificateStatus", - "certificateUpdatedBy", - "certificateUpdatedAt", - "ownerUsers", - "ownerGroups", - "classificationNames", - "meanings", - ], - direction: "OUTPUT", - }); + var raw = JSON.stringify({ + depth: 21, + guid: guid, + hideProcess: true, + allowDeletedProcess: false, + entityFilters: { + attributeName: "__state", + operator: "eq", + attributeValue: "ACTIVE", + }, + attributes: [ + "name", + "description", + "userDescription", + "sourceURL", + "qualifiedName", + "connectorName", + "certificateStatus", + "certificateUpdatedBy", + "certificateUpdatedAt", + "ownerUsers", + "ownerGroups", + "classificationNames", + "meanings", + ], + direction: "OUTPUT", + }); - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; - var response = await fetch( - `${ATLAN_INSTANCE_URL}/api/meta/lineage/getlineage`, - requestOptions - ).then((e) => e.json()); + var handleError = async (err) => { + const comment = `## ${getConnectorImage(asset.attributes.connectorName)} [${ + asset.displayText + }](${ATLAN_INSTANCE_URL}/assets/${asset.guid}) ${ + asset.attributes?.certificateStatus + ? getCertificationImage(asset.attributes.certificateStatus) + : "" + } + \`${asset.typeName + .toLowerCase() + .replace(asset.attributes.connectorName, "") + .toUpperCase()}\` + + ❌ Failed to fetch downstream impacted assets. + + [See lineage on Atlan.](${ATLAN_INSTANCE_URL}/assets/${asset.guid}/lineage)\``; - if (!response.relations) return null; + createCustomComment(octokit, context, comment) - const relations = response.relations.map(({ toEntityId }) => toEntityId); + sendSegmentEvent("dbt_ci_action_failure", { + reason: 'failed_to_fetch_lineage', + asset_guid: asset.guid, + asset_name: asset.name, + asset_typeName: asset.typeName, + msg: err + }); + } - return relations - .filter((id, index) => relations.indexOf(id) === index) - .map((id) => response.guidEntityMap[id]); + var response = await fetch( + `${ATLAN_INSTANCE_URL}/api/meta/lineage/getlineage`, + requestOptions + ).then((e) => e.json()).catch((err) => { + handleError(err) + }); + + if (!!response.error) { + handleError(response.error) + } + + if (!response?.relations) return []; + + const relations = response.relations.map(({toEntityId}) => toEntityId); + + return relations + .filter((id, index) => relations.indexOf(id) === index) + .map((id) => response.guidEntityMap[id]); } diff --git a/src/api/segment.js b/src/api/segment.js index 292e53d..1e0147d 100644 --- a/src/api/segment.js +++ b/src/api/segment.js @@ -1,50 +1,50 @@ import fetch from "node-fetch"; import core from "@actions/core"; import dotenv from "dotenv"; -import { context } from "@actions/github"; +import {context} from "@actions/github"; dotenv.config(); const ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; const ATLAN_API_TOKEN = - core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; export default async function sendSegmentEvent(action, properties) { - var myHeaders = { - authorization: `Bearer ${ATLAN_API_TOKEN}`, - "content-type": "application/json", - }; - - var domain = new URL(ATLAN_INSTANCE_URL).hostname; - - var raw = JSON.stringify({ - category: "integrations", - object: "github", - action, - properties: { - ...properties, - github_action_id: `https://github.com/${context.payload.repository.full_name}/actions/runs/${context.runId}`, - domain, - }, - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - }; - - var response = await fetch( - `${ATLAN_INSTANCE_URL}/api/service/segment/track`, - requestOptions - ) - .then(() => { - console.log("send segment event", action, raw); - }) - .catch((err) => { - console.log("couldn't send segment event", err); + var myHeaders = { + authorization: `Bearer ${ATLAN_API_TOKEN}`, + "content-type": "application/json", + }; + + var domain = new URL(ATLAN_INSTANCE_URL).hostname; + + var raw = JSON.stringify({ + category: "integrations", + object: "github", + action, + properties: { + ...properties, + github_action_id: `https://github.com/${context.payload.repository.full_name}/actions/runs/${context.runId}`, + domain, + }, }); - return response; + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + }; + + var response = await fetch( + `${ATLAN_INSTANCE_URL}/api/service/segment/track`, + requestOptions + ) + .then(() => { + console.log("send segment event", action, raw); + }) + .catch((err) => { + console.log("couldn't send segment event", err); + }); + + return response; } diff --git a/src/index.js b/src/index.js index 727a6db..dbef097 100644 --- a/src/index.js +++ b/src/index.js @@ -2,34 +2,40 @@ import dotenv from "dotenv"; import core from "@actions/core"; import github from "@actions/github"; -import { printDownstreamAssets, setResourceOnAsset } from "./main/index.js"; -import { sendSegmentEvent } from "./api/index.js"; +import {printDownstreamAssets, setResourceOnAsset} from "./main/index.js"; +import {sendSegmentEvent} from "./api/index.js"; +import {auth} from "./utils/index.js"; dotenv.config(); const GITHUB_TOKEN = core.getInput("GITHUB_TOKEN") || process.env.GITHUB_TOKEN; async function run() { - const timeStart = Date.now(); - const { context } = github; - const octokit = github.getOctokit(GITHUB_TOKEN); - const { pull_request } = context.payload; - const { state, merged } = pull_request; - - let total_assets = 0; - - if (state === "open") { - total_assets = await printDownstreamAssets({ octokit, context }); - } else if (state === "closed") { - if (merged) await setResourceOnAsset({ octokit, context }); - } - - sendSegmentEvent("dbt_ci_action_run", { - asset_count: total_assets, - total_time: Date.now() - timeStart, - }); + const timeStart = Date.now(); + const {context} = github; + const octokit = github.getOctokit(GITHUB_TOKEN); + const {pull_request} = context.payload; + const {state, merged} = pull_request; + + if (!await auth(octokit, context)) throw {message: 'Atlan Action Secrets not set properly.'} + + let total_assets = 0; + + if (state === "open") { + total_assets = await printDownstreamAssets({octokit, context}); + } else if (state === "closed") { + if (merged) total_assets = await setResourceOnAsset({octokit, context}); + } + + if (total_assets !== 0) + sendSegmentEvent("dbt_ci_action_run", { + asset_count: total_assets, + total_time: Date.now() - timeStart, + }); } -run().catch((e) => { - core.setFailed(e.message); +run().catch((err) => { + sendSegmentEvent("dbt_ci_action_failure", err); + + core.setFailed(err.message); }); diff --git a/src/main/print-downstream-assets.js b/src/main/print-downstream-assets.js index 817ea4e..6cd161b 100644 --- a/src/main/print-downstream-assets.js +++ b/src/main/print-downstream-assets.js @@ -1,44 +1,48 @@ import { - getAsset, - getDownstreamAssets, - sendSegmentEvent, + getAsset, + getDownstreamAssets, + sendSegmentEvent, } from "../api/index.js"; import { - createComment, - getChangedFiles, - getAssetName, + createComment, + getChangedFiles, + getAssetName, } from "../utils/index.js"; -export default async function printDownstreamAssets({ octokit, context }) { - const changedFiles = await getChangedFiles(octokit, context); +export default async function printDownstreamAssets({octokit, context}) { + const changedFiles = await getChangedFiles(octokit, context); + var totalChangedFiles = 0 - if (changedFiles.length === 0) return; + if (changedFiles.length === 0) return; - for (const { name, filePath } of changedFiles) { - const assetName = await getAssetName(octokit, context, name, filePath); - const asset = await getAsset({ name: assetName }); + for (const {fileName, filePath} of changedFiles) { + const assetName = await getAssetName({octokit, context, fileName, filePath}); + const asset = await getAsset({name: assetName}); - if (!asset) return; + if (!asset) return; - const { guid } = asset.attributes.sqlAsset; - const timeStart = Date.now(); - const downstreamAssets = await getDownstreamAssets(guid); + const {guid} = asset.attributes.sqlAsset; + const timeStart = Date.now(); + const downstreamAssets = await getDownstreamAssets(asset, guid, octokit, context); - sendSegmentEvent("dbt_ci_action_downstream_unfurl", { - asset_guid: asset.guid, - asset_type: asset.typeName, - downstream_count: downstreamAssets.length, - total_fetch_time: Date.now() - timeStart, - }); + if (downstreamAssets.length === 0) continue; - const comment = await createComment( - octokit, - context, - asset, - downstreamAssets - ); - console.log(comment); - } + sendSegmentEvent("dbt_ci_action_downstream_unfurl", { + asset_guid: asset.guid, + asset_type: asset.typeName, + downstream_count: downstreamAssets.length, + total_fetch_time: Date.now() - timeStart, + }); - return changedFiles.length; + await createComment( + octokit, + context, + asset, + downstreamAssets + ); + + totalChangedFiles++ + } + + return totalChangedFiles; } diff --git a/src/main/set-resource-on-asset.js b/src/main/set-resource-on-asset.js index 21fd131..f4d7620 100644 --- a/src/main/set-resource-on-asset.js +++ b/src/main/set-resource-on-asset.js @@ -1,44 +1,49 @@ -import { getAsset, createResource } from "../api/index.js"; +import {getAsset, createResource} from "../api/index.js"; import { - createCustomComment, - getChangedFiles, - getAssetName, + createCustomComment, + getChangedFiles, + getAssetName, } from "../utils/index.js"; -export default async function setResourceOnAsset({ octokit, context }) { - const changedFiles = await getChangedFiles(octokit, context); - const { pull_request } = context.payload; +export default async function setResourceOnAsset({octokit, context}) { + const changedFiles = await getChangedFiles(octokit, context); + const {pull_request} = context.payload; + var totalChangedFiles = 0 - if (changedFiles.length === 0) return; + if (changedFiles.length === 0) return; - changedFiles.forEach(async ({ name, filePath }) => { - const assetName = await getAssetName(octokit, context, name, filePath); - const asset = await getAsset({ name: assetName }); + changedFiles.forEach(async ({fileName, filePath}) => { + const assetName = await getAssetName(octokit, context, fileName, filePath); + const asset = await getAsset({name: assetName}); - if (!asset) return; + if (!asset) return; - const { guid: modelGuid } = asset; - const { guid: tableAssetGuid } = asset.attributes.sqlAsset; + const {guid: modelGuid} = asset; + const {guid: tableAssetGuid} = asset.attributes.sqlAsset; - await createResource( - modelGuid, - "Pull Request on GitHub", - pull_request.html_url - ); - await createResource( - tableAssetGuid, - "Pull Request on GitHub", - pull_request.html_url - ); - }); + await createResource( + modelGuid, + "Pull Request on GitHub", + pull_request.html_url + ); + await createResource( + tableAssetGuid, + "Pull Request on GitHub", + pull_request.html_url + ); + + totalChangedFiles++ + }); - const comment = await createCustomComment( - octokit, - context, - `🎊 Congrats on the merge! + const comment = await createCustomComment( + octokit, + context, + `🎊 Congrats on the merge! This pull request has been added as a resource to all the assets modified. ✅ ` - ); - console.log(comment); + ); + console.log(comment); + + return totalChangedFiles } diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..e644769 --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,52 @@ +import fetch from "node-fetch"; +import dotenv from "dotenv"; +import core from "@actions/core"; +import {createCustomComment} from "./create-comment.js"; + +dotenv.config(); + +const ATLAN_INSTANCE_URL = + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; +const ATLAN_API_TOKEN = + core.getInput("ATLAN_API_TOKEN") || process.env.ATLAN_API_TOKEN; + +export default async function auth(octokit, context) { + var myHeaders = { + authorization: `Bearer ${ATLAN_API_TOKEN}`, + "content-type": "application/json", + }; + + var requestOptions = { + method: "POST", + headers: myHeaders, + }; + + var response = await fetch( + `${ATLAN_INSTANCE_URL}/api/meta`, + requestOptions + ).catch((err) => { + }); + + if (response?.status === 401) { + await + createCustomComment(octokit, context, `We couldn't connect to your Atlan Instance, please make sure to set the valid Atlan Bearer Token as \`ATLAN_API_TOKEN\` as this repository's action secret. +Atlan Instance URL: ${ATLAN_INSTANCE_URL} + +Set your repository action secrets [here](https://github.com/${context.payload.repository.full_name}/settings/secrets/actions). For more information on how to setup the Atlan dbt Action, please read the [setup documentation here](https://github.com/atlanhq/dbt-action/blob/main/README.md).`) + return false + } + + if (response === undefined) { + await + createCustomComment(octokit, context, `We couldn't connect to your Atlan Instance, please make sure to set the valid Atlan Instance URL as \`ATLAN_INSTANCE_URL\` as this repository's action secret. +Atlan Instance URL: ${ATLAN_INSTANCE_URL} + +Make sure your Atlan Instance URL is set in the following format. +\`https://tenant.atlan.com\` + +Set your repository action secrets [here](https://github.com/${context.payload.repository.full_name}/settings/secrets/actions). For more information on how to setup the Atlan dbt Action, please read the [setup documentation here](https://github.com/atlanhq/dbt-action/blob/main/README.md).`) + return false + } + + return true +} \ No newline at end of file diff --git a/src/utils/create-comment.js b/src/utils/create-comment.js index 275b9b5..98558a8 100644 --- a/src/utils/create-comment.js +++ b/src/utils/create-comment.js @@ -2,64 +2,64 @@ import dotenv from "dotenv"; import core from "@actions/core"; import { - getConnectorImage, - getCertificationImage, - getImageURL, + getConnectorImage, + getCertificationImage, + getImageURL, } from "./index.js"; dotenv.config(); -const { IS_DEV } = process.env; +const {IS_DEV} = process.env; const ATLAN_INSTANCE_URL = - core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; + core.getInput("ATLAN_INSTANCE_URL") || process.env.ATLAN_INSTANCE_URL; export default async function createComment( - octokit, - context, - asset, - downstreamAssets + octokit, + context, + asset, + downstreamAssets ) { - const { pull_request } = context.payload; + const {pull_request} = context.payload; - const rows = downstreamAssets.map( - ({ displayText, guid, typeName, attributes, meanings }) => { - const connectorImage = getConnectorImage(attributes.connectorName), - certificationImage = attributes?.certificateStatus - ? getCertificationImage(attributes?.certificateStatus) - : "", - readableTypeName = typeName - .toLowerCase() - .replace(attributes.connectorName, "") - .toUpperCase(); + const rows = downstreamAssets.map( + ({displayText, guid, typeName, attributes, meanings}) => { + const connectorImage = getConnectorImage(attributes.connectorName), + certificationImage = attributes?.certificateStatus + ? getCertificationImage(attributes?.certificateStatus) + : "", + readableTypeName = typeName + .toLowerCase() + .replace(attributes.connectorName, "") + .toUpperCase(); - return [ - `${connectorImage} [${displayText}](${ATLAN_INSTANCE_URL}/assets/${guid}) ${certificationImage}`, - `\`${readableTypeName}\``, - attributes?.userDescription || attributes?.description || "--", - attributes?.ownerUsers?.join(", ") || "--", - meanings - .map( - ({ displayText, termGuid }) => - `[${displayText}](${ATLAN_INSTANCE_URL}/assets/${termGuid})` - ) - ?.join(", ") || "--", - attributes?.sourceURL || "--", - ]; - } - ); + return [ + `${connectorImage} [${displayText}](${ATLAN_INSTANCE_URL}/assets/${guid}) ${certificationImage}`, + `\`${readableTypeName}\``, + attributes?.userDescription || attributes?.description || "--", + attributes?.ownerUsers?.join(", ") || "--", + meanings + .map( + ({displayText, termGuid}) => + `[${displayText}](${ATLAN_INSTANCE_URL}/assets/${termGuid})` + ) + ?.join(", ") || "--", + attributes?.sourceURL || "--", + ]; + } + ); - const comment = ` + const comment = ` ## ${getConnectorImage(asset.attributes.connectorName)} [${ - asset.displayText - }](${ATLAN_INSTANCE_URL}/assets/${asset.guid}) ${ - asset.attributes?.certificateStatus - ? getCertificationImage(asset.attributes.certificateStatus) - : "" - } + asset.displayText + }](${ATLAN_INSTANCE_URL}/assets/${asset.guid}) ${ + asset.attributes?.certificateStatus + ? getCertificationImage(asset.attributes.certificateStatus) + : "" + } \`${asset.typeName - .toLowerCase() - .replace(asset.attributes.connectorName, "") - .toUpperCase()}\` + .toLowerCase() + .replace(asset.attributes.connectorName, "") + .toUpperCase()}\` There are ${downstreamAssets.length} downstream asset(s). Name | Type | Description | Owners | Terms | Source URL @@ -67,27 +67,31 @@ export default async function createComment( ${rows.map((row) => row.join(" | ")).join("\n")} ${getImageURL( - "atlan-logo" - )} [View asset on Atlan.](${ATLAN_INSTANCE_URL}/assets/${asset.guid})`; + "atlan-logo" + )} [View asset on Atlan.](${ATLAN_INSTANCE_URL}/assets/${asset.guid})`; + + const commentObj = { + ...context.repo, + issue_number: pull_request.number, + body: comment, + }; - const commentObj = { - ...context.repo, - issue_number: pull_request.number, - body: comment, - }; + console.log(comment) - if (IS_DEV) return comment; - return octokit.rest.issues.createComment(commentObj); + if (IS_DEV) return comment; + return octokit.rest.issues.createComment(commentObj); } export async function createCustomComment(octokit, context, content) { - const { pull_request } = context.payload; - const commentObj = { - ...context.repo, - issue_number: pull_request.number, - body: content, - }; + const {pull_request} = context.payload; + const commentObj = { + ...context.repo, + issue_number: pull_request.number, + body: content, + }; - if (IS_DEV) return content; - return octokit.rest.issues.createComment(commentObj); -} + console.log(content) + + if (IS_DEV) return content; + return octokit.rest.issues.createComment(commentObj); +} \ No newline at end of file diff --git a/src/utils/file-system.js b/src/utils/file-system.js index cbcd559..f55470e 100644 --- a/src/utils/file-system.js +++ b/src/utils/file-system.js @@ -1,59 +1,59 @@ export async function getFileContents(octokit, context, filePath) { - const { repository, pull_request } = context.payload, - owner = repository.owner.login, - repo = repository.name, - head_sha = pull_request.head.sha; - - const res = await octokit.request( - `GET /repos/${owner}/${repo}/contents/${filePath}?ref=${head_sha}`, - { - owner, - repo, - path: filePath, - } - ), - buff = Buffer.from(res.data.content, "base64"); - - return buff.toString("utf8"); + const {repository, pull_request} = context.payload, + owner = repository.owner.login, + repo = repository.name, + head_sha = pull_request.head.sha; + + const res = await octokit.request( + `GET /repos/${owner}/${repo}/contents/${filePath}?ref=${head_sha}`, + { + owner, + repo, + path: filePath, + } + ), + buff = Buffer.from(res.data.content, "base64"); + + return buff.toString("utf8"); } export async function getChangedFiles(octokit, context) { - const { repository, pull_request } = context.payload, - owner = repository.owner.login, - repo = repository.name, - pull_number = pull_request.number; - - const res = await octokit.request( - `GET /repos/${owner}/${repo}/pulls/${pull_number}/files`, - { - owner, - repo, - pull_number, - } - ); - - return res.data - .map(({ filename }) => { - const fileNameRegEx = /.*\/models\/.*\/(.*)\.sql/gm, - matches = fileNameRegEx.exec(filename); - if (matches) { - return { - name: matches[1], - filePath: filename, - }; - } - }) - .filter((i) => i !== undefined); + const {repository, pull_request} = context.payload, + owner = repository.owner.login, + repo = repository.name, + pull_number = pull_request.number; + + const res = await octokit.request( + `GET /repos/${owner}/${repo}/pulls/${pull_number}/files`, + { + owner, + repo, + pull_number, + } + ); + + return res.data + .map(({filename}) => { + const fileNameRegEx = /.*\/models\/.*\/(.*)\.sql/gm, + matches = fileNameRegEx.exec(filename); + if (matches) { + return { + fileName: matches[1], + filePath: filename, + }; + } + }) + .filter((i) => i !== undefined); } -export async function getAssetName(octokit, context, fileName, filePath) { - var regExp = /config\(.*alias=\'([^']+)\'.*\)/im; - var fileContents = await getFileContents(octokit, context, filePath); - var matches = regExp.exec(fileContents); +export async function getAssetName({octokit, context, fileName, filePath}) { + var regExp = /config\(.*alias=\'([^']+)\'.*\)/im; + var fileContents = await getFileContents(octokit, context, filePath); + var matches = regExp.exec(fileContents); - if (matches) { - return matches[1]; - } + if (matches) { + return matches[1]; + } - return fileName; + return fileName; } diff --git a/src/utils/index.js b/src/utils/index.js index 4385b13..9f97789 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,15 +1,18 @@ export { - getImageURL, - getConnectorImage, - getCertificationImage, + getImageURL, + getConnectorImage, + getCertificationImage, } from "./get-image-url.js"; -export { default as hostedImages } from "./hosted-images.js"; +export {default as hostedImages} from "./hosted-images.js"; export { - default as createComment, - createCustomComment, + default as createComment, + createCustomComment, } from "./create-comment.js"; export { - getFileContents, - getChangedFiles, - getAssetName, + getFileContents, + getChangedFiles, + getAssetName, } from "./file-system.js"; +export { + default as auth +} from "./auth.js"