diff --git a/.prettierignore b/.prettierignore index e469e58a3..e8a82122f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,6 @@ build coverage node_modules dist + +/resources/schema.graphql +*.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 458027b65..62490136d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- Deleting and renaming a dataset on Settings tab +- Added icons for dataset's tabs + ## [0.8.0] - 2023-08-04 ### Added - Added pictures in the dropdown list for engines @@ -56,6 +61,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.4.0] - 2022-11-18 ### Added + - Keeping a CHANGELOG! - Major refactorings and productization of the web UI - diff --git a/resources/schema.graphql b/resources/schema.graphql index c494c5eef..bd0c1a68d 100644 --- a/resources/schema.graphql +++ b/resources/schema.graphql @@ -140,6 +140,7 @@ type DataQueries { queryDialect: QueryDialect! dataFormat: DataBatchFormat schemaFormat: DataSchemaFormat + skip: Int limit: Int ): DataQueryResult! """ @@ -220,6 +221,8 @@ type Dataset { lastUpdatedAt: DateTime! } +scalar DatasetAlias + type DatasetConnection { """ A shorthand for `edges { node { ... } }` @@ -251,11 +254,7 @@ type DatasetData { This is equivalent to the SQL query: `SELECT * FROM dataset ORDER BY event_time DESC LIMIT N` """ - tail( - limit: Int - dataFormat: DataBatchFormat - schemaFormat: DataSchemaFormat - ): DataQueryResult! + tail(skip: Int, limit: Int, dataFormat: DataBatchFormat, schemaFormat: DataSchemaFormat): DataQueryResult! } type DatasetEdge { @@ -333,10 +332,20 @@ type DatasetMut { Access to the mutable metadata of the dataset """ metadata: DatasetMetadataMut! + """ + Rename the dataset + """ + rename(newName: DatasetName!): RenameResult! + """ + Delete the dataset + """ + delete: DeleteResult! } scalar DatasetName +scalar DatasetRef + scalar DatasetRefAny type Datasets { @@ -351,19 +360,11 @@ type Datasets { """ Returns datasets belonging to the specified account """ - byAccountId( - accountId: AccountID! - page: Int - perPage: Int - ): DatasetConnection! + byAccountId(accountId: AccountID!, page: Int, perPage: Int): DatasetConnection! """ Returns datasets belonging to the specified account """ - byAccountName( - accountName: AccountName! - page: Int - perPage: Int - ): DatasetConnection! + byAccountName(accountName: AccountName!, page: Int, perPage: Int): DatasetConnection! } type DatasetsMut { @@ -374,11 +375,7 @@ type DatasetsMut { """ Creates a new empty dataset """ - createEmpty( - accountId: AccountID! - datasetKind: DatasetKind! - datasetName: DatasetName! - ): CreateDatasetResult! + createEmpty(accountId: AccountID!, datasetKind: DatasetKind!, datasetName: DatasetName!): CreateDatasetResult! """ Creates a new dataset from provided DatasetSnapshot manifest """ @@ -396,6 +393,21 @@ The input/output is a string in RFC3339 format. """ scalar DateTime +interface DeleteResult { + message: String! +} + +type DeleteResultDanglingReference implements DeleteResult { + notDeletedDataset: DatasetAlias! + danglingChildRefs: [DatasetRef!]! + message: String! +} + +type DeleteResultSuccess implements DeleteResult { + deletedDataset: DatasetAlias! + message: String! +} + """ Describes """ @@ -475,10 +487,7 @@ type LoginResponse { accountInfo: AccountInfo! } -union MergeStrategy = - MergeStrategyAppend - | MergeStrategyLedger - | MergeStrategySnapshot +union MergeStrategy = MergeStrategyAppend | MergeStrategyLedger | MergeStrategySnapshot type MergeStrategyAppend { dummy: String @@ -550,10 +559,7 @@ type MetadataChainMut { """ Commits new event to the metadata chain """ - commitEvent( - event: String! - eventFormat: MetadataManifestFormat! - ): CommitResult! + commitEvent(event: String!, eventFormat: MetadataManifestFormat!): CommitResult! } union MetadataEvent = @@ -697,12 +703,7 @@ enum QueryDialect { SQL_DATA_FUSION } -union ReadStep = - ReadStepCsv - | ReadStepJsonLines - | ReadStepGeoJson - | ReadStepEsriShapefile - | ReadStepParquet +union ReadStep = ReadStepCsv | ReadStepJsonLines | ReadStepGeoJson | ReadStepEsriShapefile | ReadStepParquet type ReadStepCsv { schema: [String!] @@ -748,6 +749,26 @@ type ReadStepParquet { schema: [String!] } +interface RenameResult { + message: String! +} + +type RenameResultNameCollision implements RenameResult { + collidingAlias: DatasetAlias! + message: String! +} + +type RenameResultNoChanges implements RenameResult { + preservedName: DatasetName! + message: String! +} + +type RenameResultSuccess implements RenameResult { + oldName: DatasetName! + newName: DatasetName! + message: String! +} + type RequestHeader { name: String! value: String! @@ -951,11 +972,7 @@ type Tasks { Returns states of tasks associated with a given dataset ordered by creation time from newest to oldest """ - listTasksByDataset( - datasetId: DatasetID! - page: Int - perPage: Int - ): TaskConnection! + listTasksByDataset(datasetId: DatasetID!, page: Int, perPage: Int): TaskConnection! } type TasksMut { @@ -972,11 +989,7 @@ type TasksMut { Schedules a task to update the specified dataset by performing polling ingest or a derivative transformation """ - createProbeTask( - datasetId: DatasetID - busyTimeMs: Int - endWithOutcome: TaskOutcome - ): Task! + createProbeTask(datasetId: DatasetID, busyTimeMs: Int, endWithOutcome: TaskOutcome): Task! } type TemporalTable { diff --git a/src/app/api/dataset.api.ts b/src/app/api/dataset.api.ts index d72a8b9ae..b8850bc3e 100644 --- a/src/app/api/dataset.api.ts +++ b/src/app/api/dataset.api.ts @@ -7,8 +7,12 @@ import { DatasetByAccountAndDatasetNameGQL, DatasetByAccountAndDatasetNameQuery, DatasetKind, + DeleteDatasetGQL, + DeleteDatasetMutation, GetDatasetSchemaGQL, GetDatasetSchemaQuery, + RenameDatasetGQL, + RenameDatasetMutation, UpdateReadmeGQL, UpdateReadmeMutation, } from "src/app/api/kamu.graphql.interface"; @@ -51,6 +55,8 @@ export class DatasetApi { private commitEventToDatasetGQL: CommitEventToDatasetGQL, private datasetSchemaGQL: GetDatasetSchemaGQL, private updateReadmeGQL: UpdateReadmeGQL, + private deleteDatasetGQL: DeleteDatasetGQL, + private renameDatasetGQL: RenameDatasetGQL, ) {} public getDatasetMainData(params: { @@ -238,4 +244,31 @@ export class DatasetApi { }), ); } + + public deleteDataset(datasetId: string): Observable { + return this.deleteDatasetGQL + .mutate({ + datasetId, + }) + .pipe( + first(), + map((result: MutationResult) => { + return result.data; + }), + ); + } + + public renameDataset(datasetId: string, newName: string): Observable { + return this.renameDatasetGQL + .mutate({ + datasetId, + newName, + }) + .pipe( + first(), + map((result: MutationResult) => { + return result.data; + }), + ); + } } diff --git a/src/app/api/gql/delete-dataset/delete-dataset.graphql b/src/app/api/gql/delete-dataset/delete-dataset.graphql new file mode 100644 index 000000000..8acc4afce --- /dev/null +++ b/src/app/api/gql/delete-dataset/delete-dataset.graphql @@ -0,0 +1,17 @@ +mutation deleteDataset($datasetId: DatasetID!) { + datasets { + byId(datasetId: $datasetId) { + delete { + ... on DeleteResultDanglingReference { + message + danglingChildRefs + notDeletedDataset + } + ... on DeleteResultSuccess { + message + deletedDataset + } + } + } + } +} diff --git a/src/app/api/gql/rename-dataset/rename-dataset.graphql b/src/app/api/gql/rename-dataset/rename-dataset.graphql new file mode 100644 index 000000000..9dce5caec --- /dev/null +++ b/src/app/api/gql/rename-dataset/rename-dataset.graphql @@ -0,0 +1,22 @@ +mutation renameDataset($datasetId: DatasetID!, $newName: DatasetName!) { + datasets { + byId(datasetId: $datasetId) { + rename(newName: $newName) { + __typename + ... on RenameResultSuccess { + message + oldName + newName + } + ... on RenameResultNoChanges { + preservedName + message + } + ... on RenameResultNameCollision { + message + collidingAlias + } + } + } + } +} diff --git a/src/app/api/kamu.graphql.interface.ts b/src/app/api/kamu.graphql.interface.ts index 8332e7de4..7a2a72f13 100644 --- a/src/app/api/kamu.graphql.interface.ts +++ b/src/app/api/kamu.graphql.interface.ts @@ -4,15 +4,9 @@ import { Injectable } from "@angular/core"; import * as Apollo from "apollo-angular"; export type Maybe = T | null; export type InputMaybe = Maybe; -export type Exact = { - [K in keyof T]: T[K]; -}; -export type MakeOptional = Omit & { - [SubKey in K]?: Maybe; -}; -export type MakeMaybe = Omit & { - [SubKey in K]: Maybe; -}; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: string; @@ -22,8 +16,10 @@ export type Scalars = { Float: number; AccountID: any; AccountName: any; + DatasetAlias: any; DatasetID: any; DatasetName: any; + DatasetRef: any; DatasetRefAny: any; /** * Implement the DateTime scalar @@ -211,6 +207,7 @@ export type DataQueriesQueryArgs = { query: Scalars["String"]; queryDialect: QueryDialect; schemaFormat?: InputMaybe; + skip?: InputMaybe; }; export type DataQueryResult = DataQueryResultError | DataQueryResultSuccess; @@ -308,6 +305,7 @@ export type DatasetDataTailArgs = { dataFormat?: InputMaybe; limit?: InputMaybe; schemaFormat?: InputMaybe; + skip?: InputMaybe; }; export type DatasetEdge = { @@ -367,8 +365,16 @@ export type DatasetMetadataMutUpdateReadmeArgs = { export type DatasetMut = { __typename?: "DatasetMut"; + /** Delete the dataset */ + delete: DeleteResult; /** Access to the mutable metadata of the dataset */ metadata: DatasetMetadataMut; + /** Rename the dataset */ + rename: RenameResult; +}; + +export type DatasetMutRenameArgs = { + newName: Scalars["DatasetName"]; }; export type Datasets = { @@ -430,6 +436,23 @@ export type DatasetsMutCreateFromSnapshotArgs = { snapshotFormat: MetadataManifestFormat; }; +export type DeleteResult = { + message: Scalars["String"]; +}; + +export type DeleteResultDanglingReference = DeleteResult & { + __typename?: "DeleteResultDanglingReference"; + danglingChildRefs: Array; + message: Scalars["String"]; + notDeletedDataset: Scalars["DatasetAlias"]; +}; + +export type DeleteResultSuccess = DeleteResult & { + __typename?: "DeleteResultSuccess"; + deletedDataset: Scalars["DatasetAlias"]; + message: Scalars["String"]; +}; + /** Describes */ export type EngineDesc = { __typename?: "EngineDesc"; @@ -790,6 +813,29 @@ export type ReadStepParquet = { schema?: Maybe>; }; +export type RenameResult = { + message: Scalars["String"]; +}; + +export type RenameResultNameCollision = RenameResult & { + __typename?: "RenameResultNameCollision"; + collidingAlias: Scalars["DatasetAlias"]; + message: Scalars["String"]; +}; + +export type RenameResultNoChanges = RenameResult & { + __typename?: "RenameResultNoChanges"; + message: Scalars["String"]; + preservedName: Scalars["DatasetName"]; +}; + +export type RenameResultSuccess = RenameResult & { + __typename?: "RenameResultSuccess"; + message: Scalars["String"]; + newName: Scalars["DatasetName"]; + oldName: Scalars["DatasetName"]; +}; + export type RequestHeader = { __typename?: "RequestHeader"; name: Scalars["String"]; @@ -1065,20 +1111,9 @@ export type CommitEventToDatasetMutation = { chain: { __typename?: "MetadataChainMut"; commitEvent: - | { - __typename: "CommitResultAppendError"; - message: string; - } - | { - __typename: "CommitResultSuccess"; - message: string; - oldHead?: any | null; - newHead: any; - } - | { - __typename: "MetadataManifestMalformed"; - message: string; - } + | { __typename: "CommitResultAppendError"; message: string } + | { __typename: "CommitResultSuccess"; message: string; oldHead?: any | null; newHead: any } + | { __typename: "MetadataManifestMalformed"; message: string } | { __typename: "MetadataManifestUnsupportedVersion" } | { __typename: "NoChanges" }; }; @@ -1098,10 +1133,7 @@ export type CreateEmptyDatasetMutation = { datasets: { __typename?: "DatasetsMut"; createEmpty: - | { - __typename?: "CreateDatasetResultNameCollision"; - message: string; - } + | { __typename?: "CreateDatasetResultNameCollision"; message: string } | { __typename?: "CreateDatasetResultSuccess"; message: string }; }; }; @@ -1116,28 +1148,16 @@ export type CreateDatasetFromSnapshotMutation = { datasets: { __typename?: "DatasetsMut"; createFromSnapshot: - | { - __typename?: "CreateDatasetResultInvalidSnapshot"; - message: string; - } - | { - __typename?: "CreateDatasetResultMissingInputs"; - message: string; - } - | { - __typename?: "CreateDatasetResultNameCollision"; - message: string; - } + | { __typename?: "CreateDatasetResultInvalidSnapshot"; message: string } + | { __typename?: "CreateDatasetResultMissingInputs"; message: string } + | { __typename?: "CreateDatasetResultNameCollision"; message: string } | { __typename?: "CreateDatasetResultSuccess"; message: string; dataset: { __typename?: "Dataset" } & DatasetBasicsFragment; } | { __typename?: "MetadataManifestMalformed"; message: string } - | { - __typename?: "MetadataManifestUnsupportedVersion"; - message: string; - }; + | { __typename?: "MetadataManifestUnsupportedVersion"; message: string }; }; }; @@ -1156,11 +1176,7 @@ export type UpdateReadmeMutation = { __typename?: "DatasetMetadataMut"; updateReadme: | { __typename: "CommitResultAppendError"; message: string } - | { - __typename: "CommitResultSuccess"; - oldHead?: any | null; - message: string; - } + | { __typename: "CommitResultSuccess"; oldHead?: any | null; message: string } | { __typename: "NoChanges"; message: string }; }; } | null; @@ -1174,10 +1190,7 @@ export type DatasetByAccountAndDatasetNameQueryVariables = Exact<{ export type DatasetByAccountAndDatasetNameQuery = { __typename?: "Query"; - datasets: { - __typename?: "Datasets"; - byOwnerAndName?: ({ __typename?: "Dataset" } & DatasetBasicsFragment) | null; - }; + datasets: { __typename?: "Datasets"; byOwnerAndName?: ({ __typename?: "Dataset" } & DatasetBasicsFragment) | null }; }; export type DatasetByIdQueryVariables = Exact<{ @@ -1186,10 +1199,7 @@ export type DatasetByIdQueryVariables = Exact<{ export type DatasetByIdQuery = { __typename?: "Query"; - datasets: { - __typename?: "Datasets"; - byId?: ({ __typename?: "Dataset" } & DatasetBasicsFragment) | null; - }; + datasets: { __typename?: "Datasets"; byId?: ({ __typename?: "Dataset" } & DatasetBasicsFragment) | null }; }; export type GetDatasetDataSqlRunQueryVariables = Exact<{ @@ -1202,15 +1212,8 @@ export type GetDatasetDataSqlRunQuery = { data: { __typename?: "DataQueries"; query: - | { - __typename: "DataQueryResultError"; - errorMessage: string; - errorKind: DataQueryResultErrorKind; - } - | ({ - __typename: "DataQueryResultSuccess"; - limit: number; - } & DataQueryResultSuccessViewFragment); + | { __typename: "DataQueryResultError"; errorMessage: string; errorKind: DataQueryResultErrorKind } + | ({ __typename: "DataQueryResultSuccess"; limit: number } & DataQueryResultSuccessViewFragment); }; }; @@ -1235,14 +1238,8 @@ export type GetDatasetHistoryQuery = { blocks: { __typename?: "MetadataBlockConnection"; totalCount: number; - nodes: Array< - { - __typename?: "MetadataBlockExtended"; - } & MetadataBlockFragment - >; - pageInfo: { - __typename?: "PageBasedInfo"; - } & DatasetPageInfoFragment; + nodes: Array<{ __typename?: "MetadataBlockExtended" } & MetadataBlockFragment>; + pageInfo: { __typename?: "PageBasedInfo" } & DatasetPageInfoFragment; }; }; }; @@ -1284,11 +1281,7 @@ export type GetDatasetSchemaQuery = { __typename?: "Dataset"; metadata: { __typename?: "DatasetMetadata"; - currentSchema?: { - __typename?: "DataSchema"; - format: DataSchemaFormat; - content: string; - } | null; + currentSchema?: { __typename?: "DataSchema"; format: DataSchemaFormat; content: string } | null; }; } & DatasetBasicsFragment) | null; @@ -1309,25 +1302,40 @@ export type DatasetsByAccountNameQuery = { __typename?: "DatasetConnection"; totalCount: number; nodes: Array<{ __typename: "Dataset" } & DatasetSearchOverviewFragment>; - pageInfo: { - __typename?: "PageBasedInfo"; - } & DatasetPageInfoFragment; + pageInfo: { __typename?: "PageBasedInfo" } & DatasetPageInfoFragment; }; }; }; +export type DeleteDatasetMutationVariables = Exact<{ + datasetId: Scalars["DatasetID"]; +}>; + +export type DeleteDatasetMutation = { + __typename?: "Mutation"; + datasets: { + __typename?: "DatasetsMut"; + byId?: { + __typename?: "DatasetMut"; + delete: + | { + __typename?: "DeleteResultDanglingReference"; + message: string; + danglingChildRefs: Array; + notDeletedDataset: any; + } + | { __typename?: "DeleteResultSuccess"; message: string; deletedDataset: any }; + } | null; + }; +}; + export type EnginesQueryVariables = Exact<{ [key: string]: never }>; export type EnginesQuery = { __typename?: "Query"; data: { __typename?: "DataQueries"; - knownEngines: Array<{ - __typename?: "EngineDesc"; - name: string; - dialect: QueryDialect; - latestImage: string; - }>; + knownEngines: Array<{ __typename?: "EngineDesc"; name: string; dialect: QueryDialect; latestImage: string }>; }; }; @@ -1342,11 +1350,7 @@ export type AddDataEventFragment = { size: number; interval: { __typename?: "OffsetInterval"; start: number; end: number }; } | null; - outputCheckpoint?: { - __typename?: "Checkpoint"; - physicalHash: any; - size: number; - } | null; + outputCheckpoint?: { __typename?: "Checkpoint"; physicalHash: any; size: number } | null; }; export type ExecuteQueryEventFragment = { @@ -1362,39 +1366,19 @@ export type ExecuteQueryEventFragment = { inputSlices: Array<{ __typename?: "InputSlice"; datasetId: any; - blockInterval?: { - __typename?: "BlockInterval"; - start: any; - end: any; - } | null; - dataInterval?: { - __typename?: "OffsetInterval"; - start: number; - end: number; - } | null; + blockInterval?: { __typename?: "BlockInterval"; start: any; end: any } | null; + dataInterval?: { __typename?: "OffsetInterval"; start: number; end: number } | null; }>; - outputCheckpoint?: { - __typename?: "Checkpoint"; - physicalHash: any; - size: number; - } | null; + outputCheckpoint?: { __typename?: "Checkpoint"; physicalHash: any; size: number } | null; }; -export type SeedEventFragment = { - __typename?: "Seed"; - datasetId: any; - datasetKind: DatasetKind; -}; +export type SeedEventFragment = { __typename?: "Seed"; datasetId: any; datasetKind: DatasetKind }; export type SetAttachmentsEventFragment = { __typename?: "SetAttachments"; attachments: { __typename?: "AttachmentsEmbedded"; - items: Array<{ - __typename?: "AttachmentEmbedded"; - path: string; - content: string; - }>; + items: Array<{ __typename?: "AttachmentEmbedded"; path: string; content: string }>; }; }; @@ -1414,11 +1398,7 @@ export type SetPollingSourceEventFragment = { image: string; command?: Array | null; args?: Array | null; - env?: Array<{ - __typename?: "EnvVar"; - name: string; - value?: string | null; - }> | null; + env?: Array<{ __typename?: "EnvVar"; name: string; value?: string | null }> | null; } | { __typename?: "FetchStepFilesGlob"; @@ -1426,11 +1406,7 @@ export type SetPollingSourceEventFragment = { order?: SourceOrdering | null; eventTime?: | { __typename: "EventTimeSourceFromMetadata" } - | { - __typename?: "EventTimeSourceFromPath"; - pattern: string; - timestampFormat?: string | null; - } + | { __typename?: "EventTimeSourceFromPath"; pattern: string; timestampFormat?: string | null } | null; cache?: { __typename: "SourceCachingForever" } | null; } @@ -1439,17 +1415,9 @@ export type SetPollingSourceEventFragment = { url: string; eventTime?: | { __typename: "EventTimeSourceFromMetadata" } - | { - __typename?: "EventTimeSourceFromPath"; - pattern: string; - timestampFormat?: string | null; - } + | { __typename?: "EventTimeSourceFromPath"; pattern: string; timestampFormat?: string | null } | null; - headers?: Array<{ - __typename?: "RequestHeader"; - name: string; - value: string; - }> | null; + headers?: Array<{ __typename?: "RequestHeader"; name: string; value: string }> | null; cache?: { __typename: "SourceCachingForever" } | null; }; read: @@ -1475,11 +1443,7 @@ export type SetPollingSourceEventFragment = { timestampFormat?: string | null; multiLine?: boolean | null; } - | { - __typename?: "ReadStepEsriShapefile"; - schema?: Array | null; - subPath?: string | null; - } + | { __typename?: "ReadStepEsriShapefile"; schema?: Array | null; subPath?: string | null } | { __typename?: "ReadStepGeoJson"; schema?: Array | null } | { __typename?: "ReadStepJsonLines"; @@ -1504,27 +1468,15 @@ export type SetPollingSourceEventFragment = { obsvRemoved?: string | null; }; prepare?: Array< - | { - __typename?: "PrepStepDecompress"; - format: CompressionFormat; - subPath?: string | null; - } + | { __typename?: "PrepStepDecompress"; format: CompressionFormat; subPath?: string | null } | { __typename?: "PrepStepPipe"; command: Array } > | null; preprocess?: { __typename?: "TransformSql"; engine: string; version?: string | null; - queries: Array<{ - __typename?: "SqlQueryStep"; - query: string; - alias?: string | null; - }>; - temporalTables?: Array<{ - __typename?: "TemporalTable"; - name: string; - primaryKey: Array; - }> | null; + queries: Array<{ __typename?: "SqlQueryStep"; query: string; alias?: string | null }>; + temporalTables?: Array<{ __typename?: "TemporalTable"; name: string; primaryKey: Array }> | null; } | null; }; @@ -1535,10 +1487,7 @@ export type SetVocabEventFragment = { offsetColumn?: string | null; }; -export type SetWatermarkEventFragment = { - __typename?: "SetWatermark"; - outputWatermark: any; -}; +export type SetWatermarkEventFragment = { __typename?: "SetWatermark"; outputWatermark: any }; export type AccountDetailsFragment = { __typename?: "AccountInfo"; @@ -1551,16 +1500,8 @@ export type AccountDetailsFragment = { export type DataQueryResultSuccessViewFragment = { __typename?: "DataQueryResultSuccess"; - schema?: { - __typename?: "DataSchema"; - format: DataSchemaFormat; - content: string; - } | null; - data: { - __typename?: "DataBatch"; - format: DataBatchFormat; - content: string; - }; + schema?: { __typename?: "DataSchema"; format: DataSchemaFormat; content: string } | null; + data: { __typename?: "DataBatch"; format: DataBatchFormat; content: string }; }; export type DatasetBasicsFragment = { @@ -1577,25 +1518,15 @@ export type DatasetCurrentInfoFragment = { keywords?: Array | null; }; -export type DatasetDataSizeFragment = { - __typename?: "DatasetData"; - numRecordsTotal: number; - estimatedSize: number; -}; +export type DatasetDataSizeFragment = { __typename?: "DatasetData"; numRecordsTotal: number; estimatedSize: number }; export type DatasetDataFragment = { __typename?: "Dataset"; data: { __typename: "DatasetData"; tail: - | { - __typename: "DataQueryResultError"; - errorMessage: string; - errorKind: DataQueryResultErrorKind; - } - | ({ - __typename: "DataQueryResultSuccess"; - } & DataQueryResultSuccessViewFragment); + | { __typename: "DataQueryResultError"; errorMessage: string; errorKind: DataQueryResultErrorKind } + | ({ __typename: "DataQueryResultSuccess" } & DataQueryResultSuccessViewFragment); } & DatasetDataSizeFragment; }; @@ -1613,11 +1544,7 @@ export type DatasetDetailsFragment = { kind: DatasetKind; createdAt: any; lastUpdatedAt: any; - data: { - __typename?: "DatasetData"; - estimatedSize: number; - numRecordsTotal: number; - }; + data: { __typename?: "DatasetData"; estimatedSize: number; numRecordsTotal: number }; metadata: { __typename?: "DatasetMetadata"; currentWatermark?: any | null; @@ -1634,14 +1561,8 @@ export type DatasetLastUpdateFragment = { blocks: { __typename?: "MetadataBlockConnection"; totalCount: number; - nodes: Array< - { - __typename?: "MetadataBlockExtended"; - } & MetadataBlockFragment - >; - pageInfo: { - __typename?: "PageBasedInfo"; - } & DatasetPageInfoFragment; + nodes: Array<{ __typename?: "MetadataBlockExtended" } & MetadataBlockFragment>; + pageInfo: { __typename?: "PageBasedInfo" } & DatasetPageInfoFragment; }; }; }; @@ -1672,9 +1593,7 @@ export type DatasetLineageFragment = { metadata: { __typename?: "DatasetMetadata"; currentUpstreamDependencies: Array< - { - __typename?: "Dataset"; - } & DatasetBasicsFragment + { __typename?: "Dataset" } & DatasetBasicsFragment >; }; } & DatasetBasicsFragment @@ -1709,9 +1628,7 @@ export type DatasetLineageFragment = { metadata: { __typename?: "DatasetMetadata"; currentDownstreamDependencies: Array< - { - __typename?: "Dataset"; - } & DatasetBasicsFragment + { __typename?: "Dataset" } & DatasetBasicsFragment >; }; } & DatasetBasicsFragment @@ -1735,17 +1652,9 @@ export type DatasetMetadataSummaryFragment = { currentWatermark?: any | null; currentInfo: { __typename?: "SetInfo" } & DatasetCurrentInfoFragment; currentLicense?: ({ __typename?: "SetLicense" } & LicenseFragment) | null; - currentSource?: - | ({ - __typename?: "SetPollingSource"; - } & SetPollingSourceEventFragment) - | null; + currentSource?: ({ __typename?: "SetPollingSource" } & SetPollingSourceEventFragment) | null; currentTransform?: ({ __typename?: "SetTransform" } & DatasetTransformFragment) | null; - currentSchema?: { - __typename: "DataSchema"; - format: DataSchemaFormat; - content: string; - } | null; + currentSchema?: { __typename: "DataSchema"; format: DataSchemaFormat; content: string } | null; currentVocab?: ({ __typename?: "SetVocab" } & SetVocabEventFragment) | null; }; } & DatasetBasicsFragment & @@ -1785,11 +1694,7 @@ export type DatasetSearchOverviewFragment = { __typename?: "DatasetMetadata"; currentInfo: { __typename?: "SetInfo" } & DatasetCurrentInfoFragment; currentLicense?: ({ __typename?: "SetLicense" } & LicenseFragment) | null; - currentDownstreamDependencies: Array<{ - __typename?: "Dataset"; - id: any; - kind: DatasetKind; - }>; + currentDownstreamDependencies: Array<{ __typename?: "Dataset"; id: any; kind: DatasetKind }>; }; } & DatasetBasicsFragment; @@ -1797,16 +1702,8 @@ export type DatasetTransformContentFragment = { __typename?: "TransformSql"; engine: string; version?: string | null; - queries: Array<{ - __typename?: "SqlQueryStep"; - alias?: string | null; - query: string; - }>; - temporalTables?: Array<{ - __typename?: "TemporalTable"; - name: string; - primaryKey: Array; - }> | null; + queries: Array<{ __typename?: "SqlQueryStep"; alias?: string | null; query: string }>; + temporalTables?: Array<{ __typename?: "TemporalTable"; name: string; primaryKey: Array }> | null; }; export type DatasetTransformFragment = { @@ -1817,9 +1714,7 @@ export type DatasetTransformFragment = { datasetRef?: any | null; dataset: { __typename?: "Dataset" } & DatasetBasicsFragment; }>; - transform: { - __typename?: "TransformSql"; - } & DatasetTransformContentFragment; + transform: { __typename?: "TransformSql" } & DatasetTransformContentFragment; }; export type LicenseFragment = { @@ -1860,15 +1755,8 @@ export type GithubLoginMutation = { __typename?: "AuthMut"; githubLogin: { __typename?: "LoginResponse"; - token: { - __typename?: "AccessToken"; - accessToken: string; - scope: string; - tokenType: string; - }; - accountInfo: { - __typename?: "AccountInfo"; - } & AccountDetailsFragment; + token: { __typename?: "AccessToken"; accessToken: string; scope: string; tokenType: string }; + accountInfo: { __typename?: "AccountInfo" } & AccountDetailsFragment; }; }; }; @@ -1879,10 +1767,7 @@ export type FetchAccountInfoMutationVariables = Exact<{ export type FetchAccountInfoMutation = { __typename?: "Mutation"; - auth: { - __typename?: "AuthMut"; - accountInfo: { __typename?: "AccountInfo" } & AccountDetailsFragment; - }; + auth: { __typename?: "AuthMut"; accountInfo: { __typename?: "AccountInfo" } & AccountDetailsFragment }; }; export type GetMetadataBlockQueryVariables = Exact<{ @@ -1902,17 +1787,32 @@ export type GetMetadataBlockQuery = { chain: { __typename?: "MetadataChain"; blockByHashEncoded?: string | null; - blockByHash?: - | ({ - __typename?: "MetadataBlockExtended"; - } & MetadataBlockFragment) - | null; + blockByHash?: ({ __typename?: "MetadataBlockExtended" } & MetadataBlockFragment) | null; }; }; } | null; }; }; +export type RenameDatasetMutationVariables = Exact<{ + datasetId: Scalars["DatasetID"]; + newName: Scalars["DatasetName"]; +}>; + +export type RenameDatasetMutation = { + __typename?: "Mutation"; + datasets: { + __typename?: "DatasetsMut"; + byId?: { + __typename?: "DatasetMut"; + rename: + | { __typename: "RenameResultNameCollision"; message: string; collidingAlias: any } + | { __typename: "RenameResultNoChanges"; preservedName: any; message: string } + | { __typename: "RenameResultSuccess"; message: string; oldName: any; newName: any }; + } | null; + }; +}; + export type SearchDatasetsAutocompleteQueryVariables = Exact<{ query: Scalars["String"]; perPage?: InputMaybe; @@ -1944,9 +1844,7 @@ export type SearchDatasetsOverviewQuery = { __typename?: "SearchResultConnection"; totalCount: number; nodes: Array<{ __typename: "Dataset" } & DatasetSearchOverviewFragment>; - pageInfo: { - __typename?: "PageBasedInfo"; - } & DatasetPageInfoFragment; + pageInfo: { __typename?: "PageBasedInfo" } & DatasetPageInfoFragment; }; }; }; @@ -2832,6 +2730,36 @@ export class DatasetsByAccountNameGQL extends Apollo.Query< super(apollo); } } +export const DeleteDatasetDocument = gql` + mutation deleteDataset($datasetId: DatasetID!) { + datasets { + byId(datasetId: $datasetId) { + delete { + ... on DeleteResultDanglingReference { + message + danglingChildRefs + notDeletedDataset + } + ... on DeleteResultSuccess { + message + deletedDataset + } + } + } + } + } +`; + +@Injectable({ + providedIn: "root", +}) +export class DeleteDatasetGQL extends Apollo.Mutation { + document = DeleteDatasetDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } +} export const EnginesDocument = gql` query engines { data { @@ -2931,6 +2859,41 @@ export class GetMetadataBlockGQL extends Apollo.Query { + document = RenameDatasetDocument; + + constructor(apollo: Apollo.Apollo) { + super(apollo); + } +} export const SearchDatasetsAutocompleteDocument = gql` query searchDatasetsAutocomplete($query: String!, $perPage: Int, $page: Int) { search { diff --git a/src/app/common/base.processing.component.ts b/src/app/common/base.processing.component.ts index 15fab7310..047342d2a 100644 --- a/src/app/common/base.processing.component.ts +++ b/src/app/common/base.processing.component.ts @@ -54,6 +54,13 @@ export class BaseProcessingComponent extends BaseComponent { navigateToDiscussions: () => { console.log("Navigate to discussions"); }, + navigateToSettings: () => { + this.navigationService.navigateToDatasetView({ + accountName: datasetInfo.accountName, + datasetName: datasetInfo.datasetName, + tab: DatasetViewTypeEnum.Settings, + }); + }, }; } diff --git a/src/app/components/modal/modal-dialog.component.ts b/src/app/components/modal/modal-dialog.component.ts index 1d92207e7..cd6a78957 100755 --- a/src/app/components/modal/modal-dialog.component.ts +++ b/src/app/components/modal/modal-dialog.component.ts @@ -90,9 +90,9 @@ export class ModalDialogComponent extends DynamicComponent { } } - computeWidth(): number { + computeWidth(): string { const buttonsCount = this.getContextButtonsCount(); - return buttonsCount ? 100 / buttonsCount : 100; + return `${100 / buttonsCount}%`; } private getContextButtonsCount(): number { diff --git a/src/app/dataset-view/additional-components/data-tabs.mock.ts b/src/app/dataset-view/additional-components/data-tabs.mock.ts index 919f6bd5c..3b31fa0d8 100644 --- a/src/app/dataset-view/additional-components/data-tabs.mock.ts +++ b/src/app/dataset-view/additional-components/data-tabs.mock.ts @@ -1,6 +1,12 @@ import { mockDatasetBasicsFragment } from "./../../search/mock.data"; import { DatasetHistoryUpdate, DataSqlErrorUpdate, DataUpdate } from "../dataset.subscriptions.interface"; -import { CommitEventToDatasetMutation, DatasetKind, UpdateReadmeMutation } from "src/app/api/kamu.graphql.interface"; +import { + CommitEventToDatasetMutation, + DatasetKind, + DeleteDatasetMutation, + RenameDatasetMutation, + UpdateReadmeMutation, +} from "src/app/api/kamu.graphql.interface"; export const mockDataUpdate: DataUpdate = { schema: { @@ -846,3 +852,75 @@ export const mockUpdateReadmeMutation: UpdateReadmeMutation = { __typename: "DatasetsMut", }, }; + +export const mockSuccessDeleteDatasetMutation: DeleteDatasetMutation = { + datasets: { + byId: { + delete: { + message: "Success", + deletedDataset: "account.tokens.portfolio", + __typename: "DeleteResultSuccess", + }, + __typename: "DatasetMut", + }, + __typename: "DatasetsMut", + }, +}; + +export const mockErrorDeleteDatasetMutation: DeleteDatasetMutation = { + datasets: { + byId: { + delete: { + message: "Dataset 'account.tokens.transfers' has 1 dangling reference(s)", + danglingChildRefs: ["account.tokens.portfolio"], + notDeletedDataset: "account.tokens.transfers", + __typename: "DeleteResultDanglingReference", + }, + __typename: "DatasetMut", + }, + __typename: "DatasetsMut", + }, +}; + +export const mockSuccessRenameDatasetMutation: RenameDatasetMutation = { + datasets: { + byId: { + rename: { + __typename: "RenameResultSuccess", + message: "Success", + oldName: "test10", + newName: "test101", + }, + __typename: "DatasetMut", + }, + __typename: "DatasetsMut", + }, +}; + +export const mockRenameResultNameCollision: RenameDatasetMutation = { + datasets: { + byId: { + rename: { + __typename: "RenameResultNameCollision", + message: "Dataset 'account.portfolio' already exists", + collidingAlias: "account.portfolio", + }, + __typename: "DatasetMut", + }, + __typename: "DatasetsMut", + }, +}; + +export const mockRenameResultNoChanges: RenameDatasetMutation = { + datasets: { + byId: { + rename: { + __typename: "RenameResultNoChanges", + preservedName: "test11", + message: "No changes", + }, + __typename: "DatasetMut", + }, + __typename: "DatasetsMut", + }, +}; diff --git a/src/app/dataset-view/additional-components/settings-component/services/dataset-settings.service.spec.ts b/src/app/dataset-view/additional-components/settings-component/services/dataset-settings.service.spec.ts new file mode 100644 index 000000000..be282c6c8 --- /dev/null +++ b/src/app/dataset-view/additional-components/settings-component/services/dataset-settings.service.spec.ts @@ -0,0 +1,89 @@ +import { TestBed } from "@angular/core/testing"; +import { DatasetSettingsService } from "./dataset-settings.service"; +import { Apollo, ApolloModule } from "apollo-angular"; +import { ApolloTestingModule } from "apollo-angular/testing"; +import { DatasetApi } from "src/app/api/dataset.api"; +import { NavigationService } from "src/app/services/navigation.service"; +import { of } from "rxjs"; +import { + mockErrorDeleteDatasetMutation, + mockRenameResultNameCollision, + mockRenameResultNoChanges, + mockSuccessDeleteDatasetMutation, + mockSuccessRenameDatasetMutation, +} from "../../data-tabs.mock"; +import { ModalService } from "src/app/components/modal/modal.service"; + +describe("DatasetSettingsService", () => { + let service: DatasetSettingsService; + let datasetApi: DatasetApi; + let navigationService: NavigationService; + let modalService: ModalService; + const DATASET_ID = "mockDatasetId"; + const NEW_NAME = "mock-dataset-name"; + const ACCOUNT_NAME = "accountName"; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [Apollo], + imports: [ApolloModule, ApolloTestingModule], + }); + service = TestBed.inject(DatasetSettingsService); + navigationService = TestBed.inject(NavigationService); + datasetApi = TestBed.inject(DatasetApi); + modalService = TestBed.inject(ModalService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should check detete dataset with success", () => { + const deleteDatasetSpy = spyOn(datasetApi, "deleteDataset").and.returnValue( + of(mockSuccessDeleteDatasetMutation), + ); + const navigateToSearchSpy = spyOn(navigationService, "navigateToSearch"); + service.deleteDataset(DATASET_ID).subscribe(() => { + expect(deleteDatasetSpy).toHaveBeenCalledTimes(1); + expect(navigateToSearchSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("should check detete dataset with fail", () => { + const errorSpy = spyOn(modalService, "error").and.callThrough(); + const deleteDatasetSpy = spyOn(datasetApi, "deleteDataset").and.returnValue(of(mockErrorDeleteDatasetMutation)); + service.deleteDataset(DATASET_ID).subscribe(() => { + expect(deleteDatasetSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("should check rename dataset with success", () => { + const navigateToDatasetViewSpy = spyOn(navigationService, "navigateToDatasetView"); + const deleteDatasetSpy = spyOn(datasetApi, "renameDataset").and.returnValue( + of(mockSuccessRenameDatasetMutation), + ); + service.renameDataset(ACCOUNT_NAME, DATASET_ID, NEW_NAME).subscribe(() => { + expect(deleteDatasetSpy).toHaveBeenCalledTimes(1); + expect(navigateToDatasetViewSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("should check rename dataset with name collision", () => { + const errorRenameDatasetChangesSpy = spyOn(service, "errorRenameDatasetChanges").and.callThrough(); + const deleteDatasetSpy = spyOn(datasetApi, "renameDataset").and.returnValue(of(mockRenameResultNameCollision)); + service.renameDataset(ACCOUNT_NAME, DATASET_ID, NEW_NAME).subscribe(() => { + expect(deleteDatasetSpy).toHaveBeenCalledTimes(1); + expect(errorRenameDatasetChangesSpy).toHaveBeenCalledTimes(1); + }); + }); + + it("should check rename dataset with no changes", () => { + const errorRenameDatasetChangesSpy = spyOn(service, "errorRenameDatasetChanges").and.callThrough(); + const deleteDatasetSpy = spyOn(datasetApi, "renameDataset").and.returnValue(of(mockRenameResultNoChanges)); + service.renameDataset(ACCOUNT_NAME, DATASET_ID, NEW_NAME).subscribe(() => { + expect(deleteDatasetSpy).toHaveBeenCalledTimes(1); + expect(errorRenameDatasetChangesSpy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/app/dataset-view/additional-components/settings-component/services/dataset-settings.service.ts b/src/app/dataset-view/additional-components/settings-component/services/dataset-settings.service.ts new file mode 100644 index 000000000..2d3242731 --- /dev/null +++ b/src/app/dataset-view/additional-components/settings-component/services/dataset-settings.service.ts @@ -0,0 +1,93 @@ +import { Observable, Subject } from "rxjs"; +import { NavigationService } from "src/app/services/navigation.service"; +import { Injectable } from "@angular/core"; +import { DatasetApi } from "src/app/api/dataset.api"; +import { map } from "rxjs/operators"; +import { DeleteDatasetMutation, RenameDatasetMutation } from "src/app/api/kamu.graphql.interface"; +import { DatasetViewTypeEnum } from "src/app/dataset-view/dataset-view.interface"; +import { promiseWithCatch } from "src/app/common/app.helpers"; +import { ModalService } from "src/app/components/modal/modal.service"; +import { DatasetService } from "src/app/dataset-view/dataset.service"; + +@Injectable({ + providedIn: "root", +}) +export class DatasetSettingsService { + private errorRenameDatasetChanges$: Subject = new Subject(); + + public errorRenameDatasetChanges(message: string): void { + this.errorRenameDatasetChanges$.next(message); + } + + public get onErrorRenameDatasetChanges(): Observable { + return this.errorRenameDatasetChanges$.asObservable(); + } + + constructor( + private datasetApi: DatasetApi, + private navigationService: NavigationService, + private modalService: ModalService, + private datasetService: DatasetService, + ) {} + + public deleteDataset(datasetId: string): Observable { + return this.datasetApi.deleteDataset(datasetId).pipe( + map((data: DeleteDatasetMutation | undefined | null) => { + if (data?.datasets.byId?.delete.__typename === "DeleteResultSuccess") { + this.navigationService.navigateToSearch(); + } else { + if (data) { + promiseWithCatch( + this.modalService.error({ + title: "Can't delete", + message: data.datasets.byId?.delete.message, + yesButtonText: "Ok", + }), + ); + } + } + }), + ); + } + + public renameDataset(accountName: string, datasetId: string, newName: string): Observable { + return this.datasetApi.renameDataset(datasetId, newName).pipe( + map((data: RenameDatasetMutation | undefined | null) => { + if (data?.datasets.byId?.rename.__typename === "RenameResultSuccess") { + this.datasetService + .requestDatasetMainData({ + accountName, + datasetName: newName, + }) + .subscribe(); + this.navigationService.navigateToDatasetView({ + accountName, + datasetName: newName, + tab: DatasetViewTypeEnum.Overview, + }); + } else { + if (data) { + this.proccesRenameError(data); + } + } + }), + ); + } + + private proccesRenameError(data: RenameDatasetMutation): void { + if ( + data.datasets.byId?.rename.__typename === "RenameResultNameCollision" || + data.datasets.byId?.rename.__typename === "RenameResultNoChanges" + ) { + this.errorRenameDatasetChanges(data.datasets.byId.rename.message); + } else { + promiseWithCatch( + this.modalService.error({ + title: "Can't rename dataset", + message: data.datasets.byId?.rename.message, + yesButtonText: "Ok", + }), + ); + } + } +} diff --git a/src/app/dataset-view/additional-components/settings-component/settings.component.html b/src/app/dataset-view/additional-components/settings-component/settings.component.html new file mode 100644 index 000000000..f4dc2cb06 --- /dev/null +++ b/src/app/dataset-view/additional-components/settings-component/settings.component.html @@ -0,0 +1,85 @@ +
+
+
+
+ +
+
+
+
+

