Skip to content

Commit

Permalink
ADM-662: [frontend] add version in header (#824)
Browse files Browse the repository at this point in the history
  • Loading branch information
neomgb authored Dec 18, 2023
2 parents 8edc996 + ce88770 commit 3bf47ed
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 24 deletions.
6 changes: 5 additions & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ body:
id: version
attributes:
label: Version
description: What version of our software are you running? You can get the version by add "api/v1/version" after the host address you visit the heartbeat. If you can't get version by the url, you can input "1.0.0" as well
description: |
What version of our software are you running? There is two methods to get the current version:
1. you can see the version info at the top of the heartbeat pages
2. Add "api/v1/version" after the host address you visit the heartbeat
If you still could not get the version, you can input "1.1.0" as well
validations:
required: true

Expand Down
56 changes: 50 additions & 6 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ cd HearBeat/backend
```

## 2. How to run test and view test coverage
## 2. How to run unit test and view test coverage

```
cd HearBeat/frontend
Expand All @@ -23,7 +23,37 @@ cd HearBeat/frontend/coverage/lcov-report/index.html
open index.html
```

## 3. Code development specification
## 3. How to run e2e test

1. Start the mock server

```
cd HearBeat/stubs
docker-compose up -d
```

2. Start the backend service

```
cd HearBeat/backend
./gradlew bootRun --args='--spring.profiles.active=local --MOCK_SERVER_URL=http://localhost:4323'
```

3. Start the frontend service

```
cd HearBeat/frontend
pnpm start
```

4. Run the e2e tests

```
cd HearBeat/frontend
pnpm e2e / pnpm cypress open
```

## 4. Code development specification

1. Style naming starts with 'Styled' and distinguishes it from the parent component

Expand All @@ -33,11 +63,25 @@ export const StyledTypography = styled(Typography)({
})
```

2. css units should avoid using decimals eg:
2. Css units should use rem:

```
export const StyledTypography = styled(Typography)({
// fontSize: '0.88rem',
fontSize: '14px',
export const StyledTypography = styled('div')({
width: '10rem',
})
```

3. Write e2e tests using POM design pattern

```
page.cy.ts:
get headerVersion() {
return cy.get('span[title="Heartbeat"]').parent().next()
}
test.cy.ts:
homePage.headerVersion.should('exist')
```
41 changes: 41 additions & 0 deletions frontend/__tests__/src/client/HeaderClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { setupServer } from 'msw/node'
import { rest } from 'msw'
import { MOCK_VERSION_URL, VERIFY_ERROR_MESSAGE, VERSION_RESPONSE } from '../fixtures'
import { HttpStatusCode } from 'axios'
import { headerClient } from '@src/clients/header/HeaderClient'

const server = setupServer(rest.get(MOCK_VERSION_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok))))

describe('header client', () => {
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

it('should get response when get header status 200', async () => {
const excepted = '1.11'
server.use(
rest.get(MOCK_VERSION_URL, (req, res, ctx) =>
res(ctx.status(HttpStatusCode.Accepted), ctx.json(VERSION_RESPONSE))
)
)

await expect(headerClient.getVersion()).resolves.toEqual(excepted)
})

it('should throw error when get version response status 500', () => {
server.use(
rest.get(MOCK_VERSION_URL, (req, res, ctx) =>
res(
ctx.status(HttpStatusCode.InternalServerError),
ctx.json({
hintInfo: VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR,
})
)
)
)

expect(async () => {
await headerClient.getVersion()
}).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR)
})
})
2 changes: 2 additions & 0 deletions frontend/__tests__/src/components/ErrorContent/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BrowserRouter } from 'react-router-dom'
import userEvent from '@testing-library/user-event'
import { navigateMock } from '../../../setupTests'
import { ErrorContent } from '@src/components/ErrorContent'
import { headerClient } from '@src/clients/header/HeaderClient'

