diff --git a/package-lock.json b/package-lock.json index 94b01cf53..18bfa48f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr192.6406015", + "@iqss/dataverse-client-javascript": "2.0.0-pr199.da60ecd", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -3674,9 +3674,9 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-pr192.6406015", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr192.6406015/4d6562b1e287c872e92ef3a551ee8aa63c5262b5", - "integrity": "sha512-1DmypaaV1cXS4y+kbx33GLmRLwI/8Cwj82MLhpq9gdy7+racUOyzVs3MFKJmdLLWTy7pAWKtj6a+c8Pt6DmtzQ==", + "version": "2.0.0-pr199.da60ecd", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr199.da60ecd/baffd9c7b1a6f63a5b857f57d973fa9469713900", + "integrity": "sha512-eCxxMXhov5rWacjw0Cu1owFu+1FjvPZOIWhAN7q0ajiKh/OtkxWvgPMV2euNLdg3Pm2tg58merF5OCEaHyqGnw==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", diff --git a/package.json b/package.json index 0a0b77593..b234f0150 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr192.6406015", + "@iqss/dataverse-client-javascript": "2.0.0-pr199.da60ecd", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/src/api-token-info/domain/models/TokenInfo.ts b/src/api-token-info/domain/models/TokenInfo.ts new file mode 100644 index 000000000..761a983c3 --- /dev/null +++ b/src/api-token-info/domain/models/TokenInfo.ts @@ -0,0 +1,4 @@ +export interface TokenInfo { + apiToken: string + expirationDate: string +} diff --git a/src/api-token-info/domain/repositories/ApiTokenInfoRepository.tsx b/src/api-token-info/domain/repositories/ApiTokenInfoRepository.tsx new file mode 100644 index 000000000..eb825de61 --- /dev/null +++ b/src/api-token-info/domain/repositories/ApiTokenInfoRepository.tsx @@ -0,0 +1,7 @@ +import { TokenInfo } from '../models/TokenInfo' + +export interface ApiTokenInfoRepository { + getCurrentApiToken(): Promise + recreateApiToken(): Promise + deleteApiToken(): Promise +} diff --git a/src/api-token-info/domain/useCases/readCurrentApiToken.tsx b/src/api-token-info/domain/useCases/readCurrentApiToken.tsx new file mode 100644 index 000000000..43a39d1a4 --- /dev/null +++ b/src/api-token-info/domain/useCases/readCurrentApiToken.tsx @@ -0,0 +1,8 @@ +import { TokenInfo } from '../models/TokenInfo' +import { ApiTokenInfoRepository } from '../repositories/ApiTokenInfoRepository' + +export function readCurrentApiToken( + apiTokenRepository: ApiTokenInfoRepository +): Promise { + return apiTokenRepository.getCurrentApiToken() +} diff --git a/src/api-token-info/infrastructure/ApiTokenInfoJSDataverseRepository.tsx b/src/api-token-info/infrastructure/ApiTokenInfoJSDataverseRepository.tsx new file mode 100644 index 000000000..2b397dcec --- /dev/null +++ b/src/api-token-info/infrastructure/ApiTokenInfoJSDataverseRepository.tsx @@ -0,0 +1,56 @@ +// import { ReadError } from '@iqss/dataverse-client-javascript' +// import { +// getCurrentApiToken, +// recreateCurrentApiToken, +// deleteCurrentApiToken, +// ApiTokenInfo +// } from '@iqss/dataverse-client-javascript' + +import { TokenInfo } from '../domain/models/TokenInfo' +import { ApiTokenInfoRepository } from '../domain/repositories/ApiTokenInfoRepository' + +export class ApiTokenInfoJSDataverseRepository implements ApiTokenInfoRepository { + getCurrentApiToken(): Promise { + return Promise.resolve({ + apiToken: '142354345435eefrr', + expirationDate: new Date().toISOString().substring(0, 10) + }) + } + recreateApiToken(): Promise { + return Promise.resolve({ + apiToken: 'apiToken', + expirationDate: new Date().toISOString().substring(0, 10) + }) + } + + deleteApiToken(): Promise { + return Promise.resolve() + } + // getCurrentApiToken(): Promise { + // return execute() + // .then((apiTokenInfo: TokenInfo) => { + // return { + // apiToken: apiTokenInfo.apiToken, + // expirationDate: apiTokenInfo.expirationDate + // } + // }) + // .catch((error: ReadError) => { + // throw new Error(error.message) + // }) + // } + // recreateApiToken(): Promise { + // return recreateCurrentApiToken + // .execute() + // .then((apiTokenInfo: ApiTokenInfo) => { + // return { + // apiToken: apiTokenInfo.apiToken, + // expirationDate: apiTokenInfo.expirationDate + // } + // }) + // .catch((error: ReadError) => { + // throw new Error(error.message) + // }) + // } + // deleteApiToken(): Promise { + // return deleteCurrentApiToken()} +} diff --git a/src/sections/account/api-token-section/ApiTokenSection.tsx b/src/sections/account/api-token-section/ApiTokenSection.tsx index 7ead9d935..91edc181e 100644 --- a/src/sections/account/api-token-section/ApiTokenSection.tsx +++ b/src/sections/account/api-token-section/ApiTokenSection.tsx @@ -2,13 +2,36 @@ import { Trans, useTranslation } from 'react-i18next' import { Button } from '@iqss/dataverse-design-system' import accountStyles from '../Account.module.scss' import styles from './ApiTokenSection.module.scss' - +import { useEffect, useMemo, useState } from 'react' +import { TokenInfo } from '@/api-token-info/domain/models/TokenInfo' +import { readCurrentApiToken } from '@/api-token-info/domain/useCases/readCurrentApiToken' +import { ApiTokenInfoJSDataverseRepository } from '@/api-token-info/infrastructure/ApiTokenInfoJSDataverseRepository' +import ApiTokenSectionSkeleton from './ApiTokenSectionSkeleton' export const ApiTokenSection = () => { const { t } = useTranslation('account', { keyPrefix: 'apiToken' }) // TODO: When we have the use cases we need to mock stub to unit test this with or without token - const apiToken = '999fff-666rrr-this-is-not-a-real-token-123456' - const expirationDate = '2025-09-04' + const [apiToken, setApiToken] = useState('') + const [expirationDate, setExpirationDate] = useState('') + const [loading, setLoading] = useState(true) + const repository = useMemo(() => new ApiTokenInfoJSDataverseRepository(), []) + + useEffect(() => { + setLoading(true) + readCurrentApiToken(repository) + .then((tokenInfo) => { + if (tokenInfo) { + setApiToken(tokenInfo.apiToken) + setExpirationDate(tokenInfo.expirationDate) + } + }) + .catch((error) => { + console.error('There was an error fetching current Api token:', error) + }) + .finally(() => { + setLoading(false) + }) + }, [repository]) const copyToClipboard = () => { navigator.clipboard.writeText(apiToken).catch( @@ -17,6 +40,28 @@ export const ApiTokenSection = () => { } ) } + if (loading) { + return ( + <> +

+ + ) + }} + /> +

+ + + ) + } return ( <> @@ -38,7 +83,10 @@ export const ApiTokenSection = () => { {apiToken ? ( <>

- {t('expirationDate')} + {t('expirationDate')}{' '} +

{apiToken} diff --git a/src/sections/account/api-token-section/ApiTokenSectionSkeleton.tsx b/src/sections/account/api-token-section/ApiTokenSectionSkeleton.tsx new file mode 100644 index 000000000..d0f51dfcc --- /dev/null +++ b/src/sections/account/api-token-section/ApiTokenSectionSkeleton.tsx @@ -0,0 +1,37 @@ +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' +import 'react-loading-skeleton/dist/skeleton.css' +import { Button } from '@iqss/dataverse-design-system' +import styles from './ApiTokenSection.module.scss' +import { useTranslation } from 'react-i18next' + +const ApiTokenSectionSkeleton = () => { + const { t } = useTranslation('account', { keyPrefix: 'apiToken' }) + + return ( + + <> +

+ {t('expirationDate')}{' '} + +

+
+ + + +
+
+ + + +
+ +
+ ) +} +export default ApiTokenSectionSkeleton diff --git a/tests/component/sections/account/ApiTokenSection.spec.tsx b/tests/component/sections/account/ApiTokenSection.spec.tsx index 26ff3220b..ab702e5af 100644 --- a/tests/component/sections/account/ApiTokenSection.spec.tsx +++ b/tests/component/sections/account/ApiTokenSection.spec.tsx @@ -1,4 +1,5 @@ import { ApiTokenSection } from '../../../../src/sections/account/api-token-section/ApiTokenSection' +import { ApiTokenInfoJSDataverseRepository } from '../../../../src/api-token-info/infrastructure/ApiTokenInfoJSDataverseRepository' describe('ApiTokenSection', () => { beforeEach(() => { @@ -21,4 +22,31 @@ describe('ApiTokenSection', () => { }) // TODO: When we get the api token from the use case, we could mock the response and test more things. + describe('when fetching the current API token', () => { + it('should fetch and display the current API token', () => { + const mockApiToken = 'mocked-api-token' + const mockExpirationDate = '2024-12-31' + + //TODO: we need change the fake call to the real one once we have the api working + cy.stub(ApiTokenInfoJSDataverseRepository.prototype, 'getCurrentApiToken').callsFake(() => + Promise.resolve({ + apiToken: mockApiToken, + expirationDate: mockExpirationDate + }) + ) + cy.mountAuthenticated() + + cy.get('[data-testid="api-token"]').should('contain.text', mockApiToken) + cy.get('[data-testid="expiration-date"]').should('contain.text', mockExpirationDate) + }) + it('should display skeleton when the API token is fetching', () => { + it('should display skeleton when the API token is fetching', () => { + cy.stub(ApiTokenInfoJSDataverseRepository.prototype, 'getCurrentApiToken').callsFake( + () => new Promise(() => {}) + ) + + cy.get('[data-testid="loadingSkeleton"]').should('exist') + }) + }) + }) }) diff --git a/tests/e2e-integration/integration/account/infrastructure/repositories/ApiTokenInfoJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/account/infrastructure/repositories/ApiTokenInfoJSDataverseRepository.spec.ts new file mode 100644 index 000000000..918eb538c --- /dev/null +++ b/tests/e2e-integration/integration/account/infrastructure/repositories/ApiTokenInfoJSDataverseRepository.spec.ts @@ -0,0 +1,24 @@ +import { ApiTokenInfoJSDataverseRepository } from '../../../../../../src/api-token-info/infrastructure/ApiTokenInfoJSDataverseRepository' +import { TokenInfo } from '../../../../../../src/api-token-info/domain/models/TokenInfo' +//TODO: we need to change the fake call to the real one once we have the api working +const apiTokenRepository = new ApiTokenInfoJSDataverseRepository() +const tokenInfoExpected: TokenInfo = { + apiToken: '142354345435eefrr', + expirationDate: '2024-10-16' +} + +describe('get api Token Repository', () => { + it('should return the current api token', async () => { + const tokenInfo = await apiTokenRepository.getCurrentApiToken() + expect(tokenInfo).to.deep.equal(tokenInfoExpected) + }) + + it('should return a new api token', async () => { + const tokenInfo = await apiTokenRepository.recreateApiToken() + expect(tokenInfo).to.deep.equal(tokenInfo) + }) + + it('should delete the current api token', async () => { + await apiTokenRepository.deleteApiToken() + }) +})