General

+ +
+
+
+ +
+
+ +
+
+ +
+ Name is required + Invalid dataset name +
+
+
{{ renameError }}
+
+
+
+

Danger Zone

+
+
+

Delete this dataset

+

Once you delete a dataset, there is no going back. Please be certain.

+
+ +
+
+
+
diff --git a/src/app/dataset-view/additional-components/settings-component/settings.component.sass b/src/app/dataset-view/additional-components/settings-component/settings.component.sass new file mode 100644 index 000000000..39b98494b --- /dev/null +++ b/src/app/dataset-view/additional-components/settings-component/settings.component.sass @@ -0,0 +1,139 @@ +@import src/assets/styles/var +p + margin: 0 +.custom-container + display: grid + grid-template-columns: 300px 1fr + +.content-container + padding: 15px 30px 0px + h2 + font-weight: 400 + margin: 0 + dl + margin: 0 + +.p-responsive + padding-left: 16px + a + text-decoration: none + .rename-btn + padding: 3px 12px + font-size: 14px + height: 32px + border: 1px solid rgba(27,31,36,0.25 ) + border-radius: 6px + &:hover + background: rgba(27,31,36,0.16 ) +.fw-bold-custom + font-weight: 600 +.action-list + padding: 0 + +.action-list-item + position: relative + list-style: none + border-radius: 6px + text-decoration: none + +.action-list-item a + &:hover + cursor: pointer + text-decoration: none + +.action-list-content + position: relative + display: grid + width: 100% + padding: 6px 8px + font-size: 14px + font-weight: 400 + color: $app-color-fg-default + text-align: left + user-select: none + background-color: transparent + border: none + border-radius: 6px + transition: background 33.333ms linear + touch-action: manipulation + grid-template-rows: min-content + grid-template-areas: "leadingAction leadingVisual label trailingVisual trailingAction" + grid-template-columns: min-content max-content + align-items: start + &:hover + background: $app-color-action-list-item-default-selected-bg + +.action-list-item--nav-active + background: $app-color-action-list-item-default-selected-bg + &::after + position: absolute + top: calc(50% - 12px) + left: -8px + width: 4px + height: 24px + content: "" + background: $app-color-accent-fg + border-radius: 6px + +.action-list-item--navActive a + font-weight: 600 + +.action-list-sectionDivider:empty + display: block + height: 1px + padding: 0 + margin: 7px -8px 8px + list-style: none + background: $app-color-action-list-item-inline-divider + border: 0 + +.action-list-sectionDivider:not(:empty) + display: flex + padding: 6px 8px + font-size: 12px + font-weight: 500 + color: $app-color-fg-muted + flex-direction: column + +.danger-zone__content + padding: 15px + border: 1px solid rgba(27,31,36,0.25 ) + border-radius: 6px + .description + font-weight: 600 + button + color: #CF222E + padding: 3px 12px + font-size: 14px + height: 32px + border: 1px solid rgba(27,31,36,0.25 ) + border-radius: 6px + &:hover + background: rgba(27,31,36,0.16 ) + +.error-block + z-index: 10 + display: inline-block + padding: 4px 8px + font-size: 12px + border-style: solid + border-width: 1px + border-radius: 6px + background: #ffebe9 + border-color: rgba(255, 129, 130, 0.4) + position: relative + &::before + content: "\A" + border-style: solid + border-width: 5px 7px 5px 5px + border-color: transparent rgba(255, 129, 130, 0.4) transparent transparent + position: absolute + left: 15px + top: -10px + transform: rotate(90deg) + +.error-color + color: #CF222E + +.error-border-color + border-color: #dc3545 diff --git a/src/app/dataset-view/additional-components/settings-component/settings.component.spec.ts b/src/app/dataset-view/additional-components/settings-component/settings.component.spec.ts new file mode 100644 index 000000000..ff2375d7c --- /dev/null +++ b/src/app/dataset-view/additional-components/settings-component/settings.component.spec.ts @@ -0,0 +1,83 @@ +import { SharedTestModule } from "./../../../common/shared-test.module"; +import { ComponentFixture, TestBed, fakeAsync, flush, tick } from "@angular/core/testing"; +import { SettingsTabComponent } from "./settings.component"; +import { ReactiveFormsModule } from "@angular/forms"; +import { ApolloTestingModule } from "apollo-angular/testing"; +import { Apollo, ApolloModule } from "apollo-angular"; +import { DatasetSettingsService } from "./services/dataset-settings.service"; +import { mockDatasetBasicsFragment } from "src/app/search/mock.data"; +import { emitClickOnElementByDataTestId, findInputElememtByDataTestId } from "src/app/common/base-test.helpers.spec"; +import { MatDividerModule } from "@angular/material/divider"; +import { MatIconModule } from "@angular/material/icon"; +import { AngularSvgIconModule } from "angular-svg-icon"; +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { of } from "rxjs"; +import { ModalService } from "src/app/components/modal/modal.service"; + +describe("SettingsTabComponent", () => { + let component: SettingsTabComponent; + let fixture: ComponentFixture; + let datasetSettingsService: DatasetSettingsService; + let modalService: ModalService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SettingsTabComponent], + providers: [Apollo], + imports: [ + ReactiveFormsModule, + AngularSvgIconModule.forRoot(), + HttpClientTestingModule, + MatDividerModule, + MatIconModule, + ApolloModule, + ApolloTestingModule, + SharedTestModule, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SettingsTabComponent); + component = fixture.componentInstance; + component.datasetBasics = mockDatasetBasicsFragment; + datasetSettingsService = TestBed.inject(DatasetSettingsService); + modalService = TestBed.inject(ModalService); + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should check init renameError", () => { + const errorMessage = "Dataset is already exist."; + expect(component.renameError).toBe(""); + datasetSettingsService.errorRenameDatasetChanges(errorMessage); + component.ngOnInit(); + expect(component.renameError).toEqual(errorMessage); + }); + + it("should check rename dataset", () => { + const renameDatasetSpy = spyOn(datasetSettingsService, "renameDataset").and.returnValue(of()); + emitClickOnElementByDataTestId(fixture, "rename-dataset-button"); + expect(renameDatasetSpy).toHaveBeenCalledTimes(1); + }); + + it("should check renameError is empty", fakeAsync(() => { + const errorMessage = "Dataset is already exist."; + datasetSettingsService.errorRenameDatasetChanges(errorMessage); + expect(component.renameError).toEqual(errorMessage); + const input = findInputElememtByDataTestId(fixture, "rename-dataset-input"); + input.value = "newDatasetName"; + input.dispatchEvent(new Event("keyup")); + fixture.detectChanges(); + tick(); + expect(component.renameError).toEqual(""); + flush(); + })); + + it("should check modal window is show", () => { + const modalServiceSpy = spyOn(modalService, "error").and.callThrough(); + emitClickOnElementByDataTestId(fixture, "delete-dataset-button"); + expect(modalServiceSpy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/app/dataset-view/additional-components/settings-component/settings.component.ts b/src/app/dataset-view/additional-components/settings-component/settings.component.ts new file mode 100644 index 000000000..81a166e22 --- /dev/null +++ b/src/app/dataset-view/additional-components/settings-component/settings.component.ts @@ -0,0 +1,78 @@ +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { DatasetSettingsService } from "./services/dataset-settings.service"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { DatasetBasicsFragment } from "src/app/api/kamu.graphql.interface"; +import { promiseWithCatch } from "src/app/common/app.helpers"; +import { BaseComponent } from "src/app/common/base.component"; +import { ModalService } from "src/app/components/modal/modal.service"; + +@Component({ + selector: "app-settings-tab", + templateUrl: "./settings.component.html", + styleUrls: ["./settings.component.sass"], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SettingsTabComponent extends BaseComponent implements OnInit { + @Input() public datasetBasics?: DatasetBasicsFragment; + public renameError = ""; + public renameDatasetForm: FormGroup; + + constructor( + private fb: FormBuilder, + private datasetSettingsService: DatasetSettingsService, + private modalService: ModalService, + private cdr: ChangeDetectorRef, + ) { + super(); + this.renameDatasetForm = this.fb.group({ + datasetName: [ + this.getDatasetInfoFromUrl().datasetName, + // eslint-disable-next-line @typescript-eslint/unbound-method + [Validators.required, Validators.pattern(/^([a-zA-Z0-9][a-zA-Z0-9-]*)+(\.[a-zA-Z0-9][a-zA-Z0-9-]*)*$/)], + ], + }); + } + ngOnInit(): void { + this.trackSubscription( + this.datasetSettingsService.onErrorRenameDatasetChanges.subscribe((error) => { + this.renameError = error; + this.cdr.detectChanges(); + }), + ); + } + + public get datasetName() { + return this.renameDatasetForm.get("datasetName"); + } + + public renameDataset(): void { + const datasetId = this.datasetBasics?.id as string; + const accountName = this.getDatasetInfoFromUrl().accountName; + this.trackSubscription( + this.datasetSettingsService + .renameDataset(accountName, datasetId, this.datasetName?.value as string) + .subscribe(), + ); + } + + public deleteDataset(): void { + promiseWithCatch( + this.modalService.error({ + title: "Delete", + message: "Do you want to delete a dataset?", + yesButtonText: "Ok", + noButtonText: "Cancel", + handler: (ok) => { + if (ok) { + const datasetId = this.datasetBasics?.id as string; + this.trackSubscription(this.datasetSettingsService.deleteDataset(datasetId).subscribe()); + } + }, + }), + ); + } + + public changeName(): void { + this.renameError = ""; + } +} diff --git a/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.spec.ts b/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.spec.ts index b9c04268b..2b72dc3c4 100644 --- a/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.spec.ts +++ b/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.spec.ts @@ -23,6 +23,7 @@ describe("DatasetViewMenuComponent", () => { navigateToHistory: () => null, navigateToLineage: () => null, navigateToDiscussions: () => null, + navigateToSettings: () => null, }; beforeEach(async () => { diff --git a/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.ts b/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.ts index 29d43d6c2..61ce497e4 100644 --- a/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.ts +++ b/src/app/dataset-view/dataset-view-menu/dataset-view-menu-component.ts @@ -87,6 +87,10 @@ export class DatasetViewMenuComponent implements OnInit { return this.datasetViewType === DatasetViewTypeEnum.Discussions; } + public get isDatasetViewTypeSettings(): boolean { + return this.datasetViewType === DatasetViewTypeEnum.Settings; + } + public onNavigateToOverview(): void { this.datasetNavigation.navigateToOverview(); } @@ -110,4 +114,8 @@ export class DatasetViewMenuComponent implements OnInit { public onNavigateToDiscussions(): void { this.datasetNavigation.navigateToDiscussions(); } + + public onNavigateToSettings(): void { + this.datasetNavigation.navigateToSettings(); + } } diff --git a/src/app/dataset-view/dataset-view-menu/dataset-view-menu.html b/src/app/dataset-view/dataset-view-menu/dataset-view-menu.html index 0179766d1..9c69a6758 100644 --- a/src/app/dataset-view/dataset-view-menu/dataset-view-menu.html +++ b/src/app/dataset-view/dataset-view-menu/dataset-view-menu.html @@ -7,41 +7,56 @@ " >
- + Overview + > + visibility + Overview Data + > + dataset + Data Metadata + > + dataset_linked + Metadata History + > + manage_history + History Lineage + > + view_module + Lineage Discussions + > + message + Discussions + + Settings
diff --git a/src/app/dataset-view/dataset-view.component.sass b/src/app/dataset-view/dataset-view.component.sass index e81af79d4..bfaa4700e 100644 --- a/src/app/dataset-view/dataset-view.component.sass +++ b/src/app/dataset-view/dataset-view.component.sass @@ -4,7 +4,7 @@ @import 'node_modules/prismjs/plugins/line-numbers/prism-line-numbers' @import 'node_modules/prismjs/plugins/line-highlight/prism-line-highlight' -mat-button-toggle-group +.mat-button-toggle-group width: fit-content & .mat-button-toggle-standalone.mat-button-toggle-appearance-standard, .mat-button-toggle-group-appearance-standard border-radius: 0 !important @@ -74,16 +74,15 @@ mat-button-toggle z-index: 3 !important outline: none !important -mat-button-toggle:hover - box-shadow: none !important - border-bottom: 1px solid !important - border-radius: 0 !important - &.mat-button-toggle-checked - border-bottom: 2px #000 solid !important - -mat-button-toggle ::ng-deep .mat-button-toggle-focus-overlay - background-color: transparent !important - opacity: 0 !important + &:hover + box-shadow: none !important + border-bottom: 1px solid !important + border-radius: 0 !important + &.mat-button-toggle-checked + border-bottom: 2px #000 solid !important + &:ng-deep mat-button-toggle .mat-button-toggle-focus-overlay + background-color: transparent !important + opacity: 0 !important .mat-button-toggle-checked border-left: none @@ -95,4 +94,10 @@ mat-button-toggle ::ng-deep .mat-button-toggle-focus-overlay .mat-button-toggle-input background-color: none !important - padding-left: 32px !important \ No newline at end of file + padding-left: 32px !important + +.tab-icon + position: absolute + left: -14px + top: 9px + font-size: 18px diff --git a/src/app/dataset-view/dataset-view.interface.ts b/src/app/dataset-view/dataset-view.interface.ts index 4d12f1be3..95587f81f 100644 --- a/src/app/dataset-view/dataset-view.interface.ts +++ b/src/app/dataset-view/dataset-view.interface.ts @@ -5,6 +5,7 @@ export enum DatasetViewTypeEnum { Lineage = "lineage", Discussions = "discussions", History = "history", + Settings = "settings", } export interface DatasetNavigationInterface { @@ -14,4 +15,5 @@ export interface DatasetNavigationInterface { navigateToHistory: (currentPage: number) => void; navigateToLineage: () => void; navigateToDiscussions: () => void; + navigateToSettings: () => void; } diff --git a/src/app/dataset-view/dataset.component.html b/src/app/dataset-view/dataset.component.html index 671562a5a..123b2d528 100644 --- a/src/app/dataset-view/dataset.component.html +++ b/src/app/dataset-view/dataset.component.html @@ -49,6 +49,9 @@ (clickDatasetEmit)="onClickMetadataNode($event)" > + + + diff --git a/src/app/dataset-view/dataset.component.ts b/src/app/dataset-view/dataset.component.ts index 6f4003fd0..a3f318fba 100644 --- a/src/app/dataset-view/dataset.component.ts +++ b/src/app/dataset-view/dataset.component.ts @@ -129,6 +129,10 @@ export class DatasetComponent extends BaseProcessingComponent implements OnInit, console.log("initDiscussionsTab"); } + public initSettingsTab(): void { + this.datasetViewType = DatasetViewTypeEnum.Settings; + } + public selectTopic(topicName: string): void { promiseWithCatch( this.modalService.warning({ @@ -171,6 +175,10 @@ export class DatasetComponent extends BaseProcessingComponent implements OnInit, return this.datasetViewType === DatasetViewTypeEnum.Discussions; } + public get isDatasetViewTypeSettings(): boolean { + return this.datasetViewType === DatasetViewTypeEnum.Settings; + } + private initDatasetViewByType(datasetInfo: DatasetInfo, currentPage: number): void { const mapperTabs: { [key in DatasetViewTypeEnum]: () => void } = { [DatasetViewTypeEnum.Overview]: () => this.initOverviewTab(), @@ -179,6 +187,7 @@ export class DatasetComponent extends BaseProcessingComponent implements OnInit, [DatasetViewTypeEnum.History]: () => this.initHistoryTab(datasetInfo, currentPage), [DatasetViewTypeEnum.Lineage]: () => this.initLineageTab(), [DatasetViewTypeEnum.Discussions]: () => this.initDiscussionsTab(), + [DatasetViewTypeEnum.Settings]: () => this.initSettingsTab(), }; this.datasetViewType = this.getDatasetViewTypeFromUrl(); diff --git a/src/app/dataset-view/dataset.module.ts b/src/app/dataset-view/dataset.module.ts index 6536f47a4..bd3e3fd74 100644 --- a/src/app/dataset-view/dataset.module.ts +++ b/src/app/dataset-view/dataset.module.ts @@ -59,6 +59,7 @@ import { AddPollingSourceComponent } from "./additional-components/metadata-comp import { MatStepperModule } from "@angular/material/stepper"; import { EngineSelectComponent } from "./additional-components/metadata-component/components/set-transform/components/engine-section/components/engine-select/engine-select.component"; import { ReadmeSectionComponent } from "./additional-components/overview-component/components/readme-section/readme-section.component"; +import { SettingsTabComponent } from "./additional-components/settings-component/settings.component"; @NgModule({ imports: [ CommonModule, @@ -145,6 +146,7 @@ import { ReadmeSectionComponent } from "./additional-components/overview-compone AddPollingSourceComponent, EngineSelectComponent, ReadmeSectionComponent, + SettingsTabComponent, ], }) export class DatasetModule {