Fixes: Drive Service (#23637)

* fix: added missing fields

* fix: java checkstyle and add mimeType

* Fix Data Asset Headers for drive assets

---------

Co-authored-by: Aniket Katkar <aniketkatkar97@gmail.com>
This commit is contained in:
Keshav Mohta 2025-10-03 22:36:30 +05:30 committed by GitHub
parent 1adc09f07e
commit 6f47baa264
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 328 additions and 27 deletions

View File

@ -146,13 +146,71 @@ public class DirectoryRepository extends EntityRepository<Directory> {
public void clearFields(Directory directory, EntityUtil.Fields fields) {
directory.withUsageSummary(
fields.contains("usageSummary") ? directory.getUsageSummary() : null);
directory.withNumberOfFiles(
fields.contains("numberOfFiles") ? directory.getNumberOfFiles() : null);
directory.withNumberOfSubDirectories(
fields.contains("numberOfSubDirectories") ? directory.getNumberOfSubDirectories() : null);
}
@Override
public void setFields(Directory directory, EntityUtil.Fields fields) {
directory.withService(getService(directory));
directory.withParent(getParentDirectory(directory));
directory.withChildren(fields.contains("children") ? getChildrenRefs(directory) : null);
// Calculate and set directory statistics
if (fields.contains("children")
|| fields.contains("numberOfFiles")
|| fields.contains("numberOfSubDirectories")
|| fields.contains("totalSize")) {
List<EntityReference> children = getChildrenRefs(directory);
directory.withChildren(fields.contains("children") ? children : null);
// Calculate statistics from children
if (children != null && !children.isEmpty()) {
int fileCount = 0;
int dirCount = 0;
long totalSize = 0L;
for (EntityReference child : children) {
if (FILE.equals(child.getType())) {
fileCount++;
// Get file size if available
try {
org.openmetadata.schema.entity.data.File file =
Entity.getEntity(child, "", Include.NON_DELETED);
if (file.getSize() != null) {
totalSize += file.getSize();
}
} catch (Exception e) {
// Ignore if file can't be loaded
}
} else if (DIRECTORY.equals(child.getType())) {
dirCount++;
} else if (SPREADSHEET.equals(child.getType())) {
fileCount++; // Count spreadsheets as files
try {
org.openmetadata.schema.entity.data.Spreadsheet spreadsheet =
Entity.getEntity(child, "", Include.NON_DELETED);
if (spreadsheet.getSize() != null) {
totalSize += spreadsheet.getSize();
}
} catch (Exception e) {
// Ignore if spreadsheet can't be loaded
}
}
}
directory.withNumberOfFiles(fileCount);
directory.withNumberOfSubDirectories(dirCount);
// Convert long to Integer, checking for overflow
directory.withTotalSize(
totalSize > 0
? (totalSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) totalSize)
: null);
}
} else {
directory.withChildren(null);
}
}
@Override

View File

@ -47,7 +47,7 @@ import org.openmetadata.schema.type.csv.CsvHeader;
import org.openmetadata.schema.type.csv.CsvImportResult;
import org.openmetadata.service.Entity;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.resources.services.DriveServiceResource;
import org.openmetadata.service.resources.services.drive.DriveServiceResource;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.FullyQualifiedName;

View File

@ -263,7 +263,9 @@ public class SpreadsheetRepository extends EntityRepository<Spreadsheet> {
new CsvHeader().withName("domain"),
new CsvHeader().withName("dataProducts"),
new CsvHeader().withName("experts"),
new CsvHeader().withName("reviewers"));
new CsvHeader().withName("reviewers"),
new CsvHeader().withName("createdTime"),
new CsvHeader().withName("modifiedTime"));
DOCUMENTATION = new CsvDocumentation().withHeaders(HEADERS).withSummary("Spreadsheet");
}
@ -339,7 +341,11 @@ public class SpreadsheetRepository extends EntityRepository<Spreadsheet> {
Pair.of(9, TagLabel.TagSource.CLASSIFICATION),
Pair.of(10, TagLabel.TagSource.GLOSSARY))))
.withDomains(getDomains(printer, csvRecord, 11))
.withDataProducts(getDataProducts(printer, csvRecord, 12));
.withDataProducts(getDataProducts(printer, csvRecord, 12))
.withCreatedTime(
nullOrEmpty(csvRecord.get(15)) ? null : Long.parseLong(csvRecord.get(15)))
.withModifiedTime(
nullOrEmpty(csvRecord.get(16)) ? null : Long.parseLong(csvRecord.get(16)));
if (processRecord) {
createEntity(printer, csvRecord, newSpreadsheet, SPREADSHEET);
@ -353,7 +359,11 @@ public class SpreadsheetRepository extends EntityRepository<Spreadsheet> {
addField(recordList, entity.getDisplayName());
addField(recordList, entity.getDescription());
addField(recordList, entity.getDirectory().getFullyQualifiedName());
addField(recordList, entity.getMimeType().toString());
addField(recordList, entity.getMimeType() != null ? entity.getMimeType().toString() : "");
addField(
recordList, entity.getCreatedTime() != null ? entity.getCreatedTime().toString() : "");
addField(
recordList, entity.getModifiedTime() != null ? entity.getModifiedTime().toString() : "");
addField(recordList, entity.getPath());
addField(recordList, entity.getSize() != null ? entity.getSize().toString() : "");
addField(recordList, entity.getFileVersion());
@ -415,6 +425,8 @@ public class SpreadsheetRepository extends EntityRepository<Spreadsheet> {
@Override
public void entitySpecificUpdate(boolean consolidatingChanges) {
recordChange("mimeType", original.getMimeType(), updated.getMimeType());
recordChange("createdTime", original.getCreatedTime(), updated.getCreatedTime());
recordChange("modifiedTime", original.getModifiedTime(), updated.getModifiedTime());
recordChange("path", original.getPath(), updated.getPath());
recordChange("driveFileId", original.getDriveFileId(), updated.getDriveFileId());
recordChange("size", original.getSize(), updated.getSize());

View File

@ -18,6 +18,7 @@ public class DirectoryMapper implements EntityMapper<Directory, CreateDirectory>
: null)
.withPath(create.getPath())
.withIsShared(create.getIsShared())
.withSourceUrl(create.getSourceUrl());
.withSourceUrl(create.getSourceUrl())
.withDirectoryType(create.getDirectoryType());
}
}

View File

@ -76,7 +76,7 @@ import org.openmetadata.service.security.Authorizer;
public class DirectoryResource extends EntityResource<Directory, DirectoryRepository> {
public static final String COLLECTION_PATH = "v1/drives/directories/";
static final String FIELDS =
"owners,children,parent,usageSummary,tags,extension,domains,sourceHash,lifeCycle,votes,followers";
"owners,children,parent,usageSummary,tags,extension,domains,sourceHash,lifeCycle,votes,followers,numberOfFiles,numberOfSubDirectories,totalSize,directoryType";
private final DirectoryMapper mapper = new DirectoryMapper();
@Override

View File

@ -18,7 +18,9 @@ public class SpreadsheetMapper implements EntityMapper<Spreadsheet, CreateSpread
.withDriveFileId(create.getDriveFileId())
.withSize(create.getSize())
.withFileVersion(create.getFileVersion())
.withSourceUrl(create.getSourceUrl());
.withSourceUrl(create.getSourceUrl())
.withCreatedTime(create.getCreatedTime())
.withModifiedTime(create.getModifiedTime());
// Set directory from parent if provided
if (create.getParent() != null) {

View File

@ -74,7 +74,7 @@ import org.openmetadata.service.security.Authorizer;
public class SpreadsheetResource extends EntityResource<Spreadsheet, SpreadsheetRepository> {
public static final String COLLECTION_PATH = "v1/drives/spreadsheets/";
static final String FIELDS =
"owners,directory,worksheets,usageSummary,tags,extension,domains,sourceHash,lifeCycle,votes,followers";
"owners,directory,worksheets,usageSummary,tags,extension,domains,sourceHash,lifeCycle,votes,followers,mimeType,createdTime,modifiedTime";
private final SpreadsheetMapper mapper = new SpreadsheetMapper();
@Override

View File

@ -73,7 +73,7 @@ import org.openmetadata.service.security.Authorizer;
public class WorksheetResource extends EntityResource<Worksheet, WorksheetRepository> {
public static final String COLLECTION_PATH = "v1/drives/worksheets/";
static final String FIELDS =
"owners,spreadsheet,columns,sampleData,usageSummary,tags,extension,domains,sourceHash,lifeCycle,votes,followers";
"owners,spreadsheet,columns,sampleData,usageSummary,tags,extension,domains,sourceHash,lifeCycle,votes,followers,rowCount";
private final WorksheetMapper mapper = new WorksheetMapper();
@Override

View File

@ -0,0 +1,15 @@
package org.openmetadata.service.resources.services.drive;
import org.openmetadata.schema.api.services.CreateDriveService;
import org.openmetadata.schema.entity.services.DriveService;
import org.openmetadata.service.mapper.EntityMapper;
public class DriveServiceMapper implements EntityMapper<DriveService, CreateDriveService> {
@Override
public DriveService createToEntity(CreateDriveService create, String user) {
return copy(new DriveService(), create, user)
.withServiceType(create.getServiceType())
.withConnection(create.getConnection())
.withIngestionRunner(create.getIngestionRunner());
}
}

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
package org.openmetadata.service.resources.services;
package org.openmetadata.service.resources.services.drive;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.Operation;
@ -64,6 +64,7 @@ import org.openmetadata.service.Entity;
import org.openmetadata.service.jdbi3.DriveServiceRepository;
import org.openmetadata.service.limits.Limits;
import org.openmetadata.service.resources.Collection;
import org.openmetadata.service.resources.services.ServiceEntityResource;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
@ -81,6 +82,7 @@ public class DriveServiceResource
extends ServiceEntityResource<DriveService, DriveServiceRepository, DriveConnection> {
public static final String COLLECTION_PATH = "v1/services/driveServices/";
public static final String FIELDS = "pipelines,owners,tags,domains,followers";
private final DriveServiceMapper driveServiceMapper = new DriveServiceMapper();
@Override
public DriveService addHref(UriInfo uriInfo, DriveService service) {
@ -660,10 +662,7 @@ public class DriveServiceResource
}
private DriveService getService(CreateDriveService create, String user) {
return repository
.copy(new DriveService(), create, user)
.withServiceType(create.getServiceType())
.withConnection(create.getConnection());
return driveServiceMapper.createToEntity(create, user);
}
@Override

View File

@ -46,6 +46,7 @@ import org.openmetadata.schema.services.connections.database.UnityCatalogConnect
import org.openmetadata.schema.services.connections.database.datalake.GCSConfig;
import org.openmetadata.schema.services.connections.database.deltalake.StorageConfig;
import org.openmetadata.schema.services.connections.database.iceberg.IcebergFileSystem;
import org.openmetadata.schema.services.connections.drive.GoogleDriveConnection;
import org.openmetadata.schema.services.connections.mlmodel.VertexAIConnection;
import org.openmetadata.schema.services.connections.pipeline.AirflowConnection;
import org.openmetadata.schema.services.connections.pipeline.MatillionConnection;
@ -78,6 +79,7 @@ public final class ClassConverterFactory {
Map.entry(GCSConfig.class, new GCPConfigClassConverter()),
Map.entry(GCPCredentials.class, new GcpCredentialsClassConverter()),
Map.entry(GCSConnection.class, new GcpConnectionClassConverter()),
Map.entry(GoogleDriveConnection.class, new GoogleDriveConnectionClassConverter()),
Map.entry(HiveConnection.class, new HiveConnectionClassConverter()),
Map.entry(IcebergConnection.class, new IcebergConnectionClassConverter()),
Map.entry(IcebergFileSystem.class, new IcebergFileSystemClassConverter()),

View File

@ -0,0 +1,38 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openmetadata.service.secrets.converter;
import java.util.List;
import org.openmetadata.schema.security.credentials.GCPCredentials;
import org.openmetadata.schema.services.connections.drive.GoogleDriveConnection;
import org.openmetadata.schema.utils.JsonUtils;
/** Converter class to get a `GoogleDriveConnection` object. */
public class GoogleDriveConnectionClassConverter extends ClassConverter {
public GoogleDriveConnectionClassConverter() {
super(GoogleDriveConnection.class);
}
@Override
public Object convert(Object object) {
GoogleDriveConnection googleDriveConnection =
(GoogleDriveConnection) JsonUtils.convertValue(object, this.clazz);
tryToConvertOrFail(googleDriveConnection.getCredentials(), List.of(GCPCredentials.class))
.ifPresent(obj -> googleDriveConnection.setCredentials((GCPCredentials) obj));
return googleDriveConnection;
}
}

View File

@ -55,6 +55,7 @@ import org.openmetadata.schema.type.FileType;
import org.openmetadata.schema.utils.JsonUtils;
import org.openmetadata.schema.utils.ResultList;
import org.openmetadata.service.Entity;
import org.openmetadata.service.resources.services.drive.DriveServiceResource;
import org.openmetadata.service.util.TestUtils;
@Slf4j

View File

@ -5,7 +5,9 @@
"description": "Create Spreadsheet entity request",
"type": "object",
"javaType": "org.openmetadata.schema.api.data.CreateSpreadsheet",
"javaInterfaces": ["org.openmetadata.schema.CreateEntity"],
"javaInterfaces": [
"org.openmetadata.schema.CreateEntity"
],
"properties": {
"name": {
"description": "Name that identifies this spreadsheet.",
@ -92,8 +94,19 @@
"extension": {
"description": "Entity extension data with custom attributes added to the entity.",
"$ref": "../../type/basic.json#/definitions/entityExtension"
},
"createdTime": {
"description": "Spreadsheet creation timestamp",
"$ref": "../../type/basic.json#/definitions/timestamp"
},
"modifiedTime": {
"description": "Last modification timestamp",
"$ref": "../../type/basic.json#/definitions/timestamp"
}
},
"required": ["name", "service"],
"required": [
"name",
"service"
],
"additionalProperties": false
}

View File

@ -5,7 +5,9 @@
"description": "Create Drive Service entity request",
"type": "object",
"javaType": "org.openmetadata.schema.api.services.CreateDriveService",
"javaInterfaces": ["org.openmetadata.schema.CreateEntity"],
"javaInterfaces": [
"org.openmetadata.schema.CreateEntity"
],
"properties": {
"name": {
"description": "Name that identifies this drive service.",
@ -53,8 +55,15 @@
"$ref": "../../type/basic.json#/definitions/fullyQualifiedEntityName"
},
"default": null
},
"ingestionRunner": {
"description": "The ingestion agent responsible for executing the ingestion pipeline.",
"$ref": "../../type/entityReference.json"
}
},
"required": ["name", "serviceType"],
"required": [
"name",
"serviceType"
],
"additionalProperties": false
}

View File

@ -14,6 +14,10 @@
* Create Spreadsheet entity request
*/
export interface CreateSpreadsheet {
/**
* Spreadsheet creation timestamp
*/
createdTime?: number;
/**
* List of fully qualified names of data products this entity is part of.
*/
@ -50,6 +54,10 @@ export interface CreateSpreadsheet {
* MIME type of the spreadsheet file
*/
mimeType?: SpreadsheetMIMEType;
/**
* Last modification timestamp
*/
modifiedTime?: number;
/**
* Name that identifies this spreadsheet.
*/

View File

@ -31,6 +31,10 @@ export interface CreateDriveService {
* Fully qualified names of the domains the Drive Service belongs to.
*/
domains?: string[];
/**
* The ingestion agent responsible for executing the ingestion pipeline.
*/
ingestionRunner?: EntityReference;
/**
* Name that identifies this drive service.
*/
@ -293,14 +297,16 @@ export enum DriveServiceType {
}
/**
* Owners of this Drive service.
* The ingestion agent responsible for executing the ingestion pipeline.
*
* This schema defines the EntityReferenceList type used for referencing an entity.
* This schema defines the EntityReference type used for referencing an entity.
* EntityReference is used for capturing relationships from one entity to another. For
* example, a table has an attribute called database of type EntityReference that captures
* the relationship of a table `belongs to a` database.
*
* This schema defines the EntityReference type used for referencing an entity.
* Owners of this Drive service.
*
* This schema defines the EntityReferenceList type used for referencing an entity.
* EntityReference is used for capturing relationships from one entity to another. For
* example, a table has an attribute called database of type EntityReference that captures
* the relationship of a table `belongs to a` database.

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "subdomains",
"sub-domain-plural": "Subdomains",
"sub-team-plural": "Unter-Teams",
"subdirectory-plural": "Unterverzeichnisse",
"submit": "Einreichen",
"subscription": "Subscription",
"success": "Erfolg",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "sub domains",
"sub-domain-plural": "Sub Domains",
"sub-team-plural": "Sub Teams",
"subdirectory-plural": "Subdirectories",
"submit": "Submit",
"subscription": "Subscription",
"success": "Success",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "subdominios",
"sub-domain-plural": "Subdominios",
"sub-team-plural": "Subequipos",
"subdirectory-plural": "Subdirectorios",
"submit": "Enviar",
"subscription": "Subscripción",
"success": "Éxito",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "sous-domaines",
"sub-domain-plural": "Sous-Domaines",
"sub-team-plural": "Sous-Équipes",
"subdirectory-plural": "Sous-répertoires",
"submit": "Soumettre",
"subscription": "Subscription",
"success": "Succès",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "subdominios",
"sub-domain-plural": "Subdominios",
"sub-team-plural": "Subequipos",
"subdirectory-plural": "Subdirectorios",
"submit": "Enviar",
"subscription": "Subscrición",
"success": "Éxito",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "תתי-תחומים",
"sub-domain-plural": "תתי-תחומים",
"sub-team-plural": "תתי-צוותים",
"subdirectory-plural": "תתי-תיקיות",
"submit": "שלח",
"subscription": "מינוי",
"success": "הצלחה",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "サブドメイン",
"sub-domain-plural": "サブドメイン",
"sub-team-plural": "サブチーム",
"subdirectory-plural": "サブディレクトリ",
"submit": "送信",
"subscription": "サブスクリプション",
"success": "成功",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "하위 도메인들",
"sub-domain-plural": "하위 도메인들",
"sub-team-plural": "하위 팀들",
"subdirectory-plural": "하위 디렉토리",
"submit": "제출",
"subscription": "구독",
"success": "성공",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "उप डोमेन्स",
"sub-domain-plural": "उप डोमेन्स",
"sub-team-plural": "उप टीम्स",
"subdirectory-plural": "उपनिर्देशिका",
"submit": "प्रस्तुत करा",
"subscription": "सदस्यता",
"success": "यश",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "subdomeinen",
"sub-domain-plural": "Subdomeinen",
"sub-team-plural": "Subteams",
"subdirectory-plural": "Submappen",
"submit": "Indienen",
"subscription": "Abonnement",
"success": "Succes",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "زیر دامنه‌ها",
"sub-domain-plural": "زیر دامنه‌ها",
"sub-team-plural": "زیر تیم‌ها",
"subdirectory-plural": "زیرشاخه‌ها",
"submit": "ارسال",
"subscription": "اشتراک",
"success": "موفقیت",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "subdomínios",
"sub-domain-plural": "Subdomínios",
"sub-team-plural": "Subequipes",
"subdirectory-plural": "Subdiretórios",
"submit": "Enviar",
"subscription": "Assinatura",
"success": "Sucesso",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "subdomínios",
"sub-domain-plural": "Subdomínios",
"sub-team-plural": "Subequipas",
"subdirectory-plural": "Subdiretórios",
"submit": "Enviar",
"subscription": "Assinatura",
"success": "Sucesso",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "поддомены",
"sub-domain-plural": "Поддомены",
"sub-team-plural": "Подгруппы",
"subdirectory-plural": "Подкаталоги",
"submit": "Подтвердить",
"subscription": "Подписка",
"success": "Успешно",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "โดเมนย่อยหลายรายการ",
"sub-domain-plural": "โดเมนย่อยหลายรายการ",
"sub-team-plural": "ทีมย่อย",
"subdirectory-plural": "ไดเรกทอรีย่อยหลายรายการ",
"submit": "ส่ง",
"subscription": "การสมัครสมาชิก",
"success": "สำเร็จ",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "alt alan adları",
"sub-domain-plural": "Alt Alan Adları",
"sub-team-plural": "Alt Takımlar",
"subdirectory-plural": "Alt dizinler",
"submit": "Gönder",
"subscription": "Abonelik",
"success": "Başarılı",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "子域",
"sub-domain-plural": "子域",
"sub-team-plural": "子团队",
"subdirectory-plural": "子目录",
"submit": "提交",
"subscription": "订阅",
"success": "成功",

View File

@ -1608,6 +1608,7 @@
"sub-domain-lowercase-plural": "子領域",
"sub-domain-plural": "子領域",
"sub-team-plural": "子團隊",
"subdirectory-plural": "子目錄",
"submit": "提交",
"subscription": "訂閱",
"success": "成功",

View File

@ -11,8 +11,14 @@
* limitations under the License.
*/
import { render } from '@testing-library/react';
import { Tooltip, Typography } from 'antd';
import React from 'react';
import { EntityType } from '../enums/entity.enum';
import {
Spreadsheet,
SpreadsheetMIMEType,
} from '../generated/entity/data/spreadsheet';
import { mockContainerData } from '../mocks/ContainerVersion.mock';
import { MOCK_DASHBOARD_DATA_MODEL } from '../mocks/DashboardDataModel.mock';
import { mockDashboardData } from '../mocks/dashboardVersion.mock';
@ -35,13 +41,14 @@ import { mockStoredProcedureData } from '../mocks/StoredProcedure.mock';
import { MOCK_TABLE } from '../mocks/TableData.mock';
import { mockTopicData } from '../mocks/TopicVersion.mock';
import {
ExtraInfoLabel,
getDataAssetsHeaderInfo,
getEntityExtraInfoLength,
} from './DataAssetsHeader.utils';
// Mock only ExtraInfoLink, not ExtraInfoLabel as we want to test it
jest.mock('./DataAssetsHeader.utils', () => ({
...jest.requireActual('./DataAssetsHeader.utils'),
ExtraInfoLabel: jest.fn().mockImplementation(({ value }) => value),
ExtraInfoLink: jest.fn().mockImplementation(({ value }) => value),
}));
jest.mock('./EntityUtils', () => ({
@ -87,6 +94,17 @@ jest.mock('./TableUtils', () => ({
getUsagePercentile: jest.fn().mockReturnValue('getUsagePercentile'),
}));
jest.mock('./date-time/DateTimeUtils', () => ({
...jest.requireActual('./date-time/DateTimeUtils'),
formatDateTime: jest
.fn()
.mockImplementation((timestamp) => `formatted-${timestamp}`),
getEpochMillisForPastDays: jest
.fn()
.mockImplementation((days) => Date.now() - days * 24 * 60 * 60 * 1000),
getCurrentMillis: jest.fn().mockReturnValue(Date.now()),
}));
jest.mock('../constants/constants', () => ({
...jest.requireActual('../constants/constants'),
NO_DATA_PLACEHOLDER: jest.fn().mockReturnValue('---'),
@ -506,6 +524,76 @@ describe('Tests for DataAssetsHeaderUtils', () => {
);
});
// Test for Spreadsheet entity
it('Function getDataAssetsHeaderInfo should return data for Spreadsheet entity', () => {
const mockSpreadsheet: Spreadsheet = {
id: 'spreadsheet-123',
name: 'test-spreadsheet',
fullyQualifiedName: 'service.directory.test-spreadsheet',
mimeType:
'application/vnd.google-apps.spreadsheet' as SpreadsheetMIMEType,
createdTime: 1609459200000,
modifiedTime: 1640995200000,
service: {
id: 'service-123',
name: 'google-drive',
type: 'driveService',
},
};
const assetData = getDataAssetsHeaderInfo(
EntityType.SPREADSHEET,
mockSpreadsheet,
'test-spreadsheet',
[]
);
// contains all breadcrumbs
expect(assetData.breadcrumbs).toEqual([{ name: 'entityName', url: 'url' }]);
// contains extra data
expect(JSON.stringify(assetData.extraInfo)).toContain('label.mime-type');
expect(JSON.stringify(assetData.extraInfo)).toContain(
'application/vnd.google-apps.spreadsheet'
);
expect(JSON.stringify(assetData.extraInfo)).toContain('label.created-time');
expect(JSON.stringify(assetData.extraInfo)).toContain(
'formatted-1609459200000'
);
expect(JSON.stringify(assetData.extraInfo)).toContain(
'label.modified-time'
);
expect(JSON.stringify(assetData.extraInfo)).toContain(
'formatted-1640995200000'
);
// Test with missing optional data
const assetWithNoExtraData = getDataAssetsHeaderInfo(
EntityType.SPREADSHEET,
{
...mockSpreadsheet,
mimeType: undefined,
createdTime: undefined,
modifiedTime: undefined,
},
'test-spreadsheet',
[]
);
// Should not contain extra data when fields are undefined
expect(JSON.stringify(assetWithNoExtraData.extraInfo)).not.toContain(
'label.mime-type'
);
expect(JSON.stringify(assetWithNoExtraData.extraInfo)).not.toContain(
'label.created-time'
);
expect(JSON.stringify(assetWithNoExtraData.extraInfo)).not.toContain(
'label.modified-time'
);
});
// Test for Search entity
it('Function getDataAssetsHeaderInfo should return data for Search entity', () => {
// Search Service
@ -652,6 +740,30 @@ describe('Tests for DataAssetsHeaderUtils', () => {
});
});
describe('ExtraInfoLabel', () => {
it('should handle React node as value', () => {
const nodeValue = (
<Tooltip title="Full text value">
<Typography.Text ellipsis className="w-full">
Truncated text value
</Typography.Text>
</Tooltip>
);
const { container } = render(
<ExtraInfoLabel label="MIME Type" value={nodeValue} />
);
// Check that the component renders without error
expect(
container.querySelector('.extra-info-container')
).toBeInTheDocument();
expect(
container.querySelector('.extra-info-label-heading')
).toHaveTextContent('MIME Type');
});
});
describe('getEntityExtraInfoLength', () => {
it('should return 0 for non-React elements', () => {
expect(getEntityExtraInfoLength(null)).toBe(0);

View File

@ -63,6 +63,7 @@ import { PipelineService } from '../generated/entity/services/pipelineService';
import { SearchService } from '../generated/entity/services/searchService';
import { SecurityService } from '../generated/entity/services/securityService';
import { StorageService } from '../generated/entity/services/storageService';
import { formatDateTime } from './date-time/DateTimeUtils';
import {
getBreadcrumbForEntitiesWithServiceOnly,
getBreadcrumbForEntityWithParent,
@ -103,7 +104,7 @@ export const ExtraInfoLabel = ({
return (
<div className="d-flex align-start extra-info-container">
<Typography.Text
className="whitespace-nowrap text-sm d-flex flex-col gap-2"
className="whitespace-nowrap text-sm d-flex flex-col gap-2 w-full"
data-testid={dataTestId}>
{!isEmpty(label) && (
<span className="extra-info-label-heading">{label}</span>
@ -834,7 +835,13 @@ export const getDataAssetsHeaderInfo = (
/>
<ExtraInfoLabel
label={t('label.mime-type')}
value={spreadsheet.mimeType}
value={
<Tooltip title={spreadsheet.mimeType}>
<Typography.Text ellipsis className="w-full">
{spreadsheet.mimeType}
</Typography.Text>
</Tooltip>
}
/>
</>
)}
@ -846,7 +853,7 @@ export const getDataAssetsHeaderInfo = (
/>
<ExtraInfoLabel
label={t('label.created-time')}
value={spreadsheet.createdTime}
value={formatDateTime(spreadsheet.createdTime)}
/>
</>
)}
@ -858,7 +865,7 @@ export const getDataAssetsHeaderInfo = (
/>
<ExtraInfoLabel
label={t('label.modified-time')}
value={spreadsheet.modifiedTime}
value={formatDateTime(spreadsheet.modifiedTime)}
/>
</>
)}