From 044231bed9728cb3100a6e71e1db80b04ae5f157 Mon Sep 17 00:00:00 2001 From: Christopher Grote Date: Mon, 14 Oct 2024 17:38:46 +0100 Subject: [PATCH 1/6] Adds detailed search log entries Signed-off-by: Christopher Grote --- .../src/main/kotlin/AdoptionExportCfg.kt | 4 + .../atlan/pkg/adoption/AdoptionExporter.kt | 12 +++ .../adoption/exports/DetailedUserChanges.kt | 53 ++++++++++++ .../pkg/adoption/exports/DetailedUserViews.kt | 41 ++++++++++ .../src/main/resources/package.pkl | 45 +++++++++- .../src/test/kotlin/ExportDetailsTest.kt | 80 ++++++++++++++++++ .../atlan/model/search/SearchLogRequest.java | 82 +++++++++++++++++-- 7 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt create mode 100644 samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt create mode 100644 samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt diff --git a/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt b/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt index 790d159fc7..a8210d7204 100644 --- a/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt +++ b/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt @@ -16,6 +16,9 @@ import javax.annotation.processing.Generated data class AdoptionExportCfg( @JsonProperty("include_views") val includeViews: String? = null, @JsonProperty("views_max") val viewsMax: Number? = null, + @JsonProperty("views_details") val viewsDetails: String? = null, + @JsonProperty("views_from") val viewsFrom: Long? = null, + @JsonProperty("views_to") val viewsTo: Long? = null, @JsonProperty("include_changes") val includeChanges: String? = null, @JsonDeserialize(using = WidgetSerde.MultiSelectDeserializer::class) @JsonSerialize(using = WidgetSerde.MultiSelectSerializer::class) @@ -26,6 +29,7 @@ data class AdoptionExportCfg( @JsonProperty("changes_from") val changesFrom: Long? = null, @JsonProperty("changes_to") val changesTo: Long? = null, @JsonProperty("changes_max") val changesMax: Number? = null, + @JsonProperty("changes_details") val changesDetails: String? = null, @JsonProperty("include_searches") val includeSearches: String? = null, @JsonProperty("maximum_searches") val maximumSearches: Number? = null, @JsonProperty("delivery_type") val deliveryType: String? = null, diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt index 765e5ff333..8e5b8344c3 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt @@ -8,6 +8,8 @@ import com.atlan.model.assets.Asset import com.atlan.pkg.Utils import com.atlan.pkg.adoption.exports.AssetChanges import com.atlan.pkg.adoption.exports.AssetViews +import com.atlan.pkg.adoption.exports.DetailedUserChanges +import com.atlan.pkg.adoption.exports.DetailedUserViews import com.atlan.pkg.serde.xls.ExcelWriter import mu.KotlinLogging import java.io.File @@ -33,7 +35,13 @@ object AdoptionExporter { ExcelWriter(exportFile).use { xlsx -> if (includeViews != "NONE") { val maxAssets = Utils.getOrDefault(config.viewsMax, 100).toInt() + val includeDetails = Utils.getOrDefault(config.viewsDetails, "NO") == "YES" + val start = Utils.getOrDefault(config.viewsFrom, -1).toLong() + val end = Utils.getOrDefault(config.viewsTo, -1).toLong() AssetViews(xlsx, logger, includeViews, maxAssets).export() + if (includeDetails) { + DetailedUserViews(xlsx, logger, start, end).export() + } } if (includeChanges) { val byUsers = Utils.getOrDefault(config.changesByUser, listOf()) @@ -41,7 +49,11 @@ object AdoptionExporter { val start = Utils.getOrDefault(config.changesFrom, -1).toLong() val end = Utils.getOrDefault(config.changesTo, -1).toLong() val maxAssets = Utils.getOrDefault(config.changesMax, 100).toInt() + val includeDetails = Utils.getOrDefault(config.changesDetails, "NO") == "YES" AssetChanges(xlsx, logger, byUsers, byAction, start, end, maxAssets).export() + if (includeDetails) { + DetailedUserChanges(xlsx, logger, start, end).export() + } } if (includeSearches) { val maxSearches = Utils.getOrDefault(config.maximumSearches, 50) diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt new file mode 100644 index 0000000000..58cc5c9579 --- /dev/null +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: Apache-2.0 + Copyright 2024 Atlan Pte. Ltd. */ +package com.atlan.pkg.adoption.exports + +import com.atlan.Atlan +import com.atlan.model.search.AuditSearch +import com.atlan.model.search.AuditSearchRequest +import com.atlan.pkg.serde.xls.ExcelWriter +import mu.KLogger + +class DetailedUserChanges( + private val xlsx: ExcelWriter, + private val logger: KLogger, + private val start: Long, + private val end: Long, +) { + fun export() { + logger.info { "Exporting details of all user-made changes between $start - $end..." } + val sheet = xlsx.createSheet("User changes") + xlsx.addHeader( + sheet, + mapOf( + "Time" to "Time at which the change occurred", + "Username" to "User who made the change", + "Action" to "Type of change the user made", + "Asset type" to "Type of asset that was changed", + "GUID" to "Globally unique identifier of the asset that was changed", + ), + ) + val builder = + AuditSearch.builder(Atlan.getDefaultClient()) + .whereNot(AuditSearchRequest.AGENT.eq("workflow")) // Exclude crawled changes + if (start > 0) { + builder.where(AuditSearchRequest.CREATED.gte(start)) + } + if (end > 0) { + builder.where(AuditSearchRequest.CREATED.lt(end)) + } + builder.stream() + .forEach { + xlsx.appendRow( + sheet, + listOf( + it.timestamp ?: "", + it.user ?: "", + it.action?.value ?: "", + it.typeName ?: "", + it.entityId ?: "", + ), + ) + } + } +} diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt new file mode 100644 index 0000000000..04a6d85761 --- /dev/null +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: Apache-2.0 + Copyright 2024 Atlan Pte. Ltd. */ +package com.atlan.pkg.adoption.exports + +import com.atlan.model.search.SearchLogRequest +import com.atlan.pkg.serde.xls.ExcelWriter +import mu.KLogger + +class DetailedUserViews( + private val xlsx: ExcelWriter, + private val logger: KLogger, + private val start: Long, + private val end: Long, +) { + fun export() { + logger.info { "Exporting details of all asset views between $start - $end..." } + val sheet = xlsx.createSheet("User views") + xlsx.addHeader( + sheet, + mapOf( + "Time" to "Time at which the view / search occurred", + "Username" to "User who viewed / searched", + "Total" to "Total number of assets included", + "GUIDs" to "Globally unique identifiers for each asset that was included", + ), + ) + SearchLogRequest.views(start, end) + .stream() + .forEach { + xlsx.appendRow( + sheet, + listOf( + it.createdAt ?: it.timestamp ?: "", + it.userName ?: "", + it.resultsCount ?: "0", + it.resultGuidsAllowed ?: "", + ), + ) + } + } +} diff --git a/samples/packages/adoption-export/src/main/resources/package.pkl b/samples/packages/adoption-export/src/main/resources/package.pkl index 468dd69c06..20b4db3513 100644 --- a/samples/packages/adoption-export/src/main/resources/package.pkl +++ b/samples/packages/adoption-export/src/main/resources/package.pkl @@ -56,6 +56,33 @@ uiConfig { placeholderValue = 100 width = 4 } + ["views_details"] = new Radio { + title = "Include details" + possibleValues { + ["YES"] = "Yes" + ["NO"] = "No" + } + default = "NO" + helpText = "Include detailed results for every single user view of an asset?" + required = true + } + ["views_from"] = new DateInput { + title = "From date" + helpText = "Only extract views after the specified date (leave empty for all views)." + required = false + past = -90 // start as far back as 90 days ago + future = -1 // maximum from would be yesterday + defaultDay = -90 + width = 4 + } + ["views_to"] = new DateInput { + title = "To date" + helpText = "Only extract views before the specified date (leave empty for all views)." + required = false + past = -89 // maximum to would be 89 days ago + defaultDay = 0 // start with today + width = 4 + } } } ["Changes"] { @@ -122,7 +149,16 @@ uiConfig { placeholderValue = 100 width = 4 } - + ["changes_details"] = new Radio { + title = "Include details" + possibleValues { + ["YES"] = "Yes" + ["NO"] = "No" + } + default = "NO" + helpText = "Include detailed results for every single asset change?" + required = true + } } } ["Searches"] { @@ -197,6 +233,13 @@ uiConfig { whenInputs { ["include_views"] = "BY_VIEWS" } required { "views_max" } } + new UIRule { + whenInputs { ["views_details"] = "YES" } + required { + "views_from" + "views_to" + } + } new UIRule { whenInputs { ["include_changes"] = "YES" } required { diff --git a/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt new file mode 100644 index 0000000000..7d6af9fbf2 --- /dev/null +++ b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: Apache-2.0 + Copyright 2023 Atlan Pte. Ltd. */ +import com.atlan.pkg.PackageTest +import com.atlan.pkg.adoption.AdoptionExporter +import com.atlan.pkg.serde.xls.ExcelReader +import mu.KotlinLogging +import org.testng.Assert.assertFalse +import org.testng.Assert.assertTrue +import org.testng.annotations.Test +import java.io.File +import java.math.BigDecimal + +/** + * Test export of detailed view and change information. + */ +class ExportDetailsTest : PackageTest() { + override val logger = KotlinLogging.logger {} + + private val files = + listOf( + "debug.log", + "adoption-export.xlsx", + ) + + override fun setup() { + setup( + AdoptionExportCfg( + includeViews = "YES", + viewsMax = 100, + viewsDetails = "YES", + includeChanges = "YES", + changesMax = 100, + changesDetails = "YES", + ), + ) + AdoptionExporter.main(arrayOf(testDirectory)) + } + + @Test + fun filesCreated() { + validateFilesExist(files) + } + + @Test + fun hasExpectedSheets() { + val xlFile = "$testDirectory${File.separator}adoption-export.xlsx" + ExcelReader(xlFile).use { xlsx -> + assertTrue(xlsx.hasSheet("Views")) // there by default + assertTrue(xlsx.hasSheet("Changes")) + assertTrue(xlsx.hasSheet("User views")) + assertTrue(xlsx.hasSheet("User changes")) + } + } + + @Test + fun testChanges() { + val xlFile = "$testDirectory${File.separator}adoption-export.xlsx" + ExcelReader(xlFile).use { xlsx -> + val rows = xlsx.getRowsFromSheet("Changes") + assertTrue(rows.isNotEmpty()) + var lastCount = Int.MAX_VALUE + rows.forEach { row -> + assertFalse(row["Type"].isNullOrBlank()) + assertFalse(row["Qualified name"].isNullOrBlank()) + assertFalse(row["Name"].isNullOrBlank()) + assertFalse(row["Link"].isNullOrBlank()) + val changes = row["Total changes"] + assertFalse(changes.isNullOrBlank()) + val changeCount = BigDecimal(changes) + assertTrue(changeCount.toInt() <= lastCount) + lastCount = changeCount.toInt() + } + } + } + + @Test + fun errorFreeLog() { + validateErrorFreeLog() + } +} diff --git a/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java b/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java index 6366bd0c5a..74662ff67c 100644 --- a/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java +++ b/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java @@ -85,17 +85,60 @@ public class SearchLogRequest extends AtlanObject { * @return a request builder pre-configured with these criteria */ public static SearchLogRequestBuilder views(List excludeUsers) { + return views(-1, -1, excludeUsers); + } + + /** + * Start building a search log request for the views of assets. + * + * @param from timestamp after which to look for any asset view activity (inclusive) + * @param to timestamp up through which to look for any asset view activity (inclusive) + * @return a request builder pre-configured with these criteria + */ + public static SearchLogRequestBuilder views(long from, long to) { + return views(from, to, null); + } + + /** + * Start building a search log request for the views of assets. + * + * @param from timestamp after which to look for any asset view activity (inclusive) + * @param to timestamp up through which to look for any asset view activity (inclusive) + * @param excludeUsers list of usernames to exclude from the results + * @return a request builder pre-configured with these criteria + */ + public static SearchLogRequestBuilder views(long from, long to, List excludeUsers) { List exclusion = new ArrayList<>(EXCLUDE_USERS); if (excludeUsers != null) { exclusion.addAll(excludeUsers); } - Query viewedByGuid = FluentSearch._internal() + FluentSearch.FluentSearchBuilder builder = FluentSearch._internal() .where(SearchLogEntry.UTM_TAGS.eq(UTMTags.ACTION_ASSET_VIEWED)) .where(VIEWED) - .whereNot(SearchLogEntry.USER.in(exclusion)) - .build() - .toQuery(); - return SearchLogRequest.builder(viewedByGuid); + .whereNot(SearchLogEntry.USER.in(exclusion)); + if (from > 0 && to > 0) { + builder.where(FluentSearch._internal() + .whereSome(SearchLogEntry.LOGGED_AT.between(from, to)) + .whereSome(SearchLogEntry.SEARCHED_AT.between(from, to)) + .minSomes(1) + .build() + .toQuery()); + } else if (from > 0) { + builder.where(FluentSearch._internal() + .whereSome(SearchLogEntry.LOGGED_AT.gte(from)) + .whereSome(SearchLogEntry.SEARCHED_AT.gte(from)) + .minSomes(1) + .build() + .toQuery()); + } else if (to > 0) { + builder.where(FluentSearch._internal() + .whereSome(SearchLogEntry.LOGGED_AT.lte(to)) + .whereSome(SearchLogEntry.SEARCHED_AT.lte(to)) + .minSomes(1) + .build() + .toQuery()); + } + return SearchLogRequest.builder(builder.build().toQuery()); } /** @@ -412,6 +455,16 @@ public B sortBy(SortOptions option) { return this.dsl(dsl.toBuilder().sortOption(option).build()); } + /** + * Add multiple sort options to sort the resulting search log entries. + * + * @param options by which to sort the resulting entries, in order from first sort to final sort + * @return this search log request builder, with the updated sorting option(s) + */ + public B sorts(List options) { + return this.dsl(dsl.toBuilder().sort(options).build()); + } + /** * Add an aggregation on the search log entries. * @@ -423,6 +476,25 @@ public B aggregation(String key, Aggregation aggregation) { return this.dsl(dsl.toBuilder().aggregation(key, aggregation).build()); } + /** + * Add multiple aggregations on the search log entries. + * + * @param aggregations the aggregations to add on top of the results + * @return this search log request builder, with the additional aggregation(s) + */ + public B aggregations(Map aggregations) { + return this.dsl(dsl.toBuilder().aggregations(aggregations).build()); + } + + /** + * Remove all aggregations of the search log entries. + * + * @return this search log request builder, without any aggregations + */ + public B clearAggregations() { + return this.dsl(dsl.toBuilder().clearAggregations().build()); + } + /** * Return the total number of assets that will match the supplied criteria, * using the most minimal query possible (retrieves minimal data). From efb8b9d7170300ea64d2637c526b9ab78bad33b8 Mon Sep 17 00:00:00 2001 From: Christopher Grote Date: Mon, 14 Oct 2024 17:49:33 +0100 Subject: [PATCH 2/6] Exclude service accounts and internal asset types from adoption export Signed-off-by: Christopher Grote --- .../com/atlan/pkg/adoption/exports/AssetChanges.kt | 14 ++++++++------ .../pkg/adoption/exports/DetailedUserChanges.kt | 2 ++ .../java/com/atlan/model/search/AuditSearch.java | 4 +++- .../com/atlan/model/search/SearchLogRequest.java | 3 ++- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/AssetChanges.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/AssetChanges.kt index e954adc645..27edfc8f3e 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/AssetChanges.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/AssetChanges.kt @@ -22,11 +22,13 @@ class AssetChanges( private val end: Long, private val maxAssets: Int, ) { - private val excludeTypes = - listOf( - AuthService.TYPE_NAME, - AuthPolicy.TYPE_NAME, - ) + companion object { + val EXCLUDE_TYPES = + listOf( + AuthService.TYPE_NAME, + AuthPolicy.TYPE_NAME, + ) + } fun export() { logger.info { "Exporting changed assets..." } @@ -44,7 +46,7 @@ class AssetChanges( val client = Atlan.getDefaultClient() val builder = AuditSearch.builder(client) - .whereNot(AuditSearchRequest.ENTITY_TYPE.`in`(excludeTypes)) + .whereNot(AuditSearchRequest.ENTITY_TYPE.`in`(EXCLUDE_TYPES)) .aggregate("changes", AuditSearchRequest.ENTITY_ID.bucketBy(maxAssets)) if (users.isNotEmpty()) { builder.where(AuditSearchRequest.USER.`in`(users)) diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt index 58cc5c9579..85e71378cf 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt @@ -5,6 +5,7 @@ package com.atlan.pkg.adoption.exports import com.atlan.Atlan import com.atlan.model.search.AuditSearch import com.atlan.model.search.AuditSearchRequest +import com.atlan.pkg.adoption.exports.AssetChanges.Companion.EXCLUDE_TYPES import com.atlan.pkg.serde.xls.ExcelWriter import mu.KLogger @@ -29,6 +30,7 @@ class DetailedUserChanges( ) val builder = AuditSearch.builder(Atlan.getDefaultClient()) + .whereNot(AuditSearchRequest.ENTITY_TYPE.`in`(EXCLUDE_TYPES)) .whereNot(AuditSearchRequest.AGENT.eq("workflow")) // Exclude crawled changes if (start > 0) { builder.where(AuditSearchRequest.CREATED.gte(start)) diff --git a/sdk/src/main/java/com/atlan/model/search/AuditSearch.java b/sdk/src/main/java/com/atlan/model/search/AuditSearch.java index 244c295379..28fa750175 100644 --- a/sdk/src/main/java/com/atlan/model/search/AuditSearch.java +++ b/sdk/src/main/java/com/atlan/model/search/AuditSearch.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import lombok.Builder; import lombok.Singular; import lombok.experimental.SuperBuilder; @@ -46,7 +47,8 @@ public class AuditSearch extends CompoundQuery { Map aggregations; /** Number of results to retrieve per underlying API request. */ - Integer pageSize; + @Builder.Default + Integer pageSize = IndexSearchDSL.DEFAULT_PAGE_SIZE; /** Attributes to retrieve for the entity detail in each audit log entry. */ @Singular("includeOnResults") diff --git a/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java b/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java index 74662ff67c..48126ff091 100644 --- a/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java +++ b/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java @@ -115,7 +115,8 @@ public class SearchLogRequest extends AtlanObject { FluentSearch.FluentSearchBuilder builder = FluentSearch._internal() .where(SearchLogEntry.UTM_TAGS.eq(UTMTags.ACTION_ASSET_VIEWED)) .where(VIEWED) - .whereNot(SearchLogEntry.USER.in(exclusion)); + .whereNot(SearchLogEntry.USER.in(exclusion)) + .whereNot(SearchLogEntry.USER.startsWith("service-account-")); if (from > 0 && to > 0) { builder.where(FluentSearch._internal() .whereSome(SearchLogEntry.LOGGED_AT.between(from, to)) From 652ae989ce78368750345d974994b336a1287aa2 Mon Sep 17 00:00:00 2001 From: Christopher Grote Date: Mon, 14 Oct 2024 18:08:31 +0100 Subject: [PATCH 3/6] Refine details included in report Signed-off-by: Christopher Grote --- .../com/atlan/pkg/adoption/exports/DetailedUserChanges.kt | 6 +++++- .../com/atlan/pkg/adoption/exports/DetailedUserViews.kt | 2 +- .../adoption-export/src/test/kotlin/ExportDetailsTest.kt | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt index 85e71378cf..ef05033bf8 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt @@ -16,7 +16,7 @@ class DetailedUserChanges( private val end: Long, ) { fun export() { - logger.info { "Exporting details of all user-made changes between $start - $end..." } + logger.info { "Exporting details of all user-made changes between [$start, $end]..." } val sheet = xlsx.createSheet("User changes") xlsx.addHeader( sheet, @@ -26,6 +26,8 @@ class DetailedUserChanges( "Action" to "Type of change the user made", "Asset type" to "Type of asset that was changed", "GUID" to "Globally unique identifier of the asset that was changed", + "Agent" to "Mechanism through which the asset was changed", + "Details" to "Further details about the mechanism through which the asset was changed", ), ) val builder = @@ -48,6 +50,8 @@ class DetailedUserChanges( it.action?.value ?: "", it.typeName ?: "", it.entityId ?: "", + it.headers["x-atlan-agent"] ?: "UI", + it.headers["x-atlan-agent-id"] ?: "", ), ) } diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt index 04a6d85761..0a40a75025 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt @@ -13,7 +13,7 @@ class DetailedUserViews( private val end: Long, ) { fun export() { - logger.info { "Exporting details of all asset views between $start - $end..." } + logger.info { "Exporting details of all asset views between [$start, $end]..." } val sheet = xlsx.createSheet("User views") xlsx.addHeader( sheet, diff --git a/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt index 7d6af9fbf2..0995bbaced 100644 --- a/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt +++ b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt @@ -25,7 +25,7 @@ class ExportDetailsTest : PackageTest() { override fun setup() { setup( AdoptionExportCfg( - includeViews = "YES", + includeViews = "BY_VIEWS", viewsMax = 100, viewsDetails = "YES", includeChanges = "YES", From 7445d6d788292c0cabee8742fb11db654414c652 Mon Sep 17 00:00:00 2001 From: Christopher Grote Date: Mon, 14 Oct 2024 18:42:10 +0100 Subject: [PATCH 4/6] Allow inclusion of various levels of automations for changed assets Signed-off-by: Christopher Grote --- .../src/main/kotlin/AdoptionExportCfg.kt | 1 + .../atlan/pkg/adoption/AdoptionExporter.kt | 3 ++- .../adoption/exports/DetailedUserChanges.kt | 27 ++++++++++++++----- .../pkg/adoption/exports/DetailedUserViews.kt | 6 +++-- .../src/main/resources/package.pkl | 12 +++++++++ .../src/test/kotlin/ExportDetailsTest.kt | 1 + 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt b/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt index a8210d7204..cd474042b5 100644 --- a/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt +++ b/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt @@ -30,6 +30,7 @@ data class AdoptionExportCfg( @JsonProperty("changes_to") val changesTo: Long? = null, @JsonProperty("changes_max") val changesMax: Number? = null, @JsonProperty("changes_details") val changesDetails: String? = null, + @JsonProperty("changes_automations") val changesAutomations: String? = null, @JsonProperty("include_searches") val includeSearches: String? = null, @JsonProperty("maximum_searches") val maximumSearches: Number? = null, @JsonProperty("delivery_type") val deliveryType: String? = null, diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt index 8e5b8344c3..87687b81a8 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt @@ -52,7 +52,8 @@ object AdoptionExporter { val includeDetails = Utils.getOrDefault(config.changesDetails, "NO") == "YES" AssetChanges(xlsx, logger, byUsers, byAction, start, end, maxAssets).export() if (includeDetails) { - DetailedUserChanges(xlsx, logger, start, end).export() + val includeAutomations = Utils.getOrDefault(config.changesAutomations, "NONE") + DetailedUserChanges(xlsx, logger, start, end, includeAutomations).export() } } if (includeSearches) { diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt index ef05033bf8..2fa11b8afa 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt @@ -3,6 +3,7 @@ package com.atlan.pkg.adoption.exports import com.atlan.Atlan +import com.atlan.model.enums.AuditActionType import com.atlan.model.search.AuditSearch import com.atlan.model.search.AuditSearchRequest import com.atlan.pkg.adoption.exports.AssetChanges.Companion.EXCLUDE_TYPES @@ -14,6 +15,7 @@ class DetailedUserChanges( private val logger: KLogger, private val start: Long, private val end: Long, + private val includeAutomations: String, ) { fun export() { logger.info { "Exporting details of all user-made changes between [$start, $end]..." } @@ -24,8 +26,8 @@ class DetailedUserChanges( "Time" to "Time at which the change occurred", "Username" to "User who made the change", "Action" to "Type of change the user made", - "Asset type" to "Type of asset that was changed", - "GUID" to "Globally unique identifier of the asset that was changed", + "Type" to "Type of asset", + "Qualified name" to "Unique name of the asset", "Agent" to "Mechanism through which the asset was changed", "Details" to "Further details about the mechanism through which the asset was changed", ), @@ -33,7 +35,12 @@ class DetailedUserChanges( val builder = AuditSearch.builder(Atlan.getDefaultClient()) .whereNot(AuditSearchRequest.ENTITY_TYPE.`in`(EXCLUDE_TYPES)) - .whereNot(AuditSearchRequest.AGENT.eq("workflow")) // Exclude crawled changes + when (includeAutomations) { + "NONE" -> builder.whereNot(AuditSearchRequest.AGENT.`in`(listOf("sdk", "workflow"))) + "WFL" -> builder.whereNot(AuditSearchRequest.AGENT.eq("sdk")) + "SDK" -> builder.whereNot(AuditSearchRequest.AGENT.eq("workflow")) + else -> logger.info { " ... including ALL automations -- this could be a large amount of data (and take a LONG time)." } + } if (start > 0) { builder.where(AuditSearchRequest.CREATED.gte(start)) } @@ -42,6 +49,14 @@ class DetailedUserChanges( } builder.stream() .forEach { + val agent = + when (it.action) { + AuditActionType.PROPAGATED_ATLAN_TAG_ADD, + AuditActionType.PROPAGATED_ATLAN_TAG_UPDATE, + AuditActionType.PROPAGATED_ATLAN_TAG_DELETE, + -> "background" + else -> it.headers?.get("x-atlan-agent") ?: "UI" + } xlsx.appendRow( sheet, listOf( @@ -49,9 +64,9 @@ class DetailedUserChanges( it.user ?: "", it.action?.value ?: "", it.typeName ?: "", - it.entityId ?: "", - it.headers["x-atlan-agent"] ?: "UI", - it.headers["x-atlan-agent-id"] ?: "", + it.entityQualifiedName ?: "", + agent, + it.headers?.get("x-atlan-agent-id") ?: "", ), ) } diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt index 0a40a75025..ce009027d5 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt @@ -21,7 +21,8 @@ class DetailedUserViews( "Time" to "Time at which the view / search occurred", "Username" to "User who viewed / searched", "Total" to "Total number of assets included", - "GUIDs" to "Globally unique identifiers for each asset that was included", + "Type" to "Type(s) of asset that were viewed", + "Qualified name" to "Unique name(s) of the asset(s)", ), ) SearchLogRequest.views(start, end) @@ -33,7 +34,8 @@ class DetailedUserViews( it.createdAt ?: it.timestamp ?: "", it.userName ?: "", it.resultsCount ?: "0", - it.resultGuidsAllowed ?: "", + it.resultTypeNamesAllowed ?: "", + it.resultQualifiedNamesAllowed ?: "", ), ) } diff --git a/samples/packages/adoption-export/src/main/resources/package.pkl b/samples/packages/adoption-export/src/main/resources/package.pkl index 20b4db3513..20251d9b88 100644 --- a/samples/packages/adoption-export/src/main/resources/package.pkl +++ b/samples/packages/adoption-export/src/main/resources/package.pkl @@ -159,6 +159,18 @@ uiConfig { helpText = "Include detailed results for every single asset change?" required = true } + ["changes_automations"] = new Radio { + title = "Include automations" + possibleValues { + ["ALL"] = "All" + ["WFL"] = "Workflows" + ["SDK"] = "SDKs" + ["NONE"] = "None" + } + default = "NONE" + helpText = "Include detailed results for assets changed through the selected automation mechanisms." + required = false + } } } ["Searches"] { diff --git a/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt index 0995bbaced..c6c98e25b3 100644 --- a/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt +++ b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt @@ -31,6 +31,7 @@ class ExportDetailsTest : PackageTest() { includeChanges = "YES", changesMax = 100, changesDetails = "YES", + changesAutomations = "SDK", ), ) AdoptionExporter.main(arrayOf(testDirectory)) From 207a52fa78fa7e300838a6efacce6b7a51f11658 Mon Sep 17 00:00:00 2001 From: Christopher Grote Date: Mon, 14 Oct 2024 20:59:01 +0100 Subject: [PATCH 5/6] Adds extraction of user searches to adoption export Signed-off-by: Christopher Grote --- .../src/main/kotlin/AdoptionExportCfg.kt | 3 +- .../atlan/pkg/adoption/AdoptionExporter.kt | 7 +- .../pkg/adoption/exports/DetailedSearches.kt | 45 +++++++++++ .../src/main/resources/package.pkl | 23 ++++-- .../src/test/kotlin/ExportDetailsTest.kt | 2 + .../java/com/atlan/model/enums/UTMTags.java | 2 + .../model/search/IndexSearchRequest.java | 3 + .../atlan/model/search/SearchLogEntry.java | 6 ++ .../atlan/model/search/SearchLogRequest.java | 80 +++++++++++++++++++ 9 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedSearches.kt diff --git a/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt b/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt index cd474042b5..014da485ee 100644 --- a/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt +++ b/samples/packages/adoption-export/src/main/kotlin/AdoptionExportCfg.kt @@ -32,7 +32,8 @@ data class AdoptionExportCfg( @JsonProperty("changes_details") val changesDetails: String? = null, @JsonProperty("changes_automations") val changesAutomations: String? = null, @JsonProperty("include_searches") val includeSearches: String? = null, - @JsonProperty("maximum_searches") val maximumSearches: Number? = null, + @JsonProperty("searches_from") val searchesFrom: Long? = null, + @JsonProperty("searches_to") val searchesTo: Long? = null, @JsonProperty("delivery_type") val deliveryType: String? = null, @JsonProperty("email_addresses") val emailAddresses: String? = null, @JsonProperty("target_prefix") val targetPrefix: String? = null, diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt index 87687b81a8..d807dc5654 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt @@ -8,6 +8,7 @@ import com.atlan.model.assets.Asset import com.atlan.pkg.Utils import com.atlan.pkg.adoption.exports.AssetChanges import com.atlan.pkg.adoption.exports.AssetViews +import com.atlan.pkg.adoption.exports.DetailedSearches import com.atlan.pkg.adoption.exports.DetailedUserChanges import com.atlan.pkg.adoption.exports.DetailedUserViews import com.atlan.pkg.serde.xls.ExcelWriter @@ -57,9 +58,9 @@ object AdoptionExporter { } } if (includeSearches) { - val maxSearches = Utils.getOrDefault(config.maximumSearches, 50) - logger.error { "Search export is not yet implemented -- coming soon." } - // TODO: implement exports of searches + val start = Utils.getOrDefault(config.searchesFrom, -1).toLong() + val end = Utils.getOrDefault(config.searchesTo, -1).toLong() + DetailedSearches(xlsx, logger, start, end).export() } } diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedSearches.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedSearches.kt new file mode 100644 index 0000000000..5b2150a6d2 --- /dev/null +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedSearches.kt @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: Apache-2.0 + Copyright 2024 Atlan Pte. Ltd. */ +package com.atlan.pkg.adoption.exports + +import com.atlan.model.search.SearchLogRequest +import com.atlan.pkg.serde.xls.ExcelWriter +import mu.KLogger + +class DetailedSearches( + private val xlsx: ExcelWriter, + private val logger: KLogger, + private val start: Long, + private val end: Long, +) { + fun export() { + logger.info { "Exporting details of all UI-based searches between [$start, $end]..." } + val sheet = xlsx.createSheet("User searches") + xlsx.addHeader( + sheet, + mapOf( + "Time" to "Time at which the search occurred", + "Username" to "User who searched", + "Query" to "Text the user entered for the search (if empty, the search was the result of filtering by some facet)", + "Total" to "Total number of assets included", + "Types" to "Type(s) of the first 20 assets that were found", + "Qualified names" to "Unique name(s) of the first 20 assets that were found", + ), + ) + SearchLogRequest.searches(start, end) + .stream() + .forEach { + xlsx.appendRow( + sheet, + listOf( + it.createdAt ?: it.timestamp ?: "", + it.userName ?: "", + it.searchInput ?: "", + it.resultsCount ?: "0", + it.resultTypeNamesAllowed ?: "", + it.resultQualifiedNamesAllowed ?: "", + ), + ) + } + } +} diff --git a/samples/packages/adoption-export/src/main/resources/package.pkl b/samples/packages/adoption-export/src/main/resources/package.pkl index 20251d9b88..07171a0fda 100644 --- a/samples/packages/adoption-export/src/main/resources/package.pkl +++ b/samples/packages/adoption-export/src/main/resources/package.pkl @@ -186,11 +186,21 @@ uiConfig { } default = "NO" } - ["maximum_searches"] = new NumericInput { - title = "Maximum" - helpText = "Maximum number of searches to include for the most-run searches." + ["searches_from"] = new DateInput { + title = "From date" + helpText = "Only extract searches after the specified date (leave empty for all changes)." + required = false + past = -90 // start as far back as 90 days ago + future = -1 // maximum from would be yesterday + defaultDay = -90 + width = 4 + } + ["searches_to"] = new DateInput { + title = "To date" + helpText = "Only extract searches before the specified date (leave empty for all changes)." required = false - placeholderValue = 50 + past = -89 // maximum to would be 89 days ago + defaultDay = 0 // start with today width = 4 } } @@ -264,7 +274,10 @@ uiConfig { } new UIRule { whenInputs { ["include_searches"] = "YES" } - required { "maximum_searches" } + required { + "searches_from" + "searches_to" + } } new UIRule { whenInputs { ["delivery_type"] = "EMAIL" } diff --git a/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt index c6c98e25b3..353f75e565 100644 --- a/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt +++ b/samples/packages/adoption-export/src/test/kotlin/ExportDetailsTest.kt @@ -32,6 +32,7 @@ class ExportDetailsTest : PackageTest() { changesMax = 100, changesDetails = "YES", changesAutomations = "SDK", + includeSearches = "YES", ), ) AdoptionExporter.main(arrayOf(testDirectory)) @@ -50,6 +51,7 @@ class ExportDetailsTest : PackageTest() { assertTrue(xlsx.hasSheet("Changes")) assertTrue(xlsx.hasSheet("User views")) assertTrue(xlsx.hasSheet("User changes")) + assertTrue(xlsx.hasSheet("User searches")) } } diff --git a/sdk/src/main/java/com/atlan/model/enums/UTMTags.java b/sdk/src/main/java/com/atlan/model/enums/UTMTags.java index 2f0297c4bc..ebe283dd27 100644 --- a/sdk/src/main/java/com/atlan/model/enums/UTMTags.java +++ b/sdk/src/main/java/com/atlan/model/enums/UTMTags.java @@ -37,6 +37,8 @@ public enum UTMTags implements AtlanEnum { ACTION_ASSET_VIEWED("action_asset_viewed"), /* Others indicate any special mechanisms used for the action. */ + /** Search was run using the UI searchbar. */ + UI_SEARCHBAR("ui_searchbar"), /** Search was run using the UI popup searchbar. */ UI_POPUP_SEARCHBAR("ui_popup_searchbar"), /** Search was through a UI filter (discovery). */ diff --git a/sdk/src/main/java/com/atlan/model/search/IndexSearchRequest.java b/sdk/src/main/java/com/atlan/model/search/IndexSearchRequest.java index 329ca2ecef..42b571cd14 100644 --- a/sdk/src/main/java/com/atlan/model/search/IndexSearchRequest.java +++ b/sdk/src/main/java/com/atlan/model/search/IndexSearchRequest.java @@ -143,5 +143,8 @@ public static final class Metadata extends AtlanObject { /** Tags to associate with the search request. */ @Singular List utmTags; + + /** Input in the searchbar when the search was run. */ + String searchInput; } } diff --git a/sdk/src/main/java/com/atlan/model/search/SearchLogEntry.java b/sdk/src/main/java/com/atlan/model/search/SearchLogEntry.java index 5ded9aff67..47704934b1 100644 --- a/sdk/src/main/java/com/atlan/model/search/SearchLogEntry.java +++ b/sdk/src/main/java/com/atlan/model/search/SearchLogEntry.java @@ -47,6 +47,9 @@ public class SearchLogEntry extends AtlanObject { /** Tag(s) that were sent in the search request. */ public static final KeywordField UTM_TAGS = new KeywordField(UNMAPPED, "utmTags"); + /** Text a user entered for their search. */ + public static final KeywordField SEARCH_INPUT = new KeywordField(UNMAPPED, "searchInput"); + /** Whether the search had any results (true) or not (false). */ public static final BooleanField HAS_RESULT = new BooleanField(UNMAPPED, "hasResult"); @@ -104,6 +107,9 @@ public class SearchLogEntry extends AtlanObject { /** List of the UTM tags that were sent in the search request. */ List utmTags; + /** Text a user entered for their search. */ + String searchInput; + /** Whether the search had any results (true) or not (false). */ Boolean hasResult; diff --git a/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java b/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java index 48126ff091..1b7ee4f815 100644 --- a/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java +++ b/sdk/src/main/java/com/atlan/model/search/SearchLogRequest.java @@ -46,6 +46,13 @@ public class SearchLogRequest extends AtlanObject { .build() .toQuery(); + public static final Query SEARCHED = FluentSearch._internal() + .whereSome(SearchLogEntry.UTM_TAGS.eq(UTMTags.UI_MAIN_LIST)) + .whereSome(SearchLogEntry.UTM_TAGS.eq(UTMTags.UI_SEARCHBAR)) + .minSomes(1) + .build() + .toQuery(); + /** * Build a search using the provided query and default options. * @@ -142,6 +149,79 @@ public class SearchLogRequest extends AtlanObject { return SearchLogRequest.builder(builder.build().toQuery()); } + /** + * Start building a search log request for searches run against assets. + * + * @return a request builder pre-configured with these criteria + */ + public static SearchLogRequestBuilder searches() { + return searches(null); + } + + /** + * Start building a search log request for searches run against assets. + * + * @param excludeUsers list of usernames to exclude from the results + * @return a request builder pre-configured with these criteria + */ + public static SearchLogRequestBuilder searches(List excludeUsers) { + return searches(-1, -1, excludeUsers); + } + + /** + * Start building a search log request for searches run against assets. + * + * @param from timestamp after which to look for any search activity (inclusive) + * @param to timestamp up through which to look for any search activity (inclusive) + * @return a request builder pre-configured with these criteria + */ + public static SearchLogRequestBuilder searches(long from, long to) { + return searches(from, to, null); + } + + /** + * Start building a search log request for searches run against assets. + * + * @param from timestamp after which to look for any search activity (inclusive) + * @param to timestamp up through which to look for any search activity (inclusive) + * @param excludeUsers list of usernames to exclude from the results + * @return a request builder pre-configured with these criteria + */ + public static SearchLogRequestBuilder searches(long from, long to, List excludeUsers) { + List exclusion = new ArrayList<>(EXCLUDE_USERS); + if (excludeUsers != null) { + exclusion.addAll(excludeUsers); + } + FluentSearch.FluentSearchBuilder builder = FluentSearch._internal() + .where(SearchLogEntry.UTM_TAGS.eq(UTMTags.ACTION_SEARCHED)) + .where(SEARCHED) + .whereNot(SearchLogEntry.USER.in(exclusion)) + .whereNot(SearchLogEntry.USER.startsWith("service-account-")); + if (from > 0 && to > 0) { + builder.where(FluentSearch._internal() + .whereSome(SearchLogEntry.LOGGED_AT.between(from, to)) + .whereSome(SearchLogEntry.SEARCHED_AT.between(from, to)) + .minSomes(1) + .build() + .toQuery()); + } else if (from > 0) { + builder.where(FluentSearch._internal() + .whereSome(SearchLogEntry.LOGGED_AT.gte(from)) + .whereSome(SearchLogEntry.SEARCHED_AT.gte(from)) + .minSomes(1) + .build() + .toQuery()); + } else if (to > 0) { + builder.where(FluentSearch._internal() + .whereSome(SearchLogEntry.LOGGED_AT.lte(to)) + .whereSome(SearchLogEntry.SEARCHED_AT.lte(to)) + .minSomes(1) + .build() + .toQuery()); + } + return SearchLogRequest.builder(builder.build().toQuery()); + } + /** * Start building a search log request for the views of an asset, by its GUID. * From b641c40837ac2c7ca204c8c58378e4f0e34d91d5 Mon Sep 17 00:00:00 2001 From: Christopher Grote Date: Mon, 14 Oct 2024 21:16:40 +0100 Subject: [PATCH 6/6] Add asset links to details exports Signed-off-by: Christopher Grote --- .../kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt | 2 +- .../atlan/pkg/adoption/exports/DetailedUserChanges.kt | 11 +++++++++++ .../atlan/pkg/adoption/exports/DetailedUserViews.kt | 4 ++++ .../adoption-export/src/main/resources/package.pkl | 5 +++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt index d807dc5654..8bf8598640 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/AdoptionExporter.kt @@ -54,7 +54,7 @@ object AdoptionExporter { AssetChanges(xlsx, logger, byUsers, byAction, start, end, maxAssets).export() if (includeDetails) { val includeAutomations = Utils.getOrDefault(config.changesAutomations, "NONE") - DetailedUserChanges(xlsx, logger, start, end, includeAutomations).export() + DetailedUserChanges(xlsx, logger, byUsers, byAction, start, end, includeAutomations).export() } } if (includeSearches) { diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt index 2fa11b8afa..c63a286724 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserChanges.kt @@ -6,6 +6,7 @@ import com.atlan.Atlan import com.atlan.model.enums.AuditActionType import com.atlan.model.search.AuditSearch import com.atlan.model.search.AuditSearchRequest +import com.atlan.pkg.Utils import com.atlan.pkg.adoption.exports.AssetChanges.Companion.EXCLUDE_TYPES import com.atlan.pkg.serde.xls.ExcelWriter import mu.KLogger @@ -13,6 +14,8 @@ import mu.KLogger class DetailedUserChanges( private val xlsx: ExcelWriter, private val logger: KLogger, + private val users: List, + private val actions: List, private val start: Long, private val end: Long, private val includeAutomations: String, @@ -30,11 +33,18 @@ class DetailedUserChanges( "Qualified name" to "Unique name of the asset", "Agent" to "Mechanism through which the asset was changed", "Details" to "Further details about the mechanism through which the asset was changed", + "Link" to "Link to the asset's profile page in Atlan", ), ) val builder = AuditSearch.builder(Atlan.getDefaultClient()) .whereNot(AuditSearchRequest.ENTITY_TYPE.`in`(EXCLUDE_TYPES)) + if (users.isNotEmpty()) { + builder.where(AuditSearchRequest.USER.`in`(users)) + } + if (actions.isNotEmpty()) { + builder.where(AuditSearchRequest.ACTION.`in`(actions)) + } when (includeAutomations) { "NONE" -> builder.whereNot(AuditSearchRequest.AGENT.`in`(listOf("sdk", "workflow"))) "WFL" -> builder.whereNot(AuditSearchRequest.AGENT.eq("sdk")) @@ -67,6 +77,7 @@ class DetailedUserChanges( it.entityQualifiedName ?: "", agent, it.headers?.get("x-atlan-agent-id") ?: "", + Utils.getAssetLink(it.entityId), ), ) } diff --git a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt index ce009027d5..97bb9d1020 100644 --- a/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt +++ b/samples/packages/adoption-export/src/main/kotlin/com/atlan/pkg/adoption/exports/DetailedUserViews.kt @@ -3,6 +3,7 @@ package com.atlan.pkg.adoption.exports import com.atlan.model.search.SearchLogRequest +import com.atlan.pkg.Utils import com.atlan.pkg.serde.xls.ExcelWriter import mu.KLogger @@ -23,11 +24,13 @@ class DetailedUserViews( "Total" to "Total number of assets included", "Type" to "Type(s) of asset that were viewed", "Qualified name" to "Unique name(s) of the asset(s)", + "Link" to "Link to the asset's profile page in Atlan", ), ) SearchLogRequest.views(start, end) .stream() .forEach { + val guid = it.resultGuidsAllowed?.get(0) ?: "" xlsx.appendRow( sheet, listOf( @@ -36,6 +39,7 @@ class DetailedUserViews( it.resultsCount ?: "0", it.resultTypeNamesAllowed ?: "", it.resultQualifiedNamesAllowed ?: "", + if (guid.isNotBlank()) Utils.getAssetLink(guid) else "", ), ) } diff --git a/samples/packages/adoption-export/src/main/resources/package.pkl b/samples/packages/adoption-export/src/main/resources/package.pkl index 07171a0fda..ca321738c9 100644 --- a/samples/packages/adoption-export/src/main/resources/package.pkl +++ b/samples/packages/adoption-export/src/main/resources/package.pkl @@ -270,8 +270,13 @@ uiConfig { "changes_from" "changes_to" "changes_max" + "changes_details" } } + new UIRule { + whenInputs { ["changes_details"] = "YES" } + required { "changes_automations" } + } new UIRule { whenInputs { ["include_searches"] = "YES" } required {