describe('error content', () => {
it('should show error message when render error page', () => {
Expand All @@ -20,6 +21,7 @@ describe('error content', () => {
})

it('should go to home page when click button', async () => {
headerClient.getVersion = jest.fn().mockResolvedValue('')
const { getByText } = render(
<BrowserRouter>
<ErrorPage />
Expand Down
5 changes: 5 additions & 0 deletions frontend/__tests__/src/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,13 @@ export const MOCK_BOARD_URL_FOR_CLASSIC_JIRA = `${BASE_URL}/boards/classic-jira`
export const MOCK_PIPELINE_URL = `${BASE_URL}/pipelines/buildkite`
export const MOCK_SOURCE_CONTROL_URL = `${BASE_URL}/source-control`
export const MOCK_REPORT_URL = `${BASE_URL}/reports`
export const MOCK_VERSION_URL = `${BASE_URL}/version`
export const MOCK_EXPORT_CSV_URL = `${BASE_URL}/reports/:dataType/:fileName`

export const VERSION_RESPONSE = {
version: '1.11',
}

export enum VERIFY_ERROR_MESSAGE {
BAD_REQUEST = 'Please reconfirm the input',
UNAUTHORIZED = 'Token is incorrect',
Expand Down
44 changes: 31 additions & 13 deletions frontend/__tests__/src/layouts/Header.test.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,56 @@
import { fireEvent, render } from '@testing-library/react'
import { act, fireEvent, render } from '@testing-library/react'
import Header from '@src/layouts/Header'
import { BrowserRouter, MemoryRouter } from 'react-router-dom'
import { navigateMock } from '../../setupTests'
import { PROJECT_NAME } from '../fixtures'
import { headerClient } from '@src/clients/header/HeaderClient'

describe('Header', () => {
it('should show project name', () => {
const { getByText } = render(
beforeEach(() => {
headerClient.getVersion = jest.fn().mockResolvedValue('')
})

afterEach(() => {
jest.clearAllMocks()
})

const setup = () =>
render(
<BrowserRouter>
<Header />
</BrowserRouter>
)

it('should show project name', () => {
const { getByText } = setup()

expect(getByText(PROJECT_NAME)).toBeInTheDocument()
})

it('should show version info when request succeed', async () => {
headerClient.getVersion = jest.fn().mockResolvedValueOnce('1.11')
const { getByText } = await act(async () => setup())

expect(getByText(/v1.11/)).toBeInTheDocument()
})

it('should show version info when request failed', async () => {
headerClient.getVersion = jest.fn().mockResolvedValueOnce('')
const { queryByText } = await act(async () => setup())

expect(queryByText(/v/)).not.toBeInTheDocument()
})

it('should show project logo', () => {
const { getByRole } = render(
<BrowserRouter>
<Header />
</BrowserRouter>
)
const { getByRole } = setup()

const logoInstance = getByRole('img')
expect(logoInstance).toBeInTheDocument()
expect(logoInstance.getAttribute('alt')).toContain('logo')
})

it('should go to home page when click logo', () => {
const { getByText } = render(
<BrowserRouter>
<Header />
</BrowserRouter>
)
const { getByText } = setup()

fireEvent.click(getByText(PROJECT_NAME))

Expand Down
2 changes: 2 additions & 0 deletions frontend/cypress/e2e/createANewProject.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ describe('Create a new project', () => {
it('Should create a new project manually', () => {
homePage.navigate()

homePage.headerVersion.should('exist')

homePage.createANewProject()
cy.url().should('include', '/metrics')

Expand Down
4 changes: 4 additions & 0 deletions frontend/cypress/pages/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class Home {

private readonly importProjectFromFileButton = () => cy.contains('Import project from file')

get headerVersion() {
return cy.get('span[title="Heartbeat"]').parent().next()
}

navigate() {
cy.visit('/index.html')
}
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/clients/header/HeaderClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { HttpClient } from '@src/clients/Httpclient'
import { VersionResponseDTO } from '@src/clients/header/dto/request'

export class HeaderClient extends HttpClient {
response: VersionResponseDTO = {
version: '',
}

getVersion = async () => {
try {
const res = await this.axiosInstance.get(`/version`)
this.response = res.data
} catch (e) {
this.response = {
version: '',
}
throw e
}
return this.response.version
}
}

export const headerClient = new HeaderClient()
3 changes: 3 additions & 0 deletions frontend/src/clients/header/dto/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface VersionResponseDTO {
version: string
}
22 changes: 18 additions & 4 deletions frontend/src/layouts/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ import {
LogoTitle,
LogoWarp,
NotificationIconContainer,
StyledHeaderInfo,
StyledVersion,
} from '@src/layouts/style'
import { NotificationButton } from '@src/components/Common/NotificationButton/NotificationButton'
import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect'
import { useEffect, useState } from 'react'
import { headerClient } from '@src/clients/header/HeaderClient'

const Header = (props: useNotificationLayoutEffectInterface) => {
const location = useLocation()
const navigate = useNavigate()
const [version, setVersion] = useState('')

const goHome = () => {
navigate('/')
Expand All @@ -31,12 +36,21 @@ const Header = (props: useNotificationLayoutEffectInterface) => {
return ['/metrics'].includes(location.pathname)
}

useEffect(() => {
headerClient.getVersion().then((res) => {
setVersion(res)
})
}, [])

return (
<LogoWarp data-test-id={'Header'}>
<LogoContainer onClick={goHome}>
<LogoImage src={Logo} alt='logo' />
<LogoTitle title={PROJECT_NAME}>{PROJECT_NAME}</LogoTitle>
</LogoContainer>
<StyledHeaderInfo>
<LogoContainer onClick={goHome}>
<LogoImage src={Logo} alt='logo' />
<LogoTitle title={PROJECT_NAME}>{PROJECT_NAME}</LogoTitle>
</LogoContainer>
{version && <StyledVersion>v{version}</StyledVersion>}
</StyledHeaderInfo>
<IconContainer>
{shouldShowNotificationIcon() && (
<NotificationIconContainer title='Notification' data-testid='NotificationButton'>
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/layouts/style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ export const LogoContainer = styled.div({
alignItems: 'center',
cursor: 'pointer',
color: theme.main.color,
marginRight: '1rem',
})

export const StyledHeaderInfo = styled.div({
display: 'flex',
alignItems: 'center',
color: theme.main.color,
})

export const StyledVersion = styled.div({
color: '#ddd',
fontSize: '0.8rem',
paddingTop: '0.5rem',
})

export const IconContainer = styled.div({
Expand Down

0 comments on commit 3bf47ed

Please sign in to comment.