From 6f09b96b1d21bfb6fe38cd17f47a06c99bf9b7dc Mon Sep 17 00:00:00 2001
From: John Joyce
Date: Tue, 30 Jul 2024 15:24:18 -0700
Subject: [PATCH 01/15] feat(models): Introducing Dataset Partitions Aspect
(#10997)
Co-authored-by: John Joyce
Co-authored-by: John Joyce
---
.../com/linkedin/dataset/PartitionSummary.pdl | 24 +++++++++++++++++++
.../linkedin/dataset/PartitionsSummary.pdl | 19 +++++++++++++++
.../com/linkedin/schema/SchemaField.pdl | 2 ++
.../com/linkedin/timeseries/PartitionSpec.pdl | 24 +++++++++++--------
.../src/main/resources/entity-registry.yml | 1 +
5 files changed, 60 insertions(+), 10 deletions(-)
create mode 100644 metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionSummary.pdl
create mode 100644 metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionsSummary.pdl
diff --git a/metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionSummary.pdl b/metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionSummary.pdl
new file mode 100644
index 0000000000000..3984277a31417
--- /dev/null
+++ b/metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionSummary.pdl
@@ -0,0 +1,24 @@
+namespace com.linkedin.dataset
+
+import com.linkedin.common.AuditStamp
+
+/**
+ * Defines how the data is partitioned
+ */
+record PartitionSummary {
+ /**
+ * A unique id / value for the partition for which statistics were collected,
+ * generated by applying the key definition to a given row.
+ */
+ partition: string
+
+ /**
+ * The created time for a given partition.
+ */
+ created: optional AuditStamp
+
+ /**
+ * The last modified / touched time for a given partition.
+ */
+ lastModified: optional AuditStamp
+}
\ No newline at end of file
diff --git a/metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionsSummary.pdl b/metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionsSummary.pdl
new file mode 100644
index 0000000000000..34e696890d64f
--- /dev/null
+++ b/metadata-models/src/main/pegasus/com/linkedin/dataset/PartitionsSummary.pdl
@@ -0,0 +1,19 @@
+namespace com.linkedin.dataset
+
+/**
+ * Defines how the data is partitioned for Data Lake tables (e.g. Hive, S3, Iceberg, Delta, Hudi, etc).
+ */
+@Aspect = {
+ "name": "partitionsSummary"
+}
+record PartitionsSummary {
+ /**
+ * The minimum partition as ordered
+ */
+ minPartition: optional PartitionSummary
+
+ /**
+ * The maximum partition as ordered
+ */
+ maxPartition: optional PartitionSummary
+}
\ No newline at end of file
diff --git a/metadata-models/src/main/pegasus/com/linkedin/schema/SchemaField.pdl b/metadata-models/src/main/pegasus/com/linkedin/schema/SchemaField.pdl
index afb0263057b6d..f91e2004401cf 100644
--- a/metadata-models/src/main/pegasus/com/linkedin/schema/SchemaField.pdl
+++ b/metadata-models/src/main/pegasus/com/linkedin/schema/SchemaField.pdl
@@ -150,6 +150,8 @@ record SchemaField {
/**
* For Datasets which are partitioned, this determines the partitioning key.
+ * Note that multiple columns can be part of a partitioning key, but currently we do not support
+ * rendering the ordered partitioning key.
*/
isPartitioningKey: optional boolean
diff --git a/metadata-models/src/main/pegasus/com/linkedin/timeseries/PartitionSpec.pdl b/metadata-models/src/main/pegasus/com/linkedin/timeseries/PartitionSpec.pdl
index 084af1513ec80..146a285e24dba 100644
--- a/metadata-models/src/main/pegasus/com/linkedin/timeseries/PartitionSpec.pdl
+++ b/metadata-models/src/main/pegasus/com/linkedin/timeseries/PartitionSpec.pdl
@@ -1,24 +1,28 @@
namespace com.linkedin.timeseries
/**
- * Defines how the data is partitioned
+ * A reference to a specific partition in a dataset.
*/
record PartitionSpec {
-
- type: enum PartitionType {
- FULL_TABLE,
- QUERY,
- PARTITION
- } = "PARTITION"
-
/**
- * String representation of the partition
+ * A unique id / value for the partition for which statistics were collected,
+ * generated by applying the key definition to a given row.
*/
@TimeseriesField = {}
partition: string
/**
- * Time window of the partition if applicable
+ * Time window of the partition, if we are able to extract it from the partition key.
*/
timePartition: optional TimeWindow
+
+ /**
+ * Unused!
+ */
+ @deprecated
+ type: enum PartitionType {
+ FULL_TABLE,
+ QUERY,
+ PARTITION
+ } = "PARTITION"
}
\ No newline at end of file
diff --git a/metadata-models/src/main/resources/entity-registry.yml b/metadata-models/src/main/resources/entity-registry.yml
index 3af4af5e4767e..c9f9a851ccc08 100644
--- a/metadata-models/src/main/resources/entity-registry.yml
+++ b/metadata-models/src/main/resources/entity-registry.yml
@@ -45,6 +45,7 @@ entities:
- access
- structuredProperties
- forms
+ - partitionsSummary
- name: dataHubPolicy
doc: DataHub Policies represent access policies granted to users or groups on metadata operations like edit, view etc.
category: internal
From d6be56f9d9c4927a60da6920a51a24e3163e5050 Mon Sep 17 00:00:00 2001
From: Sam Black
Date: Tue, 30 Jul 2024 18:50:19 -0400
Subject: [PATCH 02/15] feat(propagation): Add Documentation Propagation
Settings (#11038)
---
.../src/app/settings/SettingsPage.tsx | 28 +++
.../src/app/settings/features/Feature.tsx | 179 ++++++++++++++++++
.../src/app/settings/features/Features.tsx | 110 +++++++++++
.../features/useDocPropagationSettings.ts | 50 +++++
datahub-web-react/src/graphql/app.graphql | 10 +
5 files changed, 377 insertions(+)
create mode 100644 datahub-web-react/src/app/settings/features/Feature.tsx
create mode 100644 datahub-web-react/src/app/settings/features/Features.tsx
create mode 100644 datahub-web-react/src/app/settings/features/useDocPropagationSettings.ts
diff --git a/datahub-web-react/src/app/settings/SettingsPage.tsx b/datahub-web-react/src/app/settings/SettingsPage.tsx
index e0a15c73a626d..24bcd17ca7f9c 100644
--- a/datahub-web-react/src/app/settings/SettingsPage.tsx
+++ b/datahub-web-react/src/app/settings/SettingsPage.tsx
@@ -8,6 +8,7 @@ import {
FilterOutlined,
TeamOutlined,
PushpinOutlined,
+ ControlOutlined,
} from '@ant-design/icons';
import { Redirect, Route, useHistory, useLocation, useRouteMatch, Switch } from 'react-router';
import styled from 'styled-components';
@@ -17,11 +18,17 @@ import { ManagePermissions } from '../permissions/ManagePermissions';
import { useAppConfig } from '../useAppConfig';
import { AccessTokens } from './AccessTokens';
import { Preferences } from './Preferences';
+import { Features } from './features/Features';
import { ManageViews } from '../entity/view/ManageViews';
import { useUserContext } from '../context/useUserContext';
import { ManageOwnership } from '../entity/ownership/ManageOwnership';
import ManagePosts from './posts/ManagePosts';
+const MenuItem = styled(Menu.Item)`
+ display: flex;
+ align-items: center;
+`;
+
const PageContainer = styled.div`
display: flex;
overflow: auto;
@@ -59,6 +66,17 @@ const ItemTitle = styled.span`
const menuStyle = { width: 256, 'margin-top': 8, overflow: 'hidden auto' };
+const NewTag = styled.span`
+ padding: 4px 8px;
+ margin-left: 8px;
+
+ border-radius: 24px;
+ background: #f1fbfe;
+
+ color: #09739a;
+ font-size: 12px;
+`;
+
/**
* URL Paths for each settings page.
*/
@@ -70,6 +88,7 @@ const PATHS = [
{ path: 'views', content: },
{ path: 'ownership', content: },
{ path: 'posts', content: },
+ { path: 'features', content: },
];
/**
@@ -80,6 +99,7 @@ const DEFAULT_PATH = PATHS[0];
export const SettingsPage = () => {
const { path, url } = useRouteMatch();
const { pathname } = useLocation();
+
const history = useHistory();
const subRoutes = PATHS.map((p) => p.path.replace('/', ''));
const currPathName = pathname.replace(path, '');
@@ -101,6 +121,7 @@ export const SettingsPage = () => {
const showViews = isViewsEnabled || false;
const showOwnershipTypes = me && me?.platformPrivileges?.manageOwnershipTypes;
const showHomePagePosts = me && me?.platformPrivileges?.manageGlobalAnnouncements && !readOnlyModeEnabled;
+ const showFeatures = true; // TODO: Add feature flag for this
return (
@@ -143,6 +164,13 @@ export const SettingsPage = () => {
)}
{(showViews || showOwnershipTypes || showHomePagePosts) && (
+ {showFeatures && (
+
+ )}
{showViews && (
My Views
diff --git a/datahub-web-react/src/app/settings/features/Feature.tsx b/datahub-web-react/src/app/settings/features/Feature.tsx
new file mode 100644
index 0000000000000..2c090aae696f8
--- /dev/null
+++ b/datahub-web-react/src/app/settings/features/Feature.tsx
@@ -0,0 +1,179 @@
+import React from 'react';
+
+import styled from 'styled-components';
+
+import { Divider, Typography, Switch, Card, Button, Tooltip } from 'antd';
+import { ArrowRightOutlined } from '@ant-design/icons';
+import { ANTD_GRAY } from '../../entity/shared/constants';
+
+const Title = styled(Typography.Title)`
+ && {
+ margin-bottom: 8px;
+ }
+`;
+
+const FeatureRow = styled.div`
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+`;
+
+const FeatureOptionRow = styled.div`
+ display: flex;
+ justify-content: space-between;
+
+ &:not(:last-child) {
+ margin-bottom: 8px;
+ }
+`;
+
+const SettingsOptionRow = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 16px;
+
+ &:not(:last-child) {
+ margin-bottom: 8px;
+ }
+`;
+
+const DescriptionText = styled(Typography.Text)`
+ color: ${ANTD_GRAY[7]};
+ font-size: 11px;
+`;
+
+const SettingTitle = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ margin-bottom: 4px;
+`;
+
+const OptionTitle = styled(Typography.Text)`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 12px;
+`;
+
+const learnMoreLinkStyle = {
+ flex: 1,
+ display: 'flex',
+ alignItems: 'center',
+ gap: '8px',
+ color: '#1890FF',
+ fontSize: '12px',
+ cursor: 'pointer',
+};
+
+const NewTag = styled.div`
+ padding: 4px 8px;
+
+ border-radius: 24px;
+ background: #f1fbfe;
+
+ color: #09739a;
+ font-size: 12px;
+`;
+
+const DataHubOnlyTag = styled.div`
+ padding: 2px 8px;
+
+ border-radius: 24px;
+ background: #c9fff2;
+
+ color: #50a494;
+ font-size: 12px;
+`;
+
+export interface FeatureType {
+ key: string;
+ title: string;
+ description: string;
+ settings: Array<{
+ key: string;
+ title: string;
+ isAvailable: boolean;
+ buttonText: string;
+ onClick?: () => void;
+ }>;
+ options: Array<{
+ key: string;
+ title: string;
+ description: string;
+ isAvailable: boolean;
+ checked: boolean;
+ onChange?: (checked: boolean) => void;
+ }>;
+ isNew: boolean;
+ learnMoreLink?: string;
+}
+
+export const Feature = ({ key, title, description, settings, options, isNew, learnMoreLink }: FeatureType) => (
+
+
+
+
+
+ {title}
+
+ {isNew && New!}
+
+
+ {description}
+
+
+
+
+
+ {settings.map((option) => (
+ <>
+
+
+
+ {option.title}
+
+
+
+
+
+
+ >
+ ))}
+
+ {options.map((option, index) => (
+ <>
+
+
+
+ {option.title}
+ {!option.isAvailable && (
+ Only available on DataHub Cloud
+ )}
+
+
+ {option.description}
+
+
+ (option.onChange ? option.onChange(checked) : null)}
+ disabled={!option.isAvailable}
+ />
+
+ {index !== options.length - 1 && }
+ >
+ ))}
+
+
+);
diff --git a/datahub-web-react/src/app/settings/features/Features.tsx b/datahub-web-react/src/app/settings/features/Features.tsx
new file mode 100644
index 0000000000000..ee8d7c628c1ef
--- /dev/null
+++ b/datahub-web-react/src/app/settings/features/Features.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+
+import styled from 'styled-components';
+
+import { Divider, Typography } from 'antd';
+import { v4 as uuidv4 } from 'uuid';
+
+import { Feature, FeatureType } from './Feature';
+
+import { useGetDocPropagationSettings, useUpdateDocPropagationSettings } from './useDocPropagationSettings';
+
+const Page = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: center;
+`;
+
+const SourceContainer = styled.div`
+ width: 80%;
+ padding-top: 20px;
+ padding-right: 40px;
+ padding-left: 40px;
+`;
+const Container = styled.div`
+ padding-top: 0px;
+`;
+
+const Title = styled(Typography.Title)`
+ && {
+ margin-bottom: 8px;
+ }
+`;
+
+export const Features = () => {
+ /*
+ * Note: When adding new features, make sure to update the features array below
+ * and create a hook file for the new feature in the same directory
+ */
+
+ // Hooks to get and update the document propagation settings
+ const { isColPropagateChecked, setIsColPropagateChecked } = useGetDocPropagationSettings();
+ const { updateDocPropagation } = useUpdateDocPropagationSettings();
+
+ // Features to display
+ const features: FeatureType[] = [
+ {
+ key: uuidv4(),
+ title: 'Documentation Propagation',
+ description: 'Automatically propagate documentation from upstream to downstream columns and assets.',
+ settings: [
+ {
+ key: uuidv4(),
+ title: 'Rollback Propagation Changes',
+ isAvailable: false,
+ buttonText: 'Rollback',
+ },
+ {
+ key: uuidv4(),
+ title: 'Backfill existing documentation from upstream to downstream columns/assets',
+ isAvailable: false,
+ buttonText: 'Initialize',
+ },
+ ],
+ options: [
+ {
+ key: uuidv4(),
+ title: 'Column Level Propagation',
+ description:
+ 'Propagate new documentation from upstream to downstream columns based on column-level lineage relationships.',
+ isAvailable: true,
+ checked: isColPropagateChecked,
+ onChange: (checked: boolean) => {
+ setIsColPropagateChecked(checked);
+ updateDocPropagation(checked);
+ },
+ },
+ {
+ key: uuidv4(),
+ title: 'Asset Level Propagation',
+ description:
+ 'Propagate new documentation from upstream to downstream assets based on data lineage relationships.',
+ isAvailable: false,
+ checked: false,
+ },
+ ],
+ isNew: true,
+ learnMoreLink: 'https://datahubproject.io/docs/automations/doc-propagation',
+ },
+ ];
+
+ // Render
+ return (
+
+
+
+
+
Features
+
+ Explore and configure specific features
+
+
+
+
+ {features.map((feature) => (
+
+ ))}
+
+
+ );
+};
diff --git a/datahub-web-react/src/app/settings/features/useDocPropagationSettings.ts b/datahub-web-react/src/app/settings/features/useDocPropagationSettings.ts
new file mode 100644
index 0000000000000..c93b610cff9d1
--- /dev/null
+++ b/datahub-web-react/src/app/settings/features/useDocPropagationSettings.ts
@@ -0,0 +1,50 @@
+import { useEffect, useState } from 'react';
+
+import { message } from 'antd';
+
+import {
+ useGetDocPropagationSettingsQuery,
+ useUpdateDocPropagationSettingsMutation,
+} from '../../../graphql/app.generated';
+
+// Hook to get the document propagation settings & manage state
+export const useGetDocPropagationSettings = () => {
+ const { data, refetch } = useGetDocPropagationSettingsQuery();
+ const [isColPropagateChecked, setIsColPropagateChecked] = useState(false);
+
+ useEffect(() => {
+ const docPropSetting = data?.docPropagationSettings?.docColumnPropagation;
+ if (docPropSetting !== undefined) setIsColPropagateChecked(!!docPropSetting);
+ }, [data]);
+
+ return {
+ isColPropagateChecked,
+ setIsColPropagateChecked,
+ refetch,
+ };
+};
+
+// Hook to update the document propagation settings
+export const useUpdateDocPropagationSettings = () => {
+ const [updateDocPropagationSettings] = useUpdateDocPropagationSettingsMutation();
+ const { refetch } = useGetDocPropagationSettingsQuery();
+
+ const updateDocPropagation = async (checked: boolean) => {
+ try {
+ await updateDocPropagationSettings({
+ variables: {
+ input: {
+ docColumnPropagation: checked,
+ },
+ },
+ });
+ refetch();
+ message.success('Successfully updated documentation propagation settings');
+ } catch (e) {
+ message.error('Failed to update documentation propagation settings');
+ refetch();
+ }
+ };
+
+ return { updateDocPropagation };
+};
diff --git a/datahub-web-react/src/graphql/app.graphql b/datahub-web-react/src/graphql/app.graphql
index bfca27a4ad106..e058a6fbb58e0 100644
--- a/datahub-web-react/src/graphql/app.graphql
+++ b/datahub-web-react/src/graphql/app.graphql
@@ -89,6 +89,16 @@ query getGlobalViewsSettings {
}
}
+query getDocPropagationSettings {
+ docPropagationSettings {
+ docColumnPropagation
+ }
+}
+
mutation updateGlobalViewsSettings($input: UpdateGlobalViewsSettingsInput!) {
updateGlobalViewsSettings(input: $input)
}
+
+mutation updateDocPropagationSettings($input: UpdateDocPropagationSettingsInput!) {
+ updateDocPropagationSettings(input: $input)
+}
From 7d4b645225953f3218c5bf891637582f93f3fbcb Mon Sep 17 00:00:00 2001
From: Shirshanka Das
Date: Tue, 30 Jul 2024 16:04:20 -0700
Subject: [PATCH 03/15] =?UTF-8?q?fix(models):=20chart=20schema=20fields=20?=
=?UTF-8?q?mapping,=20add=20dataHubAction=20entity,=20t=E2=80=A6=20(#11040?=
=?UTF-8?q?)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../chart/mappers/InputFieldsMapper.java | 18 +-
.../metadata/key/DataHubActionKey.pdl | 14 ++
.../src/main/resources/entity-registry.yml | 26 +++
smoke-test/tests/schema_fields/__init__.py | 0
.../schema_fields/queries/get_chart_field.gql | 20 ++
.../tests/schema_fields/test_schemafields.py | 177 ++++++++++++++++++
6 files changed, 251 insertions(+), 4 deletions(-)
create mode 100644 metadata-models/src/main/pegasus/com/linkedin/metadata/key/DataHubActionKey.pdl
create mode 100644 smoke-test/tests/schema_fields/__init__.py
create mode 100644 smoke-test/tests/schema_fields/queries/get_chart_field.gql
create mode 100644 smoke-test/tests/schema_fields/test_schemafields.py
diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/mappers/InputFieldsMapper.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/mappers/InputFieldsMapper.java
index 49c2d17ce0958..a4e40750f0d65 100644
--- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/mappers/InputFieldsMapper.java
+++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/chart/mappers/InputFieldsMapper.java
@@ -5,10 +5,14 @@
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.InputField;
import com.linkedin.datahub.graphql.types.dataset.mappers.SchemaFieldMapper;
+import java.net.URISyntaxException;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
public class InputFieldsMapper {
public static final InputFieldsMapper INSTANCE = new InputFieldsMapper();
@@ -31,13 +35,19 @@ public com.linkedin.datahub.graphql.generated.InputFields apply(
.map(
field -> {
InputField fieldResult = new InputField();
+ Urn parentUrn = entityUrn;
- if (field.hasSchemaField()) {
- fieldResult.setSchemaField(
- SchemaFieldMapper.map(context, field.getSchemaField(), entityUrn));
- }
if (field.hasSchemaFieldUrn()) {
fieldResult.setSchemaFieldUrn(field.getSchemaFieldUrn().toString());
+ try {
+ parentUrn = Urn.createFromString(field.getSchemaFieldUrn().getEntityKey().get(0));
+ } catch (URISyntaxException e) {
+ log.error("Field urn resolution: failed to extract parentUrn successfully from {}. Falling back to {}", field.getSchemaFieldUrn(), entityUrn, e);
+ }
+ }
+ if (field.hasSchemaField()) {
+ fieldResult.setSchemaField(
+ SchemaFieldMapper.map(context, field.getSchemaField(), parentUrn));
}
return fieldResult;
})
diff --git a/metadata-models/src/main/pegasus/com/linkedin/metadata/key/DataHubActionKey.pdl b/metadata-models/src/main/pegasus/com/linkedin/metadata/key/DataHubActionKey.pdl
new file mode 100644
index 0000000000000..8205ecbb80716
--- /dev/null
+++ b/metadata-models/src/main/pegasus/com/linkedin/metadata/key/DataHubActionKey.pdl
@@ -0,0 +1,14 @@
+namespace com.linkedin.metadata.key
+
+/**
+ * Key for a DataHub Action Pipeline
+ */
+@Aspect = {
+ "name": "dataHubActionKey"
+}
+record DataHubActionKey {
+ /**
+ * A unique id for the Action, either generated or provided
+ */
+ id: string
+}
\ No newline at end of file
diff --git a/metadata-models/src/main/resources/entity-registry.yml b/metadata-models/src/main/resources/entity-registry.yml
index c9f9a851ccc08..f8520990a0984 100644
--- a/metadata-models/src/main/resources/entity-registry.yml
+++ b/metadata-models/src/main/resources/entity-registry.yml
@@ -75,6 +75,7 @@ entities:
- forms
- subTypes
- incidentsSummary
+ - testResults
- name: dataFlow
category: core
keyAspect: dataFlowKey
@@ -96,12 +97,14 @@ entities:
- incidentsSummary
- forms
- subTypes
+ - testResults
- name: dataProcess
keyAspect: dataProcessKey
aspects:
- dataProcessInfo
- ownership
- status
+ - testResults
- name: dataProcessInstance
doc: DataProcessInstance represents an instance of a datajob/jobflow run
keyAspect: dataProcessInstanceKey
@@ -112,6 +115,7 @@ entities:
- dataProcessInstanceRelationships
- dataProcessInstanceRunEvent
- status
+ - testResults
- name: chart
category: core
keyAspect: chartKey
@@ -137,6 +141,7 @@ entities:
- structuredProperties
- incidentsSummary
- forms
+ - testResults
- name: dashboard
keyAspect: dashboardKey
aspects:
@@ -160,6 +165,7 @@ entities:
- structuredProperties
- incidentsSummary
- forms
+ - testResults
- name: notebook
doc: Notebook represents a combination of query, text, chart and etc. This is in BETA version
keyAspect: notebookKey
@@ -177,6 +183,7 @@ entities:
- subTypes
- dataPlatformInstance
- browsePathsV2
+ - testResults
- name: corpuser
doc: CorpUser represents an identity of a person (or an account) in the enterprise.
keyAspect: corpUserKey
@@ -194,6 +201,7 @@ entities:
- roleMembership
- structuredProperties
- forms
+ - testResults
- name: corpGroup
doc: CorpGroup represents an identity of a group of users in the enterprise.
keyAspect: corpGroupKey
@@ -207,6 +215,7 @@ entities:
- roleMembership
- structuredProperties
- forms
+ - testResults
- name: domain
doc: A data domain within an organization.
category: core
@@ -217,6 +226,7 @@ entities:
- ownership
- structuredProperties
- forms
+ - testResults
- name: container
doc: A container of related data assets.
category: core
@@ -237,6 +247,7 @@ entities:
- browsePathsV2
- structuredProperties
- forms
+ - testResults
- name: tag
category: core
keyAspect: tagKey
@@ -245,6 +256,7 @@ entities:
- ownership
- deprecation
- status
+ - testResults
- name: glossaryTerm
category: core
keyAspect: glossaryTermKey
@@ -260,6 +272,7 @@ entities:
- browsePaths
- structuredProperties
- forms
+ - testResults
- name: glossaryNode
category: core
keyAspect: glossaryNodeKey
@@ -270,6 +283,7 @@ entities:
- status
- structuredProperties
- forms
+ - testResults
- name: dataHubIngestionSource
category: internal
keyAspect: dataHubIngestionSourceKey
@@ -341,6 +355,7 @@ entities:
- browsePathsV2
- structuredProperties
- forms
+ - testResults
- name: mlModelGroup
category: core
keyAspect: mlModelGroupKey
@@ -358,6 +373,7 @@ entities:
- browsePathsV2
- structuredProperties
- forms
+ - testResults
- name: mlModelDeployment
category: core
keyAspect: mlModelDeploymentKey
@@ -368,6 +384,7 @@ entities:
- deprecation
- globalTags
- dataPlatformInstance
+ - testResults
- name: mlFeatureTable
category: core
keyAspect: mlFeatureTableKey
@@ -386,6 +403,7 @@ entities:
- browsePathsV2
- structuredProperties
- forms
+ - testResults
- name: mlFeature
category: core
keyAspect: mlFeatureKey
@@ -404,6 +422,7 @@ entities:
- browsePathsV2
- structuredProperties
- forms
+ - testResults
- name: mlPrimaryKey
category: core
keyAspect: mlPrimaryKeyKey
@@ -420,6 +439,7 @@ entities:
- dataPlatformInstance
- structuredProperties
- forms
+ - testResults
- name: telemetry
category: internal
keyAspect: telemetryKey
@@ -456,6 +476,7 @@ entities:
- forms
- businessAttributes
- documentation
+ - testResults
- name: globalSettings
doc: Global settings for an the platform
category: internal
@@ -523,6 +544,7 @@ entities:
- status
- structuredProperties
- forms
+ - testResults
- name: ownershipType
doc: Ownership Type represents a user-created ownership category for a person or group who is responsible for an asset.
category: core
@@ -550,6 +572,10 @@ entities:
keyAspect: dataHubPersonaKey
aspects:
- dataHubPersonaInfo
+ - name: dataHubAction
+ category: internal
+ keyAspect: dataHubActionKey
+ aspects: []
- name: entityType
doc: A type of entity in the DataHub Metadata Model.
category: core
diff --git a/smoke-test/tests/schema_fields/__init__.py b/smoke-test/tests/schema_fields/__init__.py
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/smoke-test/tests/schema_fields/queries/get_chart_field.gql b/smoke-test/tests/schema_fields/queries/get_chart_field.gql
new file mode 100644
index 0000000000000..424e5ad686ab9
--- /dev/null
+++ b/smoke-test/tests/schema_fields/queries/get_chart_field.gql
@@ -0,0 +1,20 @@
+query($urn:String!) {
+ chart(urn: $urn) {
+ inputFields {
+ fields {
+ schemaFieldUrn
+ schemaField {
+ schemaFieldEntity {
+ urn
+ fieldPath
+ documentation {
+ documentations {
+ documentation
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/smoke-test/tests/schema_fields/test_schemafields.py b/smoke-test/tests/schema_fields/test_schemafields.py
new file mode 100644
index 0000000000000..31f237308e2a8
--- /dev/null
+++ b/smoke-test/tests/schema_fields/test_schemafields.py
@@ -0,0 +1,177 @@
+import logging
+import os
+import tempfile
+import time
+from random import randint
+
+import datahub.metadata.schema_classes as models
+import pytest
+from datahub.emitter.mce_builder import (
+ make_dataset_urn,
+ make_schema_field_urn,
+)
+from datahub.emitter.mcp import MetadataChangeProposalWrapper
+from datahub.ingestion.api.common import PipelineContext, RecordEnvelope
+from datahub.ingestion.api.sink import NoopWriteCallback
+from datahub.ingestion.graph.client import DatahubClientConfig, DataHubGraph
+from datahub.ingestion.sink.file import FileSink, FileSinkConfig
+
+from tests.utils import (
+ delete_urns_from_file,
+ get_gms_url,
+ get_sleep_info,
+ ingest_file_via_rest,
+ wait_for_writes_to_sync,
+)
+
+logger = logging.getLogger(__name__)
+
+
+start_index = randint(10, 10000)
+dataset_urns = [
+ make_dataset_urn("snowflake", f"table_foo_{i}")
+ for i in range(start_index, start_index + 10)
+]
+
+
+class FileEmitter:
+ def __init__(self, filename: str) -> None:
+ self.sink: FileSink = FileSink(
+ ctx=PipelineContext(run_id="create_test_data"),
+ config=FileSinkConfig(filename=filename),
+ )
+
+ def emit(self, event):
+ self.sink.write_record_async(
+ record_envelope=RecordEnvelope(record=event, metadata={}),
+ write_callback=NoopWriteCallback(),
+ )
+
+ def close(self):
+ self.sink.close()
+
+
+@pytest.fixture(scope="module")
+def chart_urn():
+ return "urn:li:chart:(looker,chart_foo)"
+
+
+@pytest.fixture(scope="module")
+def upstream_schema_field_urn():
+ return make_schema_field_urn(make_dataset_urn("snowflake", "table_bar"), "field1")
+
+
+def create_test_data(filename: str, chart_urn: str, upstream_schema_field_urn: str):
+ documentation_mcp = MetadataChangeProposalWrapper(
+ entityUrn=upstream_schema_field_urn,
+ aspect=models.DocumentationClass(
+ documentations=[
+ models.DocumentationAssociationClass(
+ documentation="test documentation",
+ attribution=models.MetadataAttributionClass(
+ time=int(time.time() * 1000),
+ actor="urn:li:corpuser:datahub",
+ source="urn:li:dataHubAction:documentation_propagation",
+ ),
+ )
+ ]
+ ),
+ )
+
+ input_fields_mcp = MetadataChangeProposalWrapper(
+ entityUrn=chart_urn,
+ aspect=models.InputFieldsClass(
+ fields=[
+ models.InputFieldClass(
+ schemaFieldUrn=upstream_schema_field_urn,
+ schemaField=models.SchemaFieldClass(
+ fieldPath="field1",
+ type=models.SchemaFieldDataTypeClass(models.StringTypeClass()),
+ nativeDataType="STRING",
+ ),
+ )
+ ]
+ ),
+ )
+
+ file_emitter = FileEmitter(filename)
+ for mcps in [documentation_mcp, input_fields_mcp]:
+ file_emitter.emit(mcps)
+
+ file_emitter.close()
+
+
+sleep_sec, sleep_times = get_sleep_info()
+
+
+@pytest.fixture(scope="module", autouse=False)
+def ingest_cleanup_data(request, chart_urn, upstream_schema_field_urn):
+ new_file, filename = tempfile.mkstemp(suffix=".json")
+ try:
+ create_test_data(filename, chart_urn, upstream_schema_field_urn)
+ print("ingesting schema fields test data")
+ ingest_file_via_rest(filename)
+ yield
+ print("removing schema fields test data")
+ delete_urns_from_file(filename)
+ wait_for_writes_to_sync()
+ finally:
+ os.remove(filename)
+
+
+@pytest.mark.dependency()
+def test_healthchecks(wait_for_healthchecks):
+ # Call to wait_for_healthchecks fixture will do the actual functionality.
+ pass
+
+
+def get_gql_query(filename: str) -> str:
+ with open(filename) as fp:
+ return fp.read()
+
+
+def validate_schema_field_urn_for_chart(
+ graph: DataHubGraph, chart_urn: str, upstream_schema_field_urn: str
+) -> None:
+ # Validate listing
+ result = graph.execute_graphql(
+ get_gql_query("tests/schema_fields/queries/get_chart_field.gql"),
+ {"urn": chart_urn},
+ )
+ assert "chart" in result
+ assert "inputFields" in result["chart"]
+ assert len(result["chart"]["inputFields"]["fields"]) == 1
+ assert (
+ result["chart"]["inputFields"]["fields"][0]["schemaField"]["schemaFieldEntity"][
+ "urn"
+ ]
+ == upstream_schema_field_urn
+ )
+ assert (
+ result["chart"]["inputFields"]["fields"][0]["schemaField"]["schemaFieldEntity"][
+ "fieldPath"
+ ]
+ == "field1"
+ )
+ assert (
+ result["chart"]["inputFields"]["fields"][0]["schemaFieldUrn"]
+ == upstream_schema_field_urn
+ )
+ assert (
+ result["chart"]["inputFields"]["fields"][0]["schemaField"]["schemaFieldEntity"][
+ "documentation"
+ ]["documentations"][0]["documentation"]
+ == "test documentation"
+ )
+
+
+# @tenacity.retry(
+# stop=tenacity.stop_after_attempt(sleep_times), wait=tenacity.wait_fixed(sleep_sec)
+# )
+@pytest.mark.dependency(depends=["test_healthchecks"])
+def test_schema_field_gql_mapper_for_charts(
+ ingest_cleanup_data, chart_urn, upstream_schema_field_urn
+):
+ graph: DataHubGraph = DataHubGraph(config=DatahubClientConfig(server=get_gms_url()))
+
+ validate_schema_field_urn_for_chart(graph, chart_urn, upstream_schema_field_urn)
From a734d69a6ebb7dd00701a1a2c9ec7540f4ed2d69 Mon Sep 17 00:00:00 2001
From: Shirshanka Das
Date: Tue, 30 Jul 2024 16:33:00 -0700
Subject: [PATCH 04/15] fix(ci): smoke test lint failures (#11044)
---
.github/scripts/check_python_package.py | 31 ++++++++++++++-----
.../tests/schema_fields/queries/__init__.py | 0
.../tests/schema_fields/test_schemafields.py | 5 +--
.../test_structured_properties.py | 10 +++---
4 files changed, 30 insertions(+), 16 deletions(-)
create mode 100644 smoke-test/tests/schema_fields/queries/__init__.py
diff --git a/.github/scripts/check_python_package.py b/.github/scripts/check_python_package.py
index f1f3005691700..1b23d8e621ef0 100644
--- a/.github/scripts/check_python_package.py
+++ b/.github/scripts/check_python_package.py
@@ -1,18 +1,33 @@
import setuptools
+import os
folders = ["./smoke-test/tests"]
for folder in folders:
print(f"Checking folder {folder}")
- a = [i for i in setuptools.find_packages(folder) if "cypress" not in i]
- b = [i for i in setuptools.find_namespace_packages(folder) if "cypress" not in i]
+ packages = [i for i in setuptools.find_packages(folder) if "cypress" not in i]
+ namespace_packages = [
+ i for i in setuptools.find_namespace_packages(folder) if "cypress" not in i
+ ]
- in_a_not_b = set(a) - set(b)
- in_b_not_a = set(b) - set(a)
+ print("Packages found:", packages)
+ print("Namespace packages found:", namespace_packages)
+
+ in_packages_not_namespace = set(packages) - set(namespace_packages)
+ in_namespace_not_packages = set(namespace_packages) - set(packages)
+
+ if in_packages_not_namespace:
+ print(f"Packages not in namespace packages: {in_packages_not_namespace}")
+ if in_namespace_not_packages:
+ print(f"Namespace packages not in packages: {in_namespace_not_packages}")
+ for pkg in in_namespace_not_packages:
+ pkg_path = os.path.join(folder, pkg.replace(".", os.path.sep))
+ print(f"Contents of {pkg_path}:")
+ print(os.listdir(pkg_path))
assert (
- len(in_a_not_b) == 0
- ), f"Found packages in {folder} that are not in namespace packages: {in_a_not_b}"
+ len(in_packages_not_namespace) == 0
+ ), f"Found packages in {folder} that are not in namespace packages: {in_packages_not_namespace}"
assert (
- len(in_b_not_a) == 0
- ), f"Found namespace packages in {folder} that are not in packages: {in_b_not_a}"
+ len(in_namespace_not_packages) == 0
+ ), f"Found namespace packages in {folder} that are not in packages: {in_namespace_not_packages}"
diff --git a/smoke-test/tests/schema_fields/queries/__init__.py b/smoke-test/tests/schema_fields/queries/__init__.py
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/smoke-test/tests/schema_fields/test_schemafields.py b/smoke-test/tests/schema_fields/test_schemafields.py
index 31f237308e2a8..cd282db81ff72 100644
--- a/smoke-test/tests/schema_fields/test_schemafields.py
+++ b/smoke-test/tests/schema_fields/test_schemafields.py
@@ -6,10 +6,7 @@
import datahub.metadata.schema_classes as models
import pytest
-from datahub.emitter.mce_builder import (
- make_dataset_urn,
- make_schema_field_urn,
-)
+from datahub.emitter.mce_builder import make_dataset_urn, make_schema_field_urn
from datahub.emitter.mcp import MetadataChangeProposalWrapper
from datahub.ingestion.api.common import PipelineContext, RecordEnvelope
from datahub.ingestion.api.sink import NoopWriteCallback
diff --git a/smoke-test/tests/structured_properties/test_structured_properties.py b/smoke-test/tests/structured_properties/test_structured_properties.py
index bf1b5b1292750..f2327a13df6d0 100644
--- a/smoke-test/tests/structured_properties/test_structured_properties.py
+++ b/smoke-test/tests/structured_properties/test_structured_properties.py
@@ -119,9 +119,11 @@ def create_property_definition(
qualifiedName=f"{namespace}.{property_name}",
valueType=Urn.make_data_type_urn(value_type),
description="The retention policy for the dataset",
- entityTypes=[Urn.make_entity_type_urn(e) for e in entity_types]
- if entity_types
- else [Urn.make_entity_type_urn("dataset")],
+ entityTypes=(
+ [Urn.make_entity_type_urn(e) for e in entity_types]
+ if entity_types
+ else [Urn.make_entity_type_urn("dataset")]
+ ),
cardinality=cardinality,
allowedValues=allowed_values,
)
@@ -137,7 +139,7 @@ def create_property_definition(
def attach_property_to_entity(
urn: str,
property_name: str,
- property_value: Union[str, float, List[str | float]],
+ property_value: Union[str, float, List[Union[str, float]]],
graph: DataHubGraph,
namespace: str = default_namespace,
):
From 32247dac9fbd0b7865b97f04b21cbf5afa137406 Mon Sep 17 00:00:00 2001
From: Hyejin Yoon <0327jane@gmail.com>
Date: Wed, 31 Jul 2024 09:09:02 +0900
Subject: [PATCH 05/15] docs: fix learning center color scheme & typo (#11043)
---
.../src/learn/_components/LearnListPage/index.jsx | 3 ++-
.../learn/_components/LearnListPage/styles.module.scss | 8 +++++++-
docs-website/src/learn/business-metric.md | 9 +++++----
.../pages/docs/_components/CustomerCardSection/index.jsx | 4 ++--
4 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/docs-website/src/learn/_components/LearnListPage/index.jsx b/docs-website/src/learn/_components/LearnListPage/index.jsx
index 4df87a340f21e..1ceec9afa1e8a 100644
--- a/docs-website/src/learn/_components/LearnListPage/index.jsx
+++ b/docs-website/src/learn/_components/LearnListPage/index.jsx
@@ -58,8 +58,9 @@ function BlogListPageContent(props) {
For:
{audiences.map((audience) => (
diff --git a/docs-website/src/learn/_components/LearnListPage/styles.module.scss b/docs-website/src/learn/_components/LearnListPage/styles.module.scss
index d08b48a011de0..ce86e124afdb8 100644
--- a/docs-website/src/learn/_components/LearnListPage/styles.module.scss
+++ b/docs-website/src/learn/_components/LearnListPage/styles.module.scss
@@ -4,4 +4,10 @@
align-items: center;
gap: 10px;
flex-wrap: wrap;
-}
\ No newline at end of file
+
+ .buttonActive {
+ background-color: var(--ifm-color-primary);
+ border: 1px solid var(--ifm-color-primary);
+ color: #ffffff;
+ }
+}
diff --git a/docs-website/src/learn/business-metric.md b/docs-website/src/learn/business-metric.md
index ff8677b998ead..1378168f42195 100644
--- a/docs-website/src/learn/business-metric.md
+++ b/docs-website/src/learn/business-metric.md
@@ -72,13 +72,14 @@ DataHub Cloud offers comprehensive features designed to tackle the challenges of
- **[Approval Flows](https://datahubproject.io/docs/managed-datahub/approval-workflows):** Structured workflows for approving changes to metric definitions, maintaining accuracy and reliability.
- -
-![Untitled](https://prod-files-secure.s3.us-west-2.amazonaws.com/f818df0d-1067-44ab-99e1-8cf45d930c01/33ebd070-32a1-4875-b220-c31373f5eedf/Untitled.png)
+
+
+
+ Lineage Tracking
+
- **[Lineage Tracking](https://datahubproject.io/docs/generated/lineage/lineage-feature-guide):** Tools to track the origin and transformations of metrics, ensuring they align with standardized definitions.
- -
-![Screenshot 2024-07-10 at 12.07.28 PM.png](https://prod-files-secure.s3.us-west-2.amazonaws.com/f818df0d-1067-44ab-99e1-8cf45d930c01/39503957-ad64-4d2d-a5b2-b140abfc1f6c/Screenshot_2024-07-10_at_12.07.28_PM.png)
By implementing these solutions, you can ensure that your business metrics are consistently defined and accurately used across all teams, supporting reliable analysis and decision-making.
diff --git a/docs-website/src/pages/docs/_components/CustomerCardSection/index.jsx b/docs-website/src/pages/docs/_components/CustomerCardSection/index.jsx
index ca34d89df8701..27067cb3930eb 100644
--- a/docs-website/src/pages/docs/_components/CustomerCardSection/index.jsx
+++ b/docs-website/src/pages/docs/_components/CustomerCardSection/index.jsx
@@ -57,7 +57,7 @@ const customerCardContent = [
3. Community-driven project which continually evolves with industry trends and best practices
>
),
- to: "https://www.acryldata.io/blog/data-contracts-in-datahub-combining-verifiability-with-holistic-data-management?utm_source=datahub&utm_medium=referral&utm_content=blog",
+ to: "https://youtu.be/wsCFnElN_Wo?si=i-bNAQAsbHJq5O9-",
},
{
customer: "Airtel",
@@ -75,7 +75,7 @@ const customerCardContent = [
DataHub to take their data mesh implementation to the next level.
>
),
- to: "https://youtu.be/wsCFnElN_Wo?si=i-bNAQAsbHJq5O9-",
+ to: "https://www.youtube.com/watch?v=yr24mM91BN4",
},
];
From bcde06d498b9f6c08f0297b8087b0fccee4ac631 Mon Sep 17 00:00:00 2001
From: Hyejin Yoon <0327jane@gmail.com>
Date: Wed, 31 Jul 2024 09:13:16 +0900
Subject: [PATCH 06/15] feat: add cloud main page (#11017)
Co-authored-by: Jay <159848059+jayacryl@users.noreply.github.com>
---
docs-website/docusaurus.config.js | 6 +
.../cloud/CompanyLogos/customersData.json | 88 +++++++++
.../src/pages/cloud/CompanyLogos/index.js | 49 +++++
.../cloud/CompanyLogos/logos.module.scss | 56 ++++++
.../src/pages/cloud/Enterprise/index.js | 48 +++++
.../pages/cloud/Enterprise/styles.module.scss | 40 ++++
.../src/pages/cloud/FeatureCards/index.js | 130 +++++++++++++
.../cloud/FeatureCards/styles.module.scss | 164 ++++++++++++++++
.../src/pages/cloud/UnifiedTabs/index.js | 76 ++++++++
.../cloud/UnifiedTabs/styles.module.scss | 179 ++++++++++++++++++
docs-website/src/pages/cloud/index.js | 90 +++++++++
.../src/pages/cloud/styles.module.scss | 51 +++++
.../static/img/assets/data-discovery.svg | 4 +
.../static/img/assets/data-governance.svg | 6 +
docs-website/static/img/assets/data-ob.svg | 8 +
docs-website/static/img/cloud-bg.png | Bin 0 -> 239760 bytes
.../img/logos/companies/mediamarkt-saturn.png | Bin 0 -> 30437 bytes
.../static/img/logos/companies/netflix.png | Bin 0 -> 16470 bytes
.../static/img/logos/companies/visa.png | Bin 0 -> 16052 bytes
.../img/logos/scrollingCompanies/acertus.webp | Bin 0 -> 7210 bytes
.../logos/scrollingCompanies/autoscout24.webp | Bin 0 -> 6550 bytes
.../logos/scrollingCompanies/betterup.webp | Bin 0 -> 6012 bytes
.../img/logos/scrollingCompanies/depop.webp | Bin 0 -> 4530 bytes
.../logos/scrollingCompanies/dpg-media.png | Bin 0 -> 2655 bytes
.../logos/scrollingCompanies/dpg_media.webp | Bin 0 -> 2958 bytes
.../scrollingCompanies/event-callout-img.png | Bin 0 -> 146084 bytes
.../scrollingCompanies/graham-stirling.png | Bin 0 -> 9070 bytes
.../logos/scrollingCompanies/icon-check.svg | 14 ++
.../img/logos/scrollingCompanies/log-saxo.svg | 16 ++
.../img/logos/scrollingCompanies/myob.png | Bin 0 -> 2576 bytes
.../img/logos/scrollingCompanies/myob.webp | Bin 0 -> 3304 bytes
.../logos/scrollingCompanies/notion-logo.png | Bin 0 -> 19679 bytes
.../img/logos/scrollingCompanies/notion.webp | Bin 0 -> 4460 bytes
.../logos/scrollingCompanies/ovo_energy.webp | Bin 0 -> 4410 bytes
.../logos/scrollingCompanies/regeneron.png | Bin 0 -> 3454 bytes
.../logos/scrollingCompanies/regeneron.webp | Bin 0 -> 6292 bytes
.../logos/scrollingCompanies/riskified.png | Bin 0 -> 3051 bytes
.../logos/scrollingCompanies/riskified.webp | Bin 0 -> 6668 bytes
.../logos/scrollingCompanies/robinhood.png | Bin 0 -> 3547 bytes
.../logos/scrollingCompanies/robinhood.webp | Bin 0 -> 6780 bytes
.../logos/scrollingCompanies/saxo_bank.webp | Bin 0 -> 3592 bytes
.../logos/scrollingCompanies/screenshot.png | Bin 0 -> 154992 bytes
.../logos/scrollingCompanies/twilio-bg.png | Bin 0 -> 356413 bytes
.../img/logos/scrollingCompanies/twilio.png | Bin 0 -> 3329 bytes
.../img/logos/scrollingCompanies/twilio.webp | Bin 0 -> 4308 bytes
.../img/logos/scrollingCompanies/xero.png | Bin 0 -> 3079 bytes
.../img/logos/scrollingCompanies/xero.webp | Bin 0 -> 2542 bytes
.../img/logos/scrollingCompanies/zendesk.webp | Bin 0 -> 6306 bytes
48 files changed, 1025 insertions(+)
create mode 100644 docs-website/src/pages/cloud/CompanyLogos/customersData.json
create mode 100644 docs-website/src/pages/cloud/CompanyLogos/index.js
create mode 100644 docs-website/src/pages/cloud/CompanyLogos/logos.module.scss
create mode 100644 docs-website/src/pages/cloud/Enterprise/index.js
create mode 100644 docs-website/src/pages/cloud/Enterprise/styles.module.scss
create mode 100644 docs-website/src/pages/cloud/FeatureCards/index.js
create mode 100644 docs-website/src/pages/cloud/FeatureCards/styles.module.scss
create mode 100644 docs-website/src/pages/cloud/UnifiedTabs/index.js
create mode 100644 docs-website/src/pages/cloud/UnifiedTabs/styles.module.scss
create mode 100644 docs-website/src/pages/cloud/index.js
create mode 100644 docs-website/src/pages/cloud/styles.module.scss
create mode 100644 docs-website/static/img/assets/data-discovery.svg
create mode 100644 docs-website/static/img/assets/data-governance.svg
create mode 100644 docs-website/static/img/assets/data-ob.svg
create mode 100644 docs-website/static/img/cloud-bg.png
create mode 100644 docs-website/static/img/logos/companies/mediamarkt-saturn.png
create mode 100755 docs-website/static/img/logos/companies/netflix.png
create mode 100644 docs-website/static/img/logos/companies/visa.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/acertus.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/autoscout24.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/betterup.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/depop.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/dpg-media.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/dpg_media.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/event-callout-img.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/graham-stirling.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/icon-check.svg
create mode 100644 docs-website/static/img/logos/scrollingCompanies/log-saxo.svg
create mode 100644 docs-website/static/img/logos/scrollingCompanies/myob.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/myob.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/notion-logo.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/notion.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/ovo_energy.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/regeneron.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/regeneron.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/riskified.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/riskified.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/robinhood.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/robinhood.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/saxo_bank.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/screenshot.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/twilio-bg.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/twilio.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/twilio.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/xero.png
create mode 100644 docs-website/static/img/logos/scrollingCompanies/xero.webp
create mode 100644 docs-website/static/img/logos/scrollingCompanies/zendesk.webp
diff --git a/docs-website/docusaurus.config.js b/docs-website/docusaurus.config.js
index e3336d4970eed..ab033f7b04e4b 100644
--- a/docs-website/docusaurus.config.js
+++ b/docs-website/docusaurus.config.js
@@ -76,6 +76,12 @@ module.exports = {
label: "Docs",
position: "right",
},
+ {
+ to: "/cloud",
+ activeBasePath: "cloud",
+ label: "Cloud",
+ position: "right",
+ },
{
to: "/learn",
activeBasePath: "learn",
diff --git a/docs-website/src/pages/cloud/CompanyLogos/customersData.json b/docs-website/src/pages/cloud/CompanyLogos/customersData.json
new file mode 100644
index 0000000000000..09943e8ef172f
--- /dev/null
+++ b/docs-website/src/pages/cloud/CompanyLogos/customersData.json
@@ -0,0 +1,88 @@
+{
+ "customers": [
+ {
+ "link": {
+ "href": "https://robinhood.com",
+ "blank": true
+ },
+ "logo": {
+ "asset": {
+ "_ref": "/img/logos/scrollingCompanies/acertus.webp"
+ },
+ "alt": "Robinhood"
+ }
+ },
+ {
+ "link": {
+ "href": "https://www.dpgmediagroup.com/",
+ "blank": true
+ },
+ "logo": {
+ "asset": {
+ "_ref": "/img/logos/scrollingCompanies/autoscout24.webp"
+ },
+ "alt": "DPG Media"
+ }
+ },
+ {
+ "link": {
+ "href": "https://www.twilio.com",
+ "blank": true
+ },
+ "logo": {
+ "asset": {
+ "_ref": "/img/logos/scrollingCompanies/betterup.webp"
+ },
+ "alt": "Twilio"
+ }
+ },
+ {
+ "link": {
+ "href": "https://myob.com",
+ "blank": true
+ },
+ "logo": {
+ "asset": {
+ "_ref": "/img/logos/scrollingCompanies/depop.webp"
+ },
+ "alt": "Myob"
+ }
+ },
+ {
+ "link": {
+ "href": "https://regeneron.com",
+ "blank": true
+ },
+ "logo": {
+ "asset": {
+ "_ref": "/img/logos/scrollingCompanies/dpg_media.webp"
+ },
+ "alt": "Regeneron"
+ }
+ },
+ {
+ "link": {
+ "href": "https://riskified.com",
+ "blank": true
+ },
+ "logo": {
+ "asset": {
+ "_ref": "/img/logos/scrollingCompanies/myob.webp"
+ },
+ "alt": "Riskified"
+ }
+ },
+ {
+ "link": {
+ "href": "https://xero.com",
+ "blank": true
+ },
+ "logo": {
+ "asset": {
+ "_ref": "/img/logos/scrollingCompanies/notion.webp"
+ },
+ "alt": "Xero"
+ }
+ }
+ ]
+}
diff --git a/docs-website/src/pages/cloud/CompanyLogos/index.js b/docs-website/src/pages/cloud/CompanyLogos/index.js
new file mode 100644
index 0000000000000..cd3a79f277893
--- /dev/null
+++ b/docs-website/src/pages/cloud/CompanyLogos/index.js
@@ -0,0 +1,49 @@
+import React, { useEffect, useRef } from 'react';
+import Link from '@docusaurus/Link';
+import styles from './logos.module.scss';
+import customersData from './customersData.json';
+import clsx from 'clsx';
+
+const ScrollingCustomers = ({ noOverlay = true, spacing, ...rest }) => {
+ const customers = customersData.customers;
+ const duplicatedLogos = [...customers, ...customers, ...customers];
+ const scrollingRef = useRef(null);
+
+ useEffect(() => {
+ if (scrollingRef.current) {
+ scrollingRef.current.style.setProperty('--customers-length', customers.length);
+ }
+ }, [customers.length]);
+
+ return (
+
+
+ {duplicatedLogos.map((customer, index) => (
+
+
+
+ ))}
+
+
+ );
+};
+
+export default ScrollingCustomers;
diff --git a/docs-website/src/pages/cloud/CompanyLogos/logos.module.scss b/docs-website/src/pages/cloud/CompanyLogos/logos.module.scss
new file mode 100644
index 0000000000000..bc76e6c072f1e
--- /dev/null
+++ b/docs-website/src/pages/cloud/CompanyLogos/logos.module.scss
@@ -0,0 +1,56 @@
+@keyframes scrollingCustomerAnimate {
+ 0% {
+ transform: translateX(0);
+ }
+ 100% {
+ transform: translateX(calc(var(--customers-length) * -220px));
+ }
+}
+
+@media (max-width: 767px) {
+ @keyframes scrollingCustomerAnimate {
+ 0% {
+ transform: translateX(0);
+ }
+ 100% {
+ transform: translateX(calc(var(--customers-length) * -166px));
+ }
+ }
+}
+
+.scrollingCustomers {
+ position: relative;
+ overflow: hidden;
+}
+
+
+.scrollingCustomers__inner {
+ display: flex;
+ padding: 1.25rem 0;
+ position: relative;
+ align-items: center;
+ animation: scrollingCustomerAnimate 15s linear infinite;
+}
+
+.scrollingCustomers__inner img {
+ max-width: 100px!important;
+ min-width: unset;
+ filter: invert(1) brightness(0) contrast(100); // make image black
+}
+
+@media (max-width: 767px) {
+ .scrollingCustomers__inner img {
+ width: 126px !important;
+ }
+}
+
+.animateScrollingCustomers {
+ display: flex;
+ animation: scrollingCustomerAnimate 15s linear infinite;
+}
+
+@media (max-width: 767px) {
+ .animateScrollingCustomers {
+ width: calc(var(--customers-length) * 126px);
+ }
+}
diff --git a/docs-website/src/pages/cloud/Enterprise/index.js b/docs-website/src/pages/cloud/Enterprise/index.js
new file mode 100644
index 0000000000000..b717e96c8343c
--- /dev/null
+++ b/docs-website/src/pages/cloud/Enterprise/index.js
@@ -0,0 +1,48 @@
+import React from "react";
+import clsx from "clsx";
+import Link from "@docusaurus/Link";
+import styles from "./styles.module.scss";
+
+
+const featuresContent = [
+ {
+ title: "99.5% Uptime SLA",
+ description: "We’ll focus on keeping DataHub running, so you can focus on the things that really matter."
+ },
+ {
+ title: "In-VPC Ingestion and
Data Quality evaluation",
+ description: "So your actual data never leaves your network."
+ },
+ {
+ title: "SOC-2 Compliant",
+ description: "An incredibly high level of security you can trust."
+ },
+];
+
+const Feature = ({ title, description }) => {
+ return (
+
+ );
+};
+
+const Features = () =>
+ featuresContent?.length > 0 ? (
+
+
+
Enterprise Ready
+
+ {featuresContent.map((props, idx) => (
+
+ ))}
+
+
+ +4 benefits
+
+
+
+ ) : null;
+
+export default Features;
diff --git a/docs-website/src/pages/cloud/Enterprise/styles.module.scss b/docs-website/src/pages/cloud/Enterprise/styles.module.scss
new file mode 100644
index 0000000000000..9d95dcc3990b6
--- /dev/null
+++ b/docs-website/src/pages/cloud/Enterprise/styles.module.scss
@@ -0,0 +1,40 @@
+
+.container {
+ padding: 2vh 6vh;
+ padding-bottom: 8vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+.wrapper {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ width: 90%;
+ max-width: 1200px;
+
+}
+
+.title {
+ font-size: 2.8rem;
+ font-weight: 600;
+ margin-bottom: 2rem;
+}
+
+.moreBenefits {
+ font-weight: 400;
+ color: var(--ifm-color-primary);
+
+ &:hover {
+ text-decoration: none;
+ opacity: 0.8;
+ }
+
+}
+
+@media (max-width: 767px) {
+
+ .container {
+ padding: 0px 36px;
+ }
+}
\ No newline at end of file
diff --git a/docs-website/src/pages/cloud/FeatureCards/index.js b/docs-website/src/pages/cloud/FeatureCards/index.js
new file mode 100644
index 0000000000000..4a45cbcbe1717
--- /dev/null
+++ b/docs-website/src/pages/cloud/FeatureCards/index.js
@@ -0,0 +1,130 @@
+import React from "react";
+import clsx from "clsx";
+import styles from "./styles.module.scss";
+import Link from "@docusaurus/Link";
+import { CheckCircleOutlined } from "@ant-design/icons";
+
+const data = {
+ sections: [
+ {
+ title: "Data Discovery",
+ icon: "/img/assets/data-discovery.svg",
+ cloudPageLink: "https://www.acryldata.io/acryl-datahub",
+ cloudBenefits: [
+ { text: "Enhanced search ranking", link: "" }, // →
+ { text: "Personalization for every persona", link: "" }, // →
+ { text: "A browser extension for BI Tools", link: "" }, // →
+ { text: "AI-Powered Documentation", link: "" }, // →
+ { text: "No-Code Automations", link: "" }, // →
+ ],
+ coreBenefits: [
+ { text: "Integrations for 50+ data sources"},
+ { text: "Lineage for tables, columns, and jobs"},
+ ],
+ hiddenCoreBenefits: "+4 benefits",
+ hiddenCoreBenefitsLink: '/docs/managed-datahub/managed-datahub-overview#search-and-discovery',
+ },
+ {
+ title: "Data Observability",
+ icon: "/img/assets/data-ob.svg",
+ cloudPageLink: "https://www.acryldata.io/observe",
+ cloudBenefits: [
+ { text: "Continuous data quality monitors", link: "" }, // →
+ { text: "End-to-end data incident tracking & management", link: "" }, // →
+ { text: "AI-Driven anomaly detection", link: "" }, // →
+ { text: "Executive Data Health Dashboard", link: "" }, // →
+ { text: "On-demand data quality evaluation via APIs & UI", link: "" }, // →
+ ],
+ coreBenefits: [
+ { text: "Surface data quality results"},
+ { text: "Create and manage data contracts" },
+ ],
+ hiddenCoreBenefits: "+1 benefit",
+ hiddenCoreBenefitsLink: '/docs/managed-datahub/managed-datahub-overview#data-observability',
+ },
+ {
+ title: "Data Governance",
+ icon: "/img/assets/data-governance.svg",
+ cloudPageLink: "https://www.acryldata.io/acryl-datahub#governance",
+ cloudBenefits: [
+ { text: "Human-assisted asset certification workflows", link: "" }, // →
+ { text: "Automations to enforce governance standards", link: "" }, // →
+ { text: "End-to-end glossary management workflows", link: "" }, // →
+ { text: "Ownership management workflows", link: "" }, // →
+ ],
+ coreBenefits: [
+ { text: "Shift-left governance"},
+ { text: "Business glossaries"},
+ ],
+ hiddenCoreBenefits: "+1 benefit",
+ hiddenCoreBenefitsLink: '/docs/managed-datahub/managed-datahub-overview#data-governance',
+ },
+ ],
+};
+
+const Features = () => (
+
+ {data.sections.map((section, sectionIndex) => (
+
+
+
+
+
+
+
{section.title}
+
+
+
+
DataHub Cloud includes:
+
+ {section.cloudBenefits.map((benefit, index) => (
+
+ ))}
+
+
+ Explore in DataHub Cloud
+
+
+
+
+
+
+ ))}
+
+);
+
+export default Features;
diff --git a/docs-website/src/pages/cloud/FeatureCards/styles.module.scss b/docs-website/src/pages/cloud/FeatureCards/styles.module.scss
new file mode 100644
index 0000000000000..cfba5e217374d
--- /dev/null
+++ b/docs-website/src/pages/cloud/FeatureCards/styles.module.scss
@@ -0,0 +1,164 @@
+.container {
+ padding: 48px 20px;
+}
+
+.rowItem {
+ justify-content: center;
+}
+
+.cardGroup {
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ align-items: flex-start;
+ margin: 0rem 1.2rem 2rem 1.2rem;
+}
+
+.cardGroupInverse {
+ flex-direction: row-reverse;
+ .coreBenefitCard {
+ margin-left: 15rem;
+ }
+}
+
+.title {
+ display: flex;
+ align-items: center;
+ margin: 2rem;
+
+ .icon {
+ width: 4rem;
+ height: 4rem;
+ margin-right: 1rem;
+ }
+
+ .titleText {
+ font-size: 32px;
+ font-weight: 700;
+ }
+}
+
+.card {
+ background: white;
+ padding: 40px;
+ margin: 0;
+}
+
+.sectionTitle {
+ font-size: 24px;
+ font-weight: 600;
+ line-height: 33px;
+}
+
+.benefitIcon {
+ width: 32px;
+ height: 32px;
+ align-items: center;
+}
+
+.moreBenefits {
+ font-family: Manrope;
+ font-size: 20px;
+ font-weight: 400;
+ line-height: 30px;
+ text-align: left;
+ color: rgba(24, 144, 255, 1);
+
+ &:hover {
+ text-decoration: none;
+ opacity: 0.8;
+ }
+}
+
+.featureList {
+ display: grid;
+ margin-top: 1rem;
+ font-weight: 400;
+ font-size: 20px;
+ text-align: left;
+ line-height: 27px;
+}
+
+.exploreButton {
+ margin-top: 24px;
+}
+
+.cloudBenefitCard {
+ border-radius: 16px;
+ border: 2px solid rgba(24, 144, 255, 1);
+ box-shadow: 0px 4px 25px 0px rgba(24, 144, 255, 0.25);
+ background: linear-gradient(180deg, #ffffff 27%, #e4f7ff 87%);
+ min-width: 543px;
+ z-index: 2;
+ position: relative;
+}
+
+.coreBenefitCard {
+ border-radius: 16px;
+ border: 1px solid rgba(227, 227, 227, 1);
+ min-width: 451px;
+ z-index: 1;
+ position: relative;
+ margin-top: 14rem;
+ margin-left: -48px;
+ padding-left: 72px;
+}
+
+.reversedRow .coreBenefitCard {
+ margin-left: auto!important;
+ padding-left: auto!important;
+ margin-right: -48px;
+ padding-right: 72px;
+}
+
+.coreBenefitCard h3 {
+ margin-bottom: 2rem;
+}
+.coreBenefit {
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 1rem;
+}
+
+.cloudBenefit {
+ color: rgba(24, 144, 255, 1);
+ margin-bottom: 1rem;
+}
+.cloudBenefitLink {
+ display: flex;
+ flex-direction: row;
+}
+.cloudBenefitLinkText {
+ display: block;
+}
+
+.reversedRow {
+ flex-direction: row-reverse;
+}
+
+.reversedRow .title {
+ justify-content: flex-end;
+}
+
+@media (max-width: 768px) {
+ .cardGroup {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .cardGroupInverse {
+ flex-direction: column;
+ }
+
+ .cloudBenefitCard, .coreBenefitCard {
+ min-width: 100%;
+ margin-top: 20px;
+ }
+
+ .coreBenefitCard {
+ margin-top: -40px;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ padding-top: 80px;
+ }
+}
diff --git a/docs-website/src/pages/cloud/UnifiedTabs/index.js b/docs-website/src/pages/cloud/UnifiedTabs/index.js
new file mode 100644
index 0000000000000..0c7ff50ce5447
--- /dev/null
+++ b/docs-website/src/pages/cloud/UnifiedTabs/index.js
@@ -0,0 +1,76 @@
+import React, { useState } from 'react';
+import { CaretUpFilled } from '@ant-design/icons';
+import styles from './styles.module.scss';
+import clsx from 'clsx';
+
+const TabbedComponent = () => {
+ const [activeTab, setActiveTab] = useState(0);
+
+ const tabs = [
+ {
+ title: 'Data Discovery',
+ description: 'All the search and discovery features of DataHub Core you already love, enhanced.',
+ icon: "/img/assets/data-discovery.svg",
+ link: "https://www.acryldata.io/acryl-datahub",
+ image: 'https://raw.githubusercontent.com/datahub-project/static-assets/main/imgs/saas/demo/discovery.webm',
+ },
+ {
+ title: 'Data Observability',
+ description: 'Detect, resolve, and prevent data quality issues before they impact your business. Unify data health signals from all your data quality tools, including dbt tests and more.',
+ icon: "/img/assets/data-ob.svg",
+ link: "https://www.acryldata.io/observe",
+ image: 'https://raw.githubusercontent.com/datahub-project/static-assets/main/imgs/saas/demo/observe.webm',
+ },
+ {
+ title: 'Data Governance',
+ description: 'Powerful Automation, Reporting and Organizational tools to help you govern effectively.',
+ icon: "/img/assets/data-governance.svg",
+ link: "https://www.acryldata.io/acryl-datahub#governance",
+ image: 'https://raw.githubusercontent.com/datahub-project/static-assets/main/imgs/saas/demo/governance.webm',
+ },
+ ];
+
+ return (
+
+
One platform to rule them all
+
+
+ {tabs.map((tab, index) => (
+
+
+
+ {activeTab === index && (
+
+ )}
+
+ {activeTab === index && (
+
+ )}
+
+ ))}
+
+
+
+
+ );
+};
+
+export default TabbedComponent;
diff --git a/docs-website/src/pages/cloud/UnifiedTabs/styles.module.scss b/docs-website/src/pages/cloud/UnifiedTabs/styles.module.scss
new file mode 100644
index 0000000000000..7549f74305355
--- /dev/null
+++ b/docs-website/src/pages/cloud/UnifiedTabs/styles.module.scss
@@ -0,0 +1,179 @@
+.tabbedComponent {
+ text-align: center;
+ padding: 20px;
+ padding-top: 48px;
+ padding-bottom: 48px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.title {
+ font-size: 2.5rem;
+ font-weight: 500;
+ margin-bottom: 2rem;
+ text-align: center;
+ position: relative;
+ padding: 0 2rem;
+ display: block;
+ width: 100%;
+
+ &:before, &:after {
+ content: " ";
+ height: 2px;
+ width: calc((100vw - 468px - 120px)/2);
+ background: #EEE;
+ display: block;
+ position: absolute;
+ top: 50%;
+ }
+
+ &:before {
+ left: 0;
+ }
+
+ &:after {
+ right: 0;
+ }
+}
+
+.container {
+ display: flex;
+ flex-direction: column; // Changed to column for mobile view
+ background: white;
+ border-radius: 1rem;
+ overflow: hidden;
+ box-shadow: 0 4px 4px rgba(0, 0, 0, 0.1);
+ margin: 0rem 5rem;
+ max-width: 1200px;
+ width: 100%;
+}
+
+.tabs {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+}
+
+.tab {
+ align-items: center;
+ margin: 0;
+ border-bottom: 1px solid #e0e0e0;
+ position: relative;
+
+ &.activeTab {
+ border-bottom: none;
+ }
+}
+
+.activeTab .tabButton {
+ border-bottom: 1px solid #f1f1f1;
+}
+
+.tabButton {
+ padding: 1.5rem;
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ justify-content: space-between;
+
+ .tabButton:focus {
+ outline: none;
+ }
+
+ .tabTitle {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin-left: 1rem;
+ flex-grow: 1;
+ text-align: left;
+ }
+
+ .icon {
+ width: 2.3rem;
+ height: 2.3rem;
+ }
+}
+
+.arrow {
+ margin-left: auto;
+ transition: transform 0.2s;
+ color: black;
+}
+
+.upsideDown {
+ transform: rotate(180deg);
+}
+
+.dropdown {
+ background-color: #ffffff;
+ margin-top: 5px;
+ text-align: left;
+ padding: 1rem 1.5rem;
+
+ .learnMore {
+ font-weight: 600;
+ font-size: 15px;
+ line-height: 23px;
+
+ &:hover {
+ text-decoration: none;
+ opacity: 0.8;
+ }
+ }
+}
+
+.imageContainer {
+ display: none;
+ justify-content: center;
+ align-items: center;
+ background-color: transparent;
+ margin: 1rem 0; // Added margin for spacing
+ height: 380px;
+ border-radius: 24px;
+}
+
+.tabImage {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ padding: 4px;
+ align-items: center;
+ justify-content: center;
+}
+.tabImage video {
+ max-height: 100%;
+ max-width: 100%;
+ border-radius: 22px;
+ box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.mobileImageContainer {
+ @media (max-width: 767px) {
+ display: flex;
+ }
+}
+
+.webImageContainer {
+ @media (min-width: 768px) {
+ display: flex;
+ flex-grow: 1;
+ }
+}
+
+@media (min-width: 768px) {
+ .container {
+ flex-direction: row; // Change back to row for larger screens
+ padding: 40px 32px;
+ }
+ .tabs {
+ width: 440px;
+ }
+
+ .imageContainer {
+ margin: 1rem; // Reset margin for larger screens
+ }
+}
diff --git a/docs-website/src/pages/cloud/index.js b/docs-website/src/pages/cloud/index.js
new file mode 100644
index 0000000000000..48f6a58e23581
--- /dev/null
+++ b/docs-website/src/pages/cloud/index.js
@@ -0,0 +1,90 @@
+import React from "react";
+import Layout from "@theme/Layout";
+import Link from "@docusaurus/Link";
+import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
+import Enterprise from "./Enterprise";
+import { Section } from "../_components/Section";
+import ScrollingCustomers from "./CompanyLogos";
+import clsx from "clsx";
+import styles from "./styles.module.scss";
+import UnifiedTabs from "./UnifiedTabs";
+import FeatureCards from "./FeatureCards";
+
+
+function Home() {
+ const context = useDocusaurusContext();
+ const { siteConfig = {} } = context;
+ // const { colorMode } = useColorMode();
+
+
+ if (siteConfig.customFields.isSaas) {
+ window.location.replace("/docs");
+ }
+
+ return !siteConfig.customFields.isSaas ? (
+
+
+
+
+
+
Try DataHub Cloud
+
+ Introducing DataHub as a Managed Service
+
with Data Observability and Data Governance built-in.
+
+
+
+ Book Demo
+
+
+ Product Tour
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Get your free trial.
+
Data Discovery, Data Quality and Data Governance unified.
+
+
+ Book Demo
+
+
+ Product Tour
+
+
+
+
+
+
+
+
+ ) : null;
+}
+
+export default Home;
diff --git a/docs-website/src/pages/cloud/styles.module.scss b/docs-website/src/pages/cloud/styles.module.scss
new file mode 100644
index 0000000000000..d1ac31f3ef8cc
--- /dev/null
+++ b/docs-website/src/pages/cloud/styles.module.scss
@@ -0,0 +1,51 @@
+.link {
+ &:hover {
+ text-decoration: none;
+ opacity: 0.8;
+ }
+}
+
+.bgSection {
+ background-color: #FAFAFA !important;
+ }
+
+.hero {
+ :global {
+ .button {
+ margin-right: 1rem;
+ }
+ }
+ .hero__title {
+ font-size: 4rem;
+ }
+
+ .hero__secondtitle {
+ font-size: 2rem;
+ font-weight: 300;
+ margin-bottom: 2rem;
+
+ }
+
+ .hero__subtitle {
+ margin-bottom: 2rem;
+ font-size: 1.75rem;
+ line-height: 2.5rem;
+ }
+
+ .buttonLightBlue {
+ color: #1990FF !important;
+ background: #EAF3FF;
+ border-color: #EAF3FF;
+ :hover {
+ background: #D6E7FF;
+ }
+ }
+
+ .learnMore {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-top: 0.5rem;
+
+ }
+ }
+
diff --git a/docs-website/static/img/assets/data-discovery.svg b/docs-website/static/img/assets/data-discovery.svg
new file mode 100644
index 0000000000000..1a6c6f36a231a
--- /dev/null
+++ b/docs-website/static/img/assets/data-discovery.svg
@@ -0,0 +1,4 @@
+
diff --git a/docs-website/static/img/assets/data-governance.svg b/docs-website/static/img/assets/data-governance.svg
new file mode 100644
index 0000000000000..b1db48e3e76bf
--- /dev/null
+++ b/docs-website/static/img/assets/data-governance.svg
@@ -0,0 +1,6 @@
+
diff --git a/docs-website/static/img/assets/data-ob.svg b/docs-website/static/img/assets/data-ob.svg
new file mode 100644
index 0000000000000..d630b0a2333a2
--- /dev/null
+++ b/docs-website/static/img/assets/data-ob.svg
@@ -0,0 +1,8 @@
+
diff --git a/docs-website/static/img/cloud-bg.png b/docs-website/static/img/cloud-bg.png
new file mode 100644
index 0000000000000000000000000000000000000000..392aec2ff936c574cf819073e84ddb08088b40ac
GIT binary patch
literal 239760
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