From 94bcb79b8462738b3144d3800731c0c7066bad4c Mon Sep 17 00:00:00 2001 From: noggi Date: Thu, 31 Oct 2024 10:59:21 -0700 Subject: [PATCH 1/2] feat(ingestion): Add execution request cleanup job (#11765) --- .../bootstrapmcp/datahub-test-mcp.yaml | 8 +- .../docs/sources/gc/gc_recipe.dhub.yml | 11 + .../datahub/ingestion/source/gc/datahub_gc.py | 25 +- .../source/gc/execution_request_cleanup.py | 240 ++++++++++++++++++ .../src/main/resources/bootstrap_mcps.yaml | 4 +- .../bootstrap_mcps/ingestion-datahub-gc.yaml | 8 +- 6 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 metadata-ingestion/src/datahub/ingestion/source/gc/execution_request_cleanup.py diff --git a/datahub-upgrade/src/test/resources/bootstrapmcp/datahub-test-mcp.yaml b/datahub-upgrade/src/test/resources/bootstrapmcp/datahub-test-mcp.yaml index d049a807ac1d8..233db06d61c3f 100644 --- a/datahub-upgrade/src/test/resources/bootstrapmcp/datahub-test-mcp.yaml +++ b/datahub-upgrade/src/test/resources/bootstrapmcp/datahub-test-mcp.yaml @@ -23,7 +23,13 @@ keep_last_n: {{dataprocess_cleanup.keep_last_n}}{{^dataprocess_cleanup.keep_last_n}}5{{/dataprocess_cleanup.keep_last_n}} soft_deleted_entities_cleanup: retention_days: {{soft_deleted_entities_cleanup.retention_days}}{{^soft_deleted_entities_cleanup.retention_days}}10{{/soft_deleted_entities_cleanup.retention_days}} + execution_request_cleanup: + keep_history_min_count: {{execution_request_cleanup.keep_history_min_count}}{{^execution_request_cleanup.keep_history_min_count}}10{{/execution_request_cleanup.keep_history_min_count}} + keep_history_max_count: {{execution_request_cleanup.keep_history_max_count}}{{^execution_request_cleanup.keep_history_max_count}}1000{{/execution_request_cleanup.keep_history_max_count}} + keep_history_max_days: {{execution_request_cleanup.keep_history_max_days}}{{^execution_request_cleanup.keep_history_max_days}}30{{/execution_request_cleanup.keep_history_max_days}} + batch_read_size: {{execution_request_cleanup.batch_read_size}}{{^execution_request_cleanup.batch_read_size}}100{{/execution_request_cleanup.batch_read_size}} + enabled: {{execution_request_cleanup.enabled}}{{^execution_request_cleanup.enabled}}false{{/execution_request_cleanup.enabled}} extraArgs: {} debugMode: false executorId: default - headers: {} \ No newline at end of file + headers: {} diff --git a/metadata-ingestion/docs/sources/gc/gc_recipe.dhub.yml b/metadata-ingestion/docs/sources/gc/gc_recipe.dhub.yml index 21734cd4e03fa..05e5205f7da41 100644 --- a/metadata-ingestion/docs/sources/gc/gc_recipe.dhub.yml +++ b/metadata-ingestion/docs/sources/gc/gc_recipe.dhub.yml @@ -22,3 +22,14 @@ source: soft_deleted_entities_cleanup: # Delete soft deleted entities which were deleted 10 days ago retention_days: 10 + execution_request_cleanup: + # Minimum number of execution requests to keep, per ingestion source + keep_history_min_count: 10 + # Maximum number of execution requests to keep, per ingestion source + keep_history_max_count: 1000 + # Maximum number of days to keep execution requests for, per ingestion source + keep_history_max_days: 30 + # Number of records per read operation + batch_read_size: 100 + # Global switch for this cleanup task + enabled: true diff --git a/metadata-ingestion/src/datahub/ingestion/source/gc/datahub_gc.py b/metadata-ingestion/src/datahub/ingestion/source/gc/datahub_gc.py index 1897f3f288ec0..c4b4186f45fc3 100644 --- a/metadata-ingestion/src/datahub/ingestion/source/gc/datahub_gc.py +++ b/metadata-ingestion/src/datahub/ingestion/source/gc/datahub_gc.py @@ -24,6 +24,11 @@ DataProcessCleanupConfig, DataProcessCleanupReport, ) +from datahub.ingestion.source.gc.execution_request_cleanup import ( + DatahubExecutionRequestCleanup, + DatahubExecutionRequestCleanupConfig, + DatahubExecutionRequestCleanupReport, +) from datahub.ingestion.source.gc.soft_deleted_entity_cleanup import ( SoftDeletedEntitiesCleanup, SoftDeletedEntitiesCleanupConfig, @@ -70,9 +75,18 @@ class DataHubGcSourceConfig(ConfigModel): description="Configuration for soft deleted entities cleanup", ) + execution_request_cleanup: Optional[DatahubExecutionRequestCleanupConfig] = Field( + default=None, + description="Configuration for execution request cleanup", + ) + @dataclass -class DataHubGcSourceReport(DataProcessCleanupReport, SoftDeletedEntitiesReport): +class DataHubGcSourceReport( + DataProcessCleanupReport, + SoftDeletedEntitiesReport, + DatahubExecutionRequestCleanupReport, +): expired_tokens_revoked: int = 0 @@ -97,6 +111,7 @@ def __init__(self, ctx: PipelineContext, config: DataHubGcSourceConfig): self.graph = ctx.require_graph("The DataHubGc source") self.dataprocess_cleanup: Optional[DataProcessCleanup] = None self.soft_deleted_entities_cleanup: Optional[SoftDeletedEntitiesCleanup] = None + self.execution_request_cleanup: Optional[DatahubExecutionRequestCleanup] = None if self.config.dataprocess_cleanup: self.dataprocess_cleanup = DataProcessCleanup( @@ -109,6 +124,12 @@ def __init__(self, ctx: PipelineContext, config: DataHubGcSourceConfig): self.report, self.config.dry_run, ) + if self.config.execution_request_cleanup: + self.execution_request_cleanup = DatahubExecutionRequestCleanup( + config=self.config.execution_request_cleanup, + graph=self.graph, + report=self.report, + ) @classmethod def create(cls, config_dict, ctx): @@ -130,6 +151,8 @@ def get_workunits_internal( yield from self.dataprocess_cleanup.get_workunits_internal() if self.soft_deleted_entities_cleanup: self.soft_deleted_entities_cleanup.cleanup_soft_deleted_entities() + if self.execution_request_cleanup: + self.execution_request_cleanup.run() yield from [] def truncate_indices(self) -> None: diff --git a/metadata-ingestion/src/datahub/ingestion/source/gc/execution_request_cleanup.py b/metadata-ingestion/src/datahub/ingestion/source/gc/execution_request_cleanup.py new file mode 100644 index 0000000000000..570df4e99ab13 --- /dev/null +++ b/metadata-ingestion/src/datahub/ingestion/source/gc/execution_request_cleanup.py @@ -0,0 +1,240 @@ +import logging +import time +from typing import Any, Dict, Iterator, Optional + +from pydantic import BaseModel, Field + +from datahub.configuration.common import ConfigModel +from datahub.ingestion.api.source import SourceReport +from datahub.ingestion.graph.client import DataHubGraph + +logger = logging.getLogger(__name__) + +DATAHUB_EXECUTION_REQUEST_ENTITY_NAME = "dataHubExecutionRequest" +DATAHUB_EXECUTION_REQUEST_KEY_ASPECT_NAME = "dataHubExecutionRequestKey" +DATAHUB_EXECUTION_REQUEST_INPUT_ASPECT_NAME = "dataHubExecutionRequestInput" +DATAHUB_EXECUTION_REQUEST_RESULT_ASPECT_NAME = "dataHubExecutionRequestResult" + + +class DatahubExecutionRequestCleanupConfig(ConfigModel): + keep_history_min_count: int = Field( + 10, + description="Minimum number of execution requests to keep, per ingestion source", + ) + + keep_history_max_count: int = Field( + 1000, + description="Maximum number of execution requests to keep, per ingestion source", + ) + + keep_history_max_days: int = Field( + 30, + description="Maximum number of days to keep execution requests for, per ingestion source", + ) + + batch_read_size: int = Field( + 100, + description="Number of records per read operation", + ) + + enabled: bool = Field( + True, + description="Global switch for this cleanup task", + ) + + def keep_history_max_milliseconds(self): + return self.keep_history_max_days * 24 * 3600 * 1000 + + +class DatahubExecutionRequestCleanupReport(SourceReport): + execution_request_cleanup_records_read: int = 0 + execution_request_cleanup_records_preserved: int = 0 + execution_request_cleanup_records_deleted: int = 0 + execution_request_cleanup_read_errors: int = 0 + execution_request_cleanup_delete_errors: int = 0 + + +class CleanupRecord(BaseModel): + urn: str + request_id: str + status: str + ingestion_source: str + requested_at: int + + +class DatahubExecutionRequestCleanup: + def __init__( + self, + graph: DataHubGraph, + report: DatahubExecutionRequestCleanupReport, + config: Optional[DatahubExecutionRequestCleanupConfig] = None, + ) -> None: + + self.graph = graph + self.report = report + self.instance_id = int(time.time()) + + if config is not None: + self.config = config + else: + self.config = DatahubExecutionRequestCleanupConfig() + + def _to_cleanup_record(self, entry: Dict) -> CleanupRecord: + input_aspect = ( + entry.get("aspects", {}) + .get(DATAHUB_EXECUTION_REQUEST_INPUT_ASPECT_NAME, {}) + .get("value", {}) + ) + result_aspect = ( + entry.get("aspects", {}) + .get(DATAHUB_EXECUTION_REQUEST_RESULT_ASPECT_NAME, {}) + .get("value", {}) + ) + key_aspect = ( + entry.get("aspects", {}) + .get(DATAHUB_EXECUTION_REQUEST_KEY_ASPECT_NAME, {}) + .get("value", {}) + ) + return CleanupRecord( + urn=entry.get("urn"), + request_id=key_aspect.get("id"), + requested_at=input_aspect.get("requestedAt", 0), + status=result_aspect.get("status", "PENDING"), + ingestion_source=input_aspect.get("source", {}).get("ingestionSource", ""), + ) + + def _scroll_execution_requests( + self, overrides: Dict[str, Any] = {} + ) -> Iterator[CleanupRecord]: + headers: Dict[str, Any] = { + "Accept": "application/json", + "Content-Type": "application/json", + } + params = { + "aspectNames": [ + DATAHUB_EXECUTION_REQUEST_KEY_ASPECT_NAME, + DATAHUB_EXECUTION_REQUEST_INPUT_ASPECT_NAME, + DATAHUB_EXECUTION_REQUEST_RESULT_ASPECT_NAME, + ], + "count": str(self.config.batch_read_size), + "sort": "requestTimeMs", + "sortOrder": "DESCENDING", + "systemMetadata": "false", + "skipCache": "true", + } + params.update(overrides) + + while True: + try: + url = f"{self.graph.config.server}/openapi/v2/entity/{DATAHUB_EXECUTION_REQUEST_ENTITY_NAME}" + response = self.graph._session.get(url, headers=headers, params=params) + response.raise_for_status() + document = response.json() + + entries = document.get("results", []) + for entry in entries: + yield self._to_cleanup_record(entry) + + if "scrollId" not in document: + break + params["scrollId"] = document["scrollId"] + except Exception as e: + logger.error( + f"ergc({self.instance_id}): failed to fetch next batch of execution requests: {e}" + ) + self.report.execution_request_cleanup_read_errors += 1 + + def _scroll_garbage_records(self): + state: Dict[str, Dict] = {} + + now_ms = int(time.time()) * 1000 + running_guard_timeout = now_ms - 30 * 24 * 3600 * 1000 + + for entry in self._scroll_execution_requests(): + self.report.execution_request_cleanup_records_read += 1 + key = entry.ingestion_source + + # Always delete corrupted records + if not key: + logger.warning( + f"ergc({self.instance_id}): will delete corrupted entry with missing source key: {entry}" + ) + yield entry + continue + + if key not in state: + state[key] = {} + state[key]["cutoffTimestamp"] = ( + entry.requested_at - self.config.keep_history_max_milliseconds() + ) + + state[key]["count"] = state[key].get("count", 0) + 1 + + # Do not delete if number of requests is below minimum + if state[key]["count"] < self.config.keep_history_min_count: + self.report.execution_request_cleanup_records_preserved += 1 + continue + + # Do not delete if number of requests do not exceed allowed maximum, + # or the cutoff date. + if (state[key]["count"] < self.config.keep_history_max_count) and ( + entry.requested_at > state[key]["cutoffTimestamp"] + ): + self.report.execution_request_cleanup_records_preserved += 1 + continue + + # Do not delete if status is RUNNING or PENDING and created within last month. If the record is >month old and it did not + # transition to a final state within that timeframe, it likely has no value. + if entry.requested_at > running_guard_timeout and entry.status in [ + "RUNNING", + "PENDING", + ]: + self.report.execution_request_cleanup_records_preserved += 1 + continue + + # Otherwise delete current record + logger.info( + ( + f"ergc({self.instance_id}): going to delete {entry.request_id} in source {key}; " + f"source count: {state[key]['count']}; " + f"source cutoff: {state[key]['cutoffTimestamp']}; " + f"record timestamp: {entry.requested_at}." + ) + ) + self.report.execution_request_cleanup_records_deleted += 1 + yield entry + + def _delete_entry(self, entry: CleanupRecord) -> None: + try: + logger.info( + f"ergc({self.instance_id}): going to delete ExecutionRequest {entry.request_id}" + ) + self.graph.delete_entity(entry.urn, True) + except Exception as e: + self.report.execution_request_cleanup_delete_errors += 1 + logger.error( + f"ergc({self.instance_id}): failed to delete ExecutionRequest {entry.request_id}: {e}" + ) + + def run(self) -> None: + if not self.config.enabled: + logger.info( + f"ergc({self.instance_id}): ExecutionRequest cleaner is disabled." + ) + return + + logger.info( + ( + f"ergc({self.instance_id}): Starting cleanup of ExecutionRequest records; " + f"max days: {self.config.keep_history_max_days}, " + f"min records: {self.config.keep_history_min_count}, " + f"max records: {self.config.keep_history_max_count}." + ) + ) + + for entry in self._scroll_garbage_records(): + self._delete_entry(entry) + + logger.info( + f"ergc({self.instance_id}): Finished cleanup of ExecutionRequest records." + ) diff --git a/metadata-service/configuration/src/main/resources/bootstrap_mcps.yaml b/metadata-service/configuration/src/main/resources/bootstrap_mcps.yaml index 10ae176b2c31e..dda79120118e5 100644 --- a/metadata-service/configuration/src/main/resources/bootstrap_mcps.yaml +++ b/metadata-service/configuration/src/main/resources/bootstrap_mcps.yaml @@ -38,7 +38,7 @@ bootstrap: # Ingestion Recipes - name: ingestion-datahub-gc - version: v1 + version: v2 optional: true mcps_location: "bootstrap_mcps/ingestion-datahub-gc.yaml" - values_env: "DATAHUB_GC_BOOTSTRAP_VALUES" \ No newline at end of file + values_env: "DATAHUB_GC_BOOTSTRAP_VALUES" diff --git a/metadata-service/configuration/src/main/resources/bootstrap_mcps/ingestion-datahub-gc.yaml b/metadata-service/configuration/src/main/resources/bootstrap_mcps/ingestion-datahub-gc.yaml index e70ab1162a381..f30ce148ec6cb 100644 --- a/metadata-service/configuration/src/main/resources/bootstrap_mcps/ingestion-datahub-gc.yaml +++ b/metadata-service/configuration/src/main/resources/bootstrap_mcps/ingestion-datahub-gc.yaml @@ -27,7 +27,13 @@ keep_last_n: {{dataprocess_cleanup.keep_last_n}}{{^dataprocess_cleanup.keep_last_n}}5{{/dataprocess_cleanup.keep_last_n}} soft_deleted_entities_cleanup: retention_days: {{soft_deleted_entities_cleanup.retention_days}}{{^soft_deleted_entities_cleanup.retention_days}}10{{/soft_deleted_entities_cleanup.retention_days}} - extraArgs: {} + execution_request_cleanup: + keep_history_min_count: {{execution_request_cleanup.keep_history_min_count}}{{^execution_request_cleanup.keep_history_min_count}}10{{/execution_request_cleanup.keep_history_min_count}} + keep_history_max_count: {{execution_request_cleanup.keep_history_max_count}}{{^execution_request_cleanup.keep_history_max_count}}1000{{/execution_request_cleanup.keep_history_max_count}} + keep_history_max_days: {{execution_request_cleanup.keep_history_max_days}}{{^execution_request_cleanup.keep_history_max_days}}30{{/execution_request_cleanup.keep_history_max_days}} + batch_read_size: {{execution_request_cleanup.batch_read_size}}{{^execution_request_cleanup.batch_read_size}}100{{/execution_request_cleanup.batch_read_size}} + enabled: {{execution_request_cleanup.enabled}}{{^execution_request_cleanup.enabled}}false{{/execution_request_cleanup.enabled}} + extraArgs: {} debugMode: false executorId: default source: From e36bdc6495695c2b19e8b53dc0da1af1a6aa6eac Mon Sep 17 00:00:00 2001 From: Jay <159848059+jayacryl@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:43:38 -0400 Subject: [PATCH 2/2] feat(docs-site) polishes and improved responsiveness for the home and solutions pages (#11770) --- .../Community/community.module.scss | 4 ++-- .../quickstartcontent.module.scss | 2 +- .../_components/Integrations/index.js | 1 + .../Integrations/integrations.module.scss | 7 ++++++ .../_components/IntegrationsStatic/index.js | 2 +- .../integrations.module.scss | 21 ++++++++++++------ .../logo-integration-5.png | Bin 2329 -> 2999 bytes .../logo-integration-6.png | Bin 1415 -> 3800 bytes .../logo-integration-7.png | Bin 0 -> 2329 bytes .../logo-integration-8.png | Bin 0 -> 1415 bytes 10 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 docs-website/static/img/solutions/integrations-observe/logo-integration-7.png create mode 100644 docs-website/static/img/solutions/integrations-observe/logo-integration-8.png diff --git a/docs-website/src/pages/_components/Community/community.module.scss b/docs-website/src/pages/_components/Community/community.module.scss index 62f0dc13b110a..65da50435597d 100644 --- a/docs-website/src/pages/_components/Community/community.module.scss +++ b/docs-website/src/pages/_components/Community/community.module.scss @@ -221,14 +221,14 @@ .numberContainer { display: inline-block; - width: 11rem; + width: 12rem; text-align: right; } .numberChange { display: inline-block; animation: slideIn 0.5s ease-in-out; - width: 11rem; + width: 12rem; } diff --git a/docs-website/src/pages/_components/QuickstartContent/quickstartcontent.module.scss b/docs-website/src/pages/_components/QuickstartContent/quickstartcontent.module.scss index 1c1f13cbca566..27bdca710b821 100644 --- a/docs-website/src/pages/_components/QuickstartContent/quickstartcontent.module.scss +++ b/docs-website/src/pages/_components/QuickstartContent/quickstartcontent.module.scss @@ -129,7 +129,7 @@ .quickstart__content { display: flex; - margin-bottom: 3rem; + margin-bottom: 6rem; width: 100%; .quickstart__text { diff --git a/docs-website/src/pages/solutions/_components/Integrations/index.js b/docs-website/src/pages/solutions/_components/Integrations/index.js index 77f028eb4cf74..763c2a185c7dd 100644 --- a/docs-website/src/pages/solutions/_components/Integrations/index.js +++ b/docs-website/src/pages/solutions/_components/Integrations/index.js @@ -47,6 +47,7 @@ const Integrations = () => { + See all → ); }; diff --git a/docs-website/src/pages/solutions/_components/Integrations/integrations.module.scss b/docs-website/src/pages/solutions/_components/Integrations/integrations.module.scss index da0c6964e8775..07a6a2548e41a 100644 --- a/docs-website/src/pages/solutions/_components/Integrations/integrations.module.scss +++ b/docs-website/src/pages/solutions/_components/Integrations/integrations.module.scss @@ -1,6 +1,13 @@ .container { display: flex; flex-direction: column; + >a { + text-decoration: none; + text-align: center; + margin-top: 1rem; + margin-bottom: 1rem; + font-size: 1.25rem; + } .section_header { color: var(--primitives-text-tex-subtext, #777E99); diff --git a/docs-website/src/pages/solutions/_components/IntegrationsStatic/index.js b/docs-website/src/pages/solutions/_components/IntegrationsStatic/index.js index 76b99b156704e..17857f7b0360b 100644 --- a/docs-website/src/pages/solutions/_components/IntegrationsStatic/index.js +++ b/docs-website/src/pages/solutions/_components/IntegrationsStatic/index.js @@ -17,7 +17,7 @@ const Integrations = () => {
{[...Array(1)].map((_, i) => ( - {[1, 2, 3, 4, 5, 6].map((item, index) => ( + {[1, 2, 3, 4, 5, 6, 7, 8].map((item, index) => (
))} diff --git a/docs-website/src/pages/solutions/_components/IntegrationsStatic/integrations.module.scss b/docs-website/src/pages/solutions/_components/IntegrationsStatic/integrations.module.scss index aa2201fd0185c..a945f4ae0598f 100644 --- a/docs-website/src/pages/solutions/_components/IntegrationsStatic/integrations.module.scss +++ b/docs-website/src/pages/solutions/_components/IntegrationsStatic/integrations.module.scss @@ -71,18 +71,22 @@ .slider { position: relative; + display: flex; } .slide_track { display: flex; - width: max-content; + width: 80%; margin: auto; + flex-direction: row; + align-items: center; + justify-content: space-evenly; } .slide { - width: 100px; - height: 100px; - margin: auto 3rem; + width: 80px; + height: 80px; + margin: auto 0; display: flex; justify-content: space-between; overflow: hidden; @@ -99,9 +103,12 @@ max-width: 100vw; min-width: auto; } + .slide_track { + width: 95%; + } .slide { - width: 80px; - height: 80px; - margin: auto 1rem; + width: 40px; + height: 40px; + margin: auto 0; } } \ No newline at end of file diff --git a/docs-website/static/img/solutions/integrations-observe/logo-integration-5.png b/docs-website/static/img/solutions/integrations-observe/logo-integration-5.png index acc17cb75d58560e703a0e19327d6d0e8fa4a8b5..c4d69b1ed3bc198f61a793e35efb28144317ee94 100644 GIT binary patch delta 2978 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 Y0s2=NJ}H)D3IG5A07*qoM6N<$f{KQ-i2wiq delta 2303 zcmV4e6b!uG%rEG?Qe(4@5z@HCVSA+1OQ z3P?NwYsCYoFY5$?@v^oOdl^V%2vG!@l8M3|_|vT7VXPT*)r}_6T-#~VCQE$J&hI96 z?D*gHpPPHm?e8Pq+P;61%Afqsf4S#-kFN#VOgf$J*AMxHDJ8SABom255^Y4FMYOmf z$mqjLsUiJxP%m*XULc|4LWmJKORs6M<7k1VL~40)EpJ%MJEmU_foTUxA?2yWmhKB^ zf`&z=v$vkr0#AY~&o7!1>RBW;p-M!~f&0GoA}Lhvh}XVUdfCaXLJ=7Se@k3X`t^JM z4!!qYcCJ~G%Y9+wIi>Ig0Y624h*&@5@YndP?1`q zr-3J94v`{fZbj;999-!e8Oz95rCX7@Z{)L7OOcSrmLKUKAsz-}CHi{iu=~g-Irc-% zt8^ea!Lc0OybelmvuZt3ibGOx0AM{zsX;o~=?7yuA}(%bW)v9ffAKWcgE1c#84_4z zNMMm6fklP{78w#)WJq98&DZ)yTeI3A8w{uk%V{IRKf2>#F7yBjd-vkDhCp#bQ z0?zD{Zn86*D<6_{NA8}gwLWVlVv?4-yQwu!z&t!%IgFIe`3(X8G1~%k%T|vJ_-pQf7-J8vmf1lpQi~{G~J_7Oc;U$ zFEUOOrOi;!O}~L?H0J-O`=?MnjRQMYIH^J5TQv z6{khg8{Qr1!q1bh>a)?A!g`WS5*w+;W{&X_<7Uy(#HwbYd98gN-kNF}=+pR+gjB0S z8hT95f6tVyr(-DDF=;L=e_}Z#@bDuf7!#T={o^ex{PGWQ{`)S}t@4zh^Ba%3OEI}O z-^KiymkY;;&6^NCdI+5-9)sN7Tk-d(`5RF0y{s>@T-3jG7ERN07-ILAA+~)ECSZg} z5=cuUEWYwO?)3|wTR5{VC9dbMKn zTMWru{Qc{N*J|pI!2BfqfMfHX5o9ku> ze_W)z_a1NieWVvH(X|V{5gS0z1THfAz{l$DLlPqo*59udqVEgv3vtg^5Hx{{q_Lqq zSi34WJaM!w73s?`p=+o9Bl#kN#)v*L_TqQZ@%X39*3$rg zOSfPnR*H-q^cJ*>%^O!=L^pdn#`AuLVWr59qu##Gt0da%^)~nyJN*mS{lrd@I+v|? zHDqnjF5)yDv z(Y$wAaLxD<4ozIPfu^XkXN&U}jV5~+#j7tX&SES`= zhhrk0PREsqyrWC9df#ZBfB5t(AxC*gVhXOrBRarIR@z$g2H-5y6lzN(YR(X#1N9F$ ztS@zqfC&U$X(QyImc*6)ypc+$PYMxv796gx6G~^`INh7*IqPNbhQ0=enpy|)ln`PV zjBn^5{5+i8>3UUIatxY)h5CV8ffw`zj1d~oqd{8wVk#1v1BP^yRr|Pr8lsPrf@uIP z4`NITH3rAIK$jHJByIhMvFUVrP@g^ZN2J{AI7mQ>yx)HNxQ&N)tY5g6OmK+{=| Z4*~T>9P;QXQz!rc002ovPDHLkV1gQ$S2h3u diff --git a/docs-website/static/img/solutions/integrations-observe/logo-integration-6.png b/docs-website/static/img/solutions/integrations-observe/logo-integration-6.png index d9bb08766f527311a0dc18ebca1504cc8dd8cfae..c22402e99d1f367fdf2c747c55ff42c63afd5268 100644 GIT binary patch delta 3786 zcmV;*4mI(I3)meYiBL{Q4GJ0x0000DNk~Le0001U0001U2nGNE0INei=8+*Ze-3R) zL_t(|0qvcAa1`Yo$G^|+-X#}uG2vaU5Kt5l6cuX)#UNVo9ke=iT6t-;)~W@ot(|r{ zf`7E?Oh?eEZd4*auGc%qqi(ZKzkuUWrb;CNl{U^#W+klEoNe_bteq(mtK zjw&?uR;sEP+a4=b+8R(-*D#Ijaz!9ycHr;bFyxB5y0)W(RxOh(lGa8!0|8!u93*XQ zVPUu;(}0X(210t0Rt0qxlj6n+6woe0fahp!4Rx%Uu~@8BQN$w%@Fcv3>bjiccFrLt z4-A|g&r?=gTR$WHplzD1f2paPLV3eE2=D{dnkLRloo;QGN%__L5#Se!X%@9Dhb>~V zRR+_m@0h?oPVx=AnWzZF^f44pUdulfP0J62^f~PB@Vco`f1NTAGY81eZzxu{ zg8%`(Cy_8mC_zY)8z>4*vk1r;0x@%lK+GH>5Hp7e#LOWAF>{DO%p4*RGl$TSYnTa$ z+HWDMcR|!wK{V7-Q9%dP5M-nX^0+>bMfB*_-RfJ+C2~Scv41PnEvun@@h+5IUqICC zg-A3a*{>#(qEV`=fBCu0FNEyg59Ue3p${DgbKvsyQUpO|_ap^q0L^zVN_+5Z)~ z0&%bk+Q+X#`(!bUlg~%|@_S(nJP&@uS7ItVKSTb4d!c;ue>si_rU`ZPDnz!fLE?uu zATj`kP@VuC!?px%~pN8<# zhaej3+wp>?^-j@fEi6uYugCRGda&g9K=40Vv};`y-|a-?nOmT2{}g_})6*=67uJ`# zqoR$0NpZ~Me}}*rG#bXKV_^0k09n|R{)J(3dlZt-1R4*Lr@M!&`wOU>-nTqp<-4yl zzpttx&Qopwv)~6jE2gO3kI0jg>5E_A${+z5%7bynRfu0Wg+h%HT%~m#YWcBR9>td> zmgxG(>mkS^R=+GqsQgK&+t#Fby+{gq#UBwL_Z#>He{YJJh$HXCKeQ>NKKwHBJntiM zPD5vq;RwCQ<kmNLv)PwIGM*HZogMA-@H>xXJa2YC2@SVB53|oH?gJOjX!*9lUh^PfEuQ+2v2;N^hg`!`Vus#(8p?r6*K-_VG~Dr$ ztB|49&mr{IY$#RVq`gQwpYxPD>SxGBJQHV%f9gHZR?TxisiV}5})VXGYZ6Y{Vk z$VI#{%_bV4ee{y+DH-jF#3j>Of4vq$<2A{>!lCz`v7bxzcEbfpF>@48h^b}D#IRM4 zf1XUq?&EETLm$k8QF=Da9}G`<6T6_Yuse)?H0H#i6i*gdR?i-->r|a1ctT9I;&rF9 zLdg6m^z(jdJEk1ij_|UFNU=P`Zhh8OP@U3wa==KK+)Z!{k^ME392oVh|GJ+#ZH%J? z6@Ggb)gP-Ve%wpxzDZOTTW5a}32F@cfAKLVB$IV9mFjP3irwKn!OqGVm)j53?1j4S z%|p*K33`l@eK9$}H(ZhgRKhYTQv#ZjqO;ope*#UPWHv*#J1adS=<+9iQVy0 zHm-CZOQkM$c#d_l?*P^8LrWK33TE+%q+ZB&Ox^pX>sxS#UHaH`*rL_XmO*W;e-e`h z_XFu?UUBFEmCW)N{RPr69Agt@zk+$f5M-0AiBI|wp#5eT;0R^T>~PW=}y zYS{jqDlx(|T>b!Ibuna{?2Ac>0oN}&&1*#{V$~a+Z$MMu?u}^mGx`qdl$Nrnx9z#r zC7q_9yf1^~O?NIGL zh`7siN*9z#Ipl1U^)=J%HNaU!CKQSv+0jU~4s+0GVEuB(Sk54E2cOx`{gy`DW*NI{ z5f!dqgpzK>wfn93)qzkbw`=dpQ}%9kUvtCVtai?9lYKF*_77)h!p?`fYd!Q7G4;V? z5T;}Tj5J4x*4O+O?m_I1e}z`}of~{n#4owad4im(yw_$rsx9X6gJJfy&jiY*_uVED zs}LYPkUq@%G;8)KwYmr#kOe6Jd&54q&mb75jBP#ae4n*%q(d|%=WX>+uGakM zCVT8k7mT*$L%6AF{9u^n>1LbkubEURPM^-2(KfEIc5I~*zihgdf9i7`U-2(Q9-Wxn z6_zOk(c{k4Uz(t1$w|;@olQmEz81>9EzbAjwRLbAynrW4U>ulh>)vp#o7KBWpx2XD z(gg3$N{qglYMOIh1ylBHB0;aCdgn;!6v#`mW*N@65(jGKCr~%MOBYB@#_}1zCtqCY zo^xpB)9x1tg(R6~e~Y7ysHv%+D}>1E&4by|DfrtM>kCiE?0YH>-n|r{!gA{P$n0y{ zxus9oe{B+kr*?;+)(7ei%U5=Oj)F(7bgtFlGF=&Bx6enm5yG76i5?(M-RWmu<$8`S zE&Sfoj`0n*zu?-b=ko4{9x0ZUv3!<3g;RKxx8wm=A+5Fbe}YNy3Z7}`N{sm>WGLVD zTxi7;v|eqs9kZID?_78&wCMzba=mTUL&Gbdgu3|y*K<5;=tC}sSMZFO{KXh-oi4(N z#yTjQKXkr^1dUC5jjUsG(xN&eDz|EW{h8}2W&0-xEuZE7I{K{mrPHllW?sQl`!jj_ zxue~qX%ml5e@>4ZJmofC|5wC*`wEP~W363b&0G9jX*$lYON_Y%vEMC&e!jCQnj6gW z=lHcAnA~f9iuqR^6R6MtF zh}TXI&N+n0BqVQRur}0k0zD1cdmwavTsYNoc&ri|{ghl%Q`-LLhE_k1@X`mJHOmw@ zlf=t{*YMt!KDyc)Zg>#d`gf2R^>fGR@bX6xe)I2;d4*)fchGt{HMzar4c?5~&eD{i zX*F-zf5TM!d^K~z!RoHR8S#s~x!unbab|mI7825IIVQ=Q!)7=W7v4gl@5oD_Hr*~Q%#bs zP~GvVmHty{N^kSV7c3;LvkjA!4D-o9dDWnrfx(Ya!wLA2d#!Y2C@o%lJY}efW52n^#%gb?xcl;Tung z1C;7JaF}=SqA({u?oNs)r#X6(yofJu>coZL*pz>wal#CkT;Dv>_qO9o2zkn}+h4M7 ze;wloAsPSNZjC+3i*AK}#udn;^;2ED2tse@Shrm}K0`+PAYOJ0^ih*Cd&wTfSKe$Y zoPZC#KZmB?rH3}@c1GBymK8~HWBk0EV9@&Lg?^rn;wx`96IPva%W9~fF0njRrFu`( z%_&j_HQmdg<#z}xsV{q$TDK(C&%PQae`S!Qhw`i)#W&t;YjDpG7q|H#xQl?o4z6y zhM`m-z}L`HSy56Vs+E$GNF~jdk_KscvsB_+f&kxQnpnv{EiuUnnu`G6Vi+jr zpO%=>Xrw%tYW;~2Vs3J&v?mQ_Bfxji64~-!PPBB&R8>_KYg+g#I@IwV|A1^!9*q`^ zZGO^XnI$D9Jk18y=_7PqOig{Rf7nmPX9s#s|#Bv(+zp%ei}i*k-7Gp&*7ba>Y)TPg1v3q(C~bi^8MuR|wjveF6k>$E`B zE~T7AoL5v7nTt+v*!#(AYhz{P^-fU~aZw;_XOLW2rh2kc-cd_Tr%P`Rz!s}%C^bzv zLQ!Nf)k*s!zy@9T+ohD1AVR2>+_%H+9_S+e59TnMB?U{}D*ylh07*qoM6N<$f=T95 ATmS$7 delta 1382 zcmca1+s>`n8Q|y6%O%Cdz`(%k>ERLtq>F&Kn1c;Sx-^E}n5bw{&nn~T;uuoF`1Y=E zk#wlQfsgOh6T2*q8874EHcL7v^^|3zzQaszf#*yDegeN)6s^k|MZIg7PpcnbXIQgA zWuv>%3@xE&rgt}~M)xi8_&4ji@BdXfCnrAfdinl)+OxAq9CxI-2Acd>c2Zt)N1@f9 zi)TOA@UIg*BwDybtmRj&QdWH<`<(FE_qX3=DEz%DQD1b@<5ZdV*^73ZyT6F@omGiJ zi?2rf`=Tp+XEN7KNN0R0_~XQGO<9*ecJXnyW<7T`Zl9t#Wy80g_htv~w)uHJJ$%(B z|NN&X2B#-?-J9QU`G~LL$n7gDK340!|EuFa&q^s(>uH1i$Gc)XP6-?+dvNjO?Q`|- zmOS_2sTA6HZt;YgbT!T`?IPFJj?S^zT6N?~!eoOtU&<|xOGs~eA#3}wL-SA?&sO!v za+S&|`7u8~Fv&G~3kl`Oi0n20)sv%~TG?Bk_wC@)b=@KNtoIif7%3_D$#2uMy!q1b z?*)S=rypfBw?DVC(2dehnfv>SL5f2KgP&+zy@7-YduQr>EqM8Pf?I`$vdt1fxl0`UnJw)mhn%GzD)xRr2-icz z7x9U5ua7_UX#IW1x9j5_7Ju5?|M17<^$L15ETOAvT=eAZnyvowMO{729r5NLN9bys zl)qPt8+LOybaOA@y%+i7|J~RB|FDI6r%P)ZzTd+f^`-v9G&cJh!Bx|)ojZ4Fw^q_O z<$J!_Y@Ye+LzC2O&MuP(TvGXC;tMn5`l}sEcORcPlC(@FaLHbU2`BghUJ1JX(zX`O zH#T(Ft|UI6G5!(S~x-yV2WbiYar=p6={7BWt(^#w-cJS&-~B+&!4RaDKwD-d4)~Nn)MB38~1SmgX_u1Q_~$1Ikzk-VE=c{ zu6@c1lwpx8>?|DJidc`}w-0Mei={o4+ehe2ytAs|nF+ z<+xpR+rCd{<(+z_<(D%(WNK<}y#4s4i?^UFAYC>3pUXSm@G>P=`KhN84xD}X_cQ;8 z6@O;_U%~0uxcQ=n+qEd~uKNwa597Py=lriyd6G46d*=(y9ZL!qujT(-^TdPsxb4N| zH>ULZtz=I2Pfo1A5yT#owBm5?wPUtTlj9C)+wmUS@VH%NrA6ln?s|*4h5e7VOs_f< z;?u9OEKX5De@gy=<-4{W3|)Gb_nmLI#pZU7(sVUj9m3y!7W{H-6a{B(cPj6;A zbIr_BX5swBaL4yiV1!-GZ4I0JbrYE7zNmfRV99aUnt$q}&6$@$itH*GoENwv9xpg@ zeC~_qCeNK}xCFloWJ)LfdH7|D&7-9;nonHoMO-I;pC`rPC?;&gv4iKG&yqNf4e`fr jbl33xc=YN_=pS|^$NWG0H{Nam7M%>9u6{1-oD!M<<#vqy diff --git a/docs-website/static/img/solutions/integrations-observe/logo-integration-7.png b/docs-website/static/img/solutions/integrations-observe/logo-integration-7.png new file mode 100644 index 0000000000000000000000000000000000000000..acc17cb75d58560e703a0e19327d6d0e8fa4a8b5 GIT binary patch literal 2329 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 F=HDsFkPH9- literal 0 HcmV?d00001