diff --git a/plugins/extractors/maxcompute/README.md b/plugins/extractors/maxcompute/README.md index 322a7c0d3..47b726f33 100644 --- a/plugins/extractors/maxcompute/README.md +++ b/plugins/extractors/maxcompute/README.md @@ -19,21 +19,23 @@ source: - schema_b tables: - schema_c.table_a + min_table_lifecycle: 8 concurrency: 10 ``` ## Inputs -| Key | Value | Example | Description | | -| :-- | :---- | :------ | :---------- | :-- | -| `project_name` | `string` | `goto_test` | MaxCompute Project Name | *required* | -| `endpoint_project` | `string` | `http://goto_test-maxcompute.com` | Endpoint Project URL | *required* | -| `access_key.id` | `string` | `access_key_id` | Access Key ID | *required* | -| `access_key.secret` | `string` | `access_key_secret` | Access Key Secret | *required* | -| `schema_name` | `string` | `DEFAULT` | Default schema name | *optional* | -| `exclude.schemas` | `[]string` | `["schema_a", "schema_b"]` | List of schemas to exclude | *optional* | -| `exclude.tables` | `[]string` | `["schema_c.table_a"]` | List of tables to exclude | *optional* | -| `concurrency` | `int` | `10` | Number of concurrent requests to MaxCompute | *optional* | +| Key | Value | Example | Description | | +|:------------------------------|:-----------|:----------------------------------|:----------------------------------------------------------------------------------------| :-- | +| `project_name` | `string` | `goto_test` | MaxCompute Project Name | *required* | +| `endpoint_project` | `string` | `http://goto_test-maxcompute.com` | Endpoint Project URL | *required* | +| `access_key.id` | `string` | `access_key_id` | Access Key ID | *required* | +| `access_key.secret` | `string` | `access_key_secret` | Access Key Secret | *required* | +| `schema_name` | `string` | `DEFAULT` | Default schema name | *optional* | +| `exclude.schemas` | `[]string` | `["schema_a", "schema_b"]` | List of schemas to exclude | *optional* | +| `exclude.tables` | `[]string` | `["schema_c.table_a"]` | List of tables to exclude | *optional* | +| `exclude.min_table_lifecycle` | `int` | `8` | Exclude tables with a lifecycle less than this value (in days). Value must more than 1. | *optional* | +| `concurrency` | `int` | `10` | Number of concurrent requests to MaxCompute | *optional* | ### *Notes* diff --git a/plugins/extractors/maxcompute/config/config.go b/plugins/extractors/maxcompute/config/config.go index c4ea58059..7e6a46777 100644 --- a/plugins/extractors/maxcompute/config/config.go +++ b/plugins/extractors/maxcompute/config/config.go @@ -11,8 +11,9 @@ type Config struct { } `mapstructure:"access_key"` SchemaName string `mapstructure:"schema_name,omitempty"` Exclude struct { - Schemas []string `mapstructure:"schemas"` - Tables []string `mapstructure:"tables"` + Schemas []string `mapstructure:"schemas"` + Tables []string `mapstructure:"tables"` + MinTableLifecycle int `mapstructure:"min_table_lifecycle"` } `mapstructure:"exclude,omitempty"` MaxPreviewRows int `mapstructure:"max_preview_rows,omitempty"` MixValues bool `mapstructure:"mix_values,omitempty"` diff --git a/plugins/extractors/maxcompute/maxcompute.go b/plugins/extractors/maxcompute/maxcompute.go index 6ef8a758b..fcac555af 100644 --- a/plugins/extractors/maxcompute/maxcompute.go +++ b/plugins/extractors/maxcompute/maxcompute.go @@ -36,7 +36,7 @@ const ( attributesDataResourceURL = "resource_url" attributesDataPartitionFields = "partition_fields" attributesDataLabel = "label" - attributesDataResourceType = "resource_type" + attributesDataLifecycle = "lifecycle" ) type Extractor struct { @@ -145,6 +145,7 @@ func (e *Extractor) Extract(ctx context.Context, emit plugins.Emit) error { continue } if contains(e.config.Exclude.Schemas, schema.Name()) { + e.logger.Info("skipping schema as it is in the exclude list", "schema", schema.Name()) continue } @@ -164,7 +165,9 @@ func (e *Extractor) fetchTablesFromSchema(ctx context.Context, schema *odps.Sche } for _, table := range tables { - if contains(e.config.Exclude.Tables, fmt.Sprintf("%s.%s", table.SchemaName(), table.Name())) { + tableName := fmt.Sprintf("%s.%s", schema.Name(), table.Name()) + if contains(e.config.Exclude.Tables, tableName) { + e.logger.Info("skipping table as it is in the exclude list", "table", tableName) continue } @@ -183,6 +186,18 @@ func (e *Extractor) processTable(ctx context.Context, schema *odps.Schema, table return err } + // If lifecycle is less than the minimum lifecycle (days), skip the table + if e.config.Exclude.MinTableLifecycle > 1 { + lifecyclePermanent := tableSchema.Lifecycle == -1 + lifecycleNotConfigured := tableSchema.Lifecycle == 0 + if !lifecyclePermanent && !lifecycleNotConfigured && tableSchema.Lifecycle < e.config.Exclude.MinTableLifecycle { + tableName := fmt.Sprintf("%s.%s", schema.Name(), table.Name()) + e.logger.Info("skipping table due to lifecycle less than minimum configured lifecycle", + "table", tableName, "lifecycle", tableSchema.Lifecycle) + return nil + } + } + asset, err := e.buildAsset(ctx, schema, table, tableType, tableSchema) if err != nil { e.logger.Error("failed to build asset", "table", table.Name(), "error", err) @@ -222,7 +237,7 @@ func (e *Extractor) buildAsset(ctx context.Context, schema *odps.Schema, Service: maxcomputeService, } - tableAttributesData := e.buildTableAttributesData(schemaName, tableType, table, tableSchema) + tableAttributesData := e.buildTableAttributesData(schemaName, tableType, tableSchema) if tableType == config.TableTypeView { query := tableSchema.ViewText @@ -266,7 +281,13 @@ func (e *Extractor) buildAsset(ctx context.Context, schema *odps.Schema, columns = append(columns, columnData) } + tableProfile := &v1beta2.TableProfile{} + if tableSchema.RecordNum >= 0 { + tableProfile.TotalRows = int64(tableSchema.RecordNum) + } + tableData := &v1beta2.Table{ + Profile: tableProfile, Attributes: utils.TryParseMapToProto(tableAttributesData), Columns: columns, CreateTime: timestamppb.New(time.Time(tableSchema.CreateTime)), @@ -326,7 +347,7 @@ func buildColumns(dataType datatype.DataType) []*v1beta2.Column { return columns } -func (e *Extractor) buildTableAttributesData(schemaName, tableType string, table *odps.Table, tableInfo *tableschema.TableSchema) map[string]interface{} { +func (e *Extractor) buildTableAttributesData(schemaName, tableType string, tableInfo *tableschema.TableSchema) map[string]interface{} { attributesData := map[string]interface{}{} attributesData[attributesDataProjectName] = e.config.ProjectName @@ -340,6 +361,10 @@ func (e *Extractor) buildTableAttributesData(schemaName, tableType string, table attributesData[attributesDataSQL] = tableInfo.ViewText } + if tableInfo.Lifecycle != 0 { + attributesData[attributesDataLifecycle] = tableInfo.Lifecycle + } + var partitionNames []interface{} if tableInfo.PartitionColumns != nil && len(tableInfo.PartitionColumns) > 0 { partitionNames = make([]interface{}, len(tableInfo.PartitionColumns)) diff --git a/plugins/extractors/maxcompute/maxcompute_test.go b/plugins/extractors/maxcompute/maxcompute_test.go index bb4353942..a8874c83b 100644 --- a/plugins/extractors/maxcompute/maxcompute_test.go +++ b/plugins/extractors/maxcompute/maxcompute_test.go @@ -83,6 +83,13 @@ func TestExtract(t *testing.T) { odps.NewTable(nil, projectID, "my_schema", "new_table"), } + table2 := []*odps.Table{ + odps.NewTable(nil, projectID, "my_schema", "dummy_table"), + odps.NewTable(nil, projectID, "my_schema", "new_table"), + odps.NewTable(nil, projectID, "my_schema", "table_lifecycle_3"), + odps.NewTable(nil, projectID, "my_schema", "table_lifecycle_8"), + } + c1 := tableschema.Column{ Name: "id", Type: datatype.BigIntType, @@ -139,6 +146,7 @@ func TestExtract(t *testing.T) { newTableSchemaBuilder.Name("new_table"). Columns(c3, c4) newTableSchema := newTableSchemaBuilder.Build() + newTableSchema.TableName = "new_table" newTableSchema.ViewText = "SELECT user_id, email FROM test-project-id.my_schema.new_table" newCreateTime, err := time.Parse(time.RFC3339, "2024-11-18T08:00:00Z") if err != nil { @@ -147,10 +155,26 @@ func TestExtract(t *testing.T) { newTableSchema.CreateTime = common.GMTTime(newCreateTime) newTableSchema.LastModifiedTime = common.GMTTime(newCreateTime) + // Schema for table_lifecycle_3 + tableLifecycle3Schema := newTableSchema // copy + tableLifecycle3Schema.TableName = "table_lifecycle_3" + tableLifecycle3Schema.ViewText = "SELECT user_id, email FROM test-project-id.my_schema.table_lifecycle_3" + tableLifecycle3Schema.Lifecycle = 3 + tableLifecycle3Schema.RecordNum = 100 + + // Schema for table_lifecycle_8 + tableLifecycle8Schema := newTableSchema // copy + tableLifecycle8Schema.TableName = "table_lifecycle_8" + tableLifecycle8Schema.ViewText = "SELECT user_id, email FROM test-project-id.my_schema.table_lifecycle_8" + tableLifecycle8Schema.Lifecycle = 8 + tableLifecycle8Schema.RecordNum = 200 + // Schema mapping schemaMapping := map[string]*tableschema.TableSchema{ - "dummy_table": &dummyTableSchema, - "new_table": &newTableSchema, + "dummy_table": &dummyTableSchema, + "new_table": &newTableSchema, + "table_lifecycle_3": &tableLifecycle3Schema, + "table_lifecycle_8": &tableLifecycle8Schema, } runTest := func(t *testing.T, cfg plugins.Config, mockSetup func(mockClient *mocks.MaxComputeClient), randomizer func(seed int64) func(int64) int64) ([]*v1beta2.Asset, error) { @@ -289,13 +313,16 @@ func TestExtract(t *testing.T) { }, "endpoint_project": "https://example.com/some-api", "exclude": map[string]interface{}{ - "tables": []string{"my_schema.dummy_table"}, + "tables": []string{"my_schema.dummy_table"}, + "min_table_lifecycle": 8, }, }, }, func(mockClient *mocks.MaxComputeClient) { mockClient.EXPECT().ListSchema(mock.Anything).Return(schema1, nil) - mockClient.EXPECT().ListTable(mock.Anything, "my_schema").Return(table1[1:], nil) - mockClient.EXPECT().GetTableSchema(mock.Anything, table1[1]).Return("MANAGED_TABLE", schemaMapping[table1[1].Name()], nil) + mockClient.EXPECT().ListTable(mock.Anything, "my_schema").Return(table2, nil) + mockClient.EXPECT().GetTableSchema(mock.Anything, table2[1]).Return("MANAGED_TABLE", schemaMapping[table2[1].Name()], nil) + mockClient.EXPECT().GetTableSchema(mock.Anything, table2[2]).Return("MANAGED_TABLE", schemaMapping[table2[2].Name()], nil) + mockClient.EXPECT().GetTableSchema(mock.Anything, table2[3]).Return("MANAGED_TABLE", schemaMapping[table2[3].Name()], nil) mockClient.EXPECT().GetMaskingPolicies(mock.Anything).Return(map[string][]string{}, nil) }, nil) diff --git a/plugins/extractors/maxcompute/testdata/expected-assets-with-table-exclusion.json b/plugins/extractors/maxcompute/testdata/expected-assets-with-table-exclusion.json index 2db0459d9..5cdec95b9 100644 --- a/plugins/extractors/maxcompute/testdata/expected-assets-with-table-exclusion.json +++ b/plugins/extractors/maxcompute/testdata/expected-assets-with-table-exclusion.json @@ -1,55 +1,123 @@ [ - { - "urn": "urn:maxcompute:test-project-id:table:test-project-id.my_schema.new_table", - "name": "new_table", - "service": "maxcompute", - "type": "table", - "url": "", - "description": "", - "data": { - "@type": "type.googleapis.com/gotocompany.assets.v1beta2.Table", - "profile": null, - "columns": [ - { - "name": "user_id", - "description": "Unique identifier for users", - "data_type": "BIGINT", - "is_nullable": false, - "length": "0", - "profile": null, - "columns": [], - "attributes": {} - }, - { - "name": "email", - "description": "User email address", - "data_type": "STRING", - "is_nullable": false, - "length": "0", - "profile": null, - "columns": [], - "attributes": {} - } - ], - "preview_fields": [], - "preview_rows": null, - "attributes": { - "project_name": "test-project-id", - "resource_url": "/projects/test-project-id/schemas/my_schema/tables/new_table", - "schema": "my_schema", - "sql": "SELECT user_id, email FROM test-project-id.my_schema.new_table", - "type": "MANAGED_TABLE" - }, - "create_time": "2024-11-18T08:00:00Z", - "update_time": "2024-11-18T08:00:00Z" + { + "urn": "urn:maxcompute:test-project-id:table:test-project-id.my_schema.new_table", + "name": "new_table", + "service": "maxcompute", + "type": "table", + "url": "", + "description": "", + "data": { + "@type": "type.googleapis.com/gotocompany.assets.v1beta2.Table", + "profile": { + "common_joins": [], + "filters": [], + "partition_key": "", + "partition_value": "", + "total_rows": "0", + "usage_count": "0" + }, + "columns": [ + { + "name": "user_id", + "description": "Unique identifier for users", + "data_type": "BIGINT", + "is_nullable": false, + "length": "0", + "profile": null, + "columns": [], + "attributes": {} }, - "owners": [], - "lineage": null, - "is_deleted": false, - "labels": {}, - "refreshed_at": null, - "event": null, - "create_time": "2024-11-18T08:00:00Z", - "update_time": "2024-11-18T08:00:00Z" - } + { + "name": "email", + "description": "User email address", + "data_type": "STRING", + "is_nullable": false, + "length": "0", + "profile": null, + "columns": [], + "attributes": {} + } + ], + "preview_fields": [], + "preview_rows": null, + "attributes": { + "project_name": "test-project-id", + "resource_url": "/projects/test-project-id/schemas/my_schema/tables/new_table", + "schema": "my_schema", + "sql": "SELECT user_id, email FROM test-project-id.my_schema.new_table", + "type": "MANAGED_TABLE" + }, + "create_time": "2024-11-18T08:00:00Z", + "update_time": "2024-11-18T08:00:00Z" + }, + "owners": [], + "lineage": null, + "is_deleted": false, + "labels": {}, + "refreshed_at": null, + "event": null, + "create_time": "2024-11-18T08:00:00Z", + "update_time": "2024-11-18T08:00:00Z" + }, + { + "urn": "urn:maxcompute:test-project-id:table:test-project-id.my_schema.table_lifecycle_8", + "name": "table_lifecycle_8", + "service": "maxcompute", + "type": "table", + "url": "", + "description": "", + "data": { + "@type": "type.googleapis.com/gotocompany.assets.v1beta2.Table", + "profile": { + "common_joins": [], + "filters": [], + "partition_key": "", + "partition_value": "", + "total_rows": "200", + "usage_count": "0" + }, + "columns": [ + { + "name": "user_id", + "description": "Unique identifier for users", + "data_type": "BIGINT", + "is_nullable": false, + "length": "0", + "profile": null, + "columns": [], + "attributes": {} + }, + { + "name": "email", + "description": "User email address", + "data_type": "STRING", + "is_nullable": false, + "length": "0", + "profile": null, + "columns": [], + "attributes": {} + } + ], + "preview_fields": [], + "preview_rows": null, + "attributes": { + "lifecycle": 8, + "project_name": "test-project-id", + "resource_url": "/projects/test-project-id/schemas/my_schema/tables/table_lifecycle_8", + "schema": "my_schema", + "sql": "SELECT user_id, email FROM test-project-id.my_schema.table_lifecycle_8", + "type": "MANAGED_TABLE" + }, + "create_time": "2024-11-18T08:00:00Z", + "update_time": "2024-11-18T08:00:00Z" + }, + "owners": [], + "lineage": null, + "is_deleted": false, + "labels": {}, + "refreshed_at": null, + "event": null, + "create_time": "2024-11-18T08:00:00Z", + "update_time": "2024-11-18T08:00:00Z" + } ] \ No newline at end of file diff --git a/plugins/extractors/maxcompute/testdata/expected-assets-with-view-lineage.json b/plugins/extractors/maxcompute/testdata/expected-assets-with-view-lineage.json index e40d75cc9..c96dfa16c 100644 --- a/plugins/extractors/maxcompute/testdata/expected-assets-with-view-lineage.json +++ b/plugins/extractors/maxcompute/testdata/expected-assets-with-view-lineage.json @@ -8,7 +8,14 @@ "description": "dummy table description", "data": { "@type": "type.googleapis.com/gotocompany.assets.v1beta2.Table", - "profile": null, + "profile": { + "common_joins": [], + "filters": [], + "partition_key": "", + "partition_value": "", + "total_rows": "0", + "usage_count": "0" + }, "columns": [ { "name": "id", @@ -92,7 +99,14 @@ "description": "", "data": { "@type": "type.googleapis.com/gotocompany.assets.v1beta2.Table", - "profile": null, + "profile": { + "common_joins": [], + "filters": [], + "partition_key": "", + "partition_value": "", + "total_rows": "0", + "usage_count": "0" + }, "columns": [ { "name": "user_id", diff --git a/plugins/extractors/maxcompute/testdata/expected-assets.json b/plugins/extractors/maxcompute/testdata/expected-assets.json index 749e67e47..4090ff8fc 100644 --- a/plugins/extractors/maxcompute/testdata/expected-assets.json +++ b/plugins/extractors/maxcompute/testdata/expected-assets.json @@ -8,7 +8,14 @@ "description": "dummy table description", "data": { "@type": "type.googleapis.com/gotocompany.assets.v1beta2.Table", - "profile": null, + "profile": { + "common_joins": [], + "filters": [], + "partition_key": "", + "partition_value": "", + "total_rows": "0", + "usage_count": "0" + }, "columns": [ { "name": "id", @@ -49,7 +56,7 @@ "name": "last_name", "profile": null } - ] + ] } ], "preview_fields": [], @@ -82,7 +89,14 @@ "description": "", "data": { "@type": "type.googleapis.com/gotocompany.assets.v1beta2.Table", - "profile": null, + "profile": { + "common_joins": [], + "filters": [], + "partition_key": "", + "partition_value": "", + "total_rows": "0", + "usage_count": "0" + }, "columns": [ { "name": "user_id", diff --git a/plugins/extractors/metabase/testdata/expected.json b/plugins/extractors/metabase/testdata/expected.json index 03243c493..80b57ba29 100644 --- a/plugins/extractors/metabase/testdata/expected.json +++ b/plugins/extractors/metabase/testdata/expected.json @@ -26,7 +26,6 @@ "data_source": "", "description": "HELPFUL CHART DESC", "event": null, - "is_deleted": false, "lineage": { "downstreams": [], "upstreams": [ @@ -40,7 +39,6 @@ }, "name": "Orders, Filtered by Quantity", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "metabase", "type": "", @@ -65,7 +63,6 @@ "data_source": "", "description": "This shows only exceptional users.", "event": null, - "is_deleted": false, "lineage": { "downstreams": [], "upstreams": [ @@ -79,7 +76,6 @@ }, "name": "Exceptional Users", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "metabase", "type": "", @@ -104,7 +100,6 @@ "data_source": "", "description": "Users, Average of Total Followers", "event": null, - "is_deleted": false, "lineage": { "downstreams": [], "upstreams": [ @@ -118,7 +113,6 @@ }, "name": "Users, Average of Total Followers and Cumulative sum of Total Likes, Filtered by Total Followers", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "metabase", "type": "", @@ -143,7 +137,6 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": { "downstreams": [], "upstreams": [ @@ -163,7 +156,6 @@ }, "name": "BCR", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "metabase", "type": "", @@ -188,14 +180,12 @@ "data_source": "", "description": "When template-tags contains invalid type it should not returning downstream", "event": null, - "is_deleted": false, "lineage": { "downstreams": [], "upstreams": [] }, "name": "Exceptional Users", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "metabase", "type": "", diff --git a/plugins/extractors/tableau/testdata/dashboards_proto.json b/plugins/extractors/tableau/testdata/dashboards_proto.json index dbeabe1e2..f565d2928 100644 --- a/plugins/extractors/tableau/testdata/dashboards_proto.json +++ b/plugins/extractors/tableau/testdata/dashboards_proto.json @@ -24,11 +24,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Obesity Map", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -47,11 +45,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "S&P Forward Returns", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -70,11 +66,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Heat Map", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -93,11 +87,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Scatter", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -116,11 +108,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "College", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -139,11 +129,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Flight Delays", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -162,11 +150,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Stocks", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -185,11 +171,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "S&P Returns Vs Conditions", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -208,11 +192,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Obesity Scatter Plot", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -231,11 +213,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "S&P Returns by Decade", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -333,11 +313,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "CustomerOverview", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -356,11 +334,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "QuotaAttainment", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -379,11 +355,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "What If Forecast", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -402,11 +376,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Product Detail Sheet", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -425,11 +397,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "CustomerScatter", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -448,11 +418,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Total Sales", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -471,11 +439,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Sale Map", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -494,11 +460,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "CommissionProjection", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -517,11 +481,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "ShipSummary", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -540,11 +502,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Sales by Product", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -563,11 +523,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Performance", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -586,11 +544,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "ProductDetails", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -609,11 +565,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Tooltip: Profit Ratio by City", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -632,11 +586,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Forecast", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -655,11 +607,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "ShippingTrend", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -678,11 +628,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "OTE", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -701,11 +649,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "CustomerRank", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -724,11 +670,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Sales", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -747,11 +691,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "DaystoShip", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -770,11 +712,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Sales by Segment", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -793,11 +733,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "ProductView", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -877,11 +815,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Best Day to Send InMails", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -900,11 +836,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Number of InMail Sent", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -923,11 +857,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Tooltip: volume InMail sent vs accepted by weekday", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -946,11 +878,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "InMail Response Rate", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -969,11 +899,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Tooltip: volume InMail sent vs accepted by time of day and weekday", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -992,11 +920,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "InMail response rate by time of day", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -1015,11 +941,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "All Team Memeber Engagement", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -1038,11 +962,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Weekday by time of day response rate heatmap", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -1061,11 +983,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Best Time to Send InMails", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -1084,11 +1004,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "InMail Response Rate Timeline", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -1107,11 +1025,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "Tooltip: volume InMail sent vs accepted by time of day", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "", @@ -1130,11 +1046,9 @@ "data_source": "", "description": "", "event": null, - "is_deleted": false, "lineage": null, "name": "InMail response by weekday", "owners": [], - "refreshed_at": null, "raw_query": "", "source": "tableau", "type": "",