Skip to content

Commit

Permalink
Merge pull request #2618 from cozy/feat/Add-export-data-page
Browse files Browse the repository at this point in the history
Add export data page
  • Loading branch information
Merkur39 authored Mar 6, 2023
2 parents 069893a + 09cef1e commit dbe6a86
Show file tree
Hide file tree
Showing 24 changed files with 1,075 additions and 19 deletions.
11 changes: 11 additions & 0 deletions manifest.webapp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@
"file": "konnectorAlerts.js",
"trigger": "@event io.cozy.jobs:UPDATED:konnector:worker",
"debounce": "60s"
},
"export": {
"type": "node",
"file": "export.js",
"comment": "This service must be called programmatically from Banks.",
"debounce": "5m"
}
},
"notifications": {
Expand Down Expand Up @@ -272,6 +278,11 @@
"description": "Used to link tags to bank operations",
"type": "io.cozy.tags",
"verbs": ["GET", "POST", "PUT", "DELETE"]
},
"files": {
"description": "Used by the export feature to store the exported data",
"type": "io.cozy.files",
"verbs": ["POST", "PUT", "GET"]
}
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"diacritics": "^1.3.0",
"element-scroll-polyfill": "1.0.1",
"eslint-config-cozy-app": "^4.0.0",
"fast-csv": "^4.3.6",
"fastclick": "^1.0.6",
"fuse.js": "^6.4.2",
"geco": "0.11.1",
Expand Down
4 changes: 3 additions & 1 deletion src/AppContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
ACCOUNT_DOCTYPE,
COZY_ACCOUNT_DOCTYPE,
TRANSACTION_DOCTYPE,
GROUP_DOCTYPE
GROUP_DOCTYPE,
FILES_DOCTYPE
} from 'doctypes'
import { StoreURLProvider } from 'ducks/store/StoreContext'
const jobsProviderOptions = t => ({
Expand Down Expand Up @@ -91,6 +92,7 @@ const AppContainer = ({ store, lang, client }) => {
<RealTimeQueries doctype={COZY_ACCOUNT_DOCTYPE} />
<RealTimeQueries doctype={TRANSACTION_DOCTYPE} />
<RealTimeQueries doctype={GROUP_DOCTYPE} />
<RealTimeQueries doctype={FILES_DOCTYPE} />
<Router>
<AppRoute />
</Router>
Expand Down
23 changes: 23 additions & 0 deletions src/assets/icons/download-illu.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/components/AppRoute.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ import ScrollToTopOnMountWrapper from 'components/scrollToTopOnMount'
import PlannedTransactionsPage from 'ducks/future/PlannedTransactionsPage'
import SetFilterAndRedirect from 'ducks/balance/SetFilterAndRedirect'
import TagPage from 'ducks/tags/TagPage'
import Export from 'ducks/settings/Export'

// Use a function to delay instantation and have access to AppRoute.renderExtraRoutes
const AppRoute = () => (
<Routes>
<Route element={<UserActionRequired />}>
{AppRoute.renderExtraRoutesWithoutLayout()}
<Route path="/" element={<App />}>
{isWebApp() && (
<Route index element={<Navigate to="balances" replace />} />
Expand Down Expand Up @@ -128,6 +130,11 @@ const AppRoute = () => (
</Route>
</Route>
<Route path="settings">
<Route
path="configuration/export"
element={<Navigate to="../export" replace />}
/>
<Route path="export" element={<Export />} />
<Route
element={
<ScrollToTopOnMountWrapper>
Expand Down Expand Up @@ -213,5 +220,6 @@ const AppRoute = () => (

// Ability to overrides easily
AppRoute.renderExtraRoutes = () => null
AppRoute.renderExtraRoutesWithoutLayout = () => null

export default AppRoute
26 changes: 26 additions & 0 deletions src/components/__snapshots__/AppRoute.spec.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ exports[`App route should be a function returning a route 1`] = `
<Route
path="settings"
>
<Route
element={
<Navigate
replace={true}
to="../export"
/>
}
path="configuration/export"
/>
<Route
element={<Export />}
path="export"
/>
<Route
element={
<ScrollToTopOnMountWrapper>
Expand Down Expand Up @@ -370,6 +383,19 @@ exports[`App route should have renderExtraRoutes 1`] = `
<Route
path="settings"
>
<Route
element={
<Navigate
replace={true}
to="../export"
/>
}
path="configuration/export"
/>
<Route
element={<Export />}
path="export"
/>
<Route
element={
<ScrollToTopOnMountWrapper>
Expand Down
1 change: 1 addition & 0 deletions src/doctypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const CONTACT_DOCTYPE = 'io.cozy.contacts'
export const RECURRENCE_DOCTYPE = 'io.cozy.bank.recurrence'
export const IDENTITIES_DOCTYPE = 'io.cozy.identities'
export const TAGS_DOCTYPE = 'io.cozy.tags'
export const FILES_DOCTYPE = 'io.cozy.files'

export const offlineDoctypes = [
ACCOUNT_DOCTYPE,
Expand Down
4 changes: 4 additions & 0 deletions src/ducks/export/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const DATE_FORMAT = 'YYYY-MM-DD'
export const DATA_EXPORT_DIR_ID = 'io.cozy.files.root-dir'
export const DATA_EXPORT_NAME = 'export-data-banks.csv'
export const DATA_EXPORT_PATH = `/${DATA_EXPORT_NAME}`
34 changes: 34 additions & 0 deletions src/ducks/export/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { JOBS_DOCTYPE } from 'src/doctypes'

/**
* Find export job in progress
*
* @param {CozyClient} client
*/
export const isExportJobInProgress = async client => {
const jobColl = client.collection(JOBS_DOCTYPE)
// This method return all jobs queued or running state
const { data: allJobsServiceQueuedOrRunning } = await jobColl.queued(
'service'
)

return allJobsServiceQueuedOrRunning.some(
job =>
job.attributes.message.slug === 'banks' &&
job.attributes.message.name === 'export'
)
}

/**
* Launch export job
*
* @param {CozyClient} client
*/
export const launchExportJob = async client => {
const exportJobInProgress = await isExportJobInProgress(client)

if (!exportJobInProgress) {
const jobColl = client.collection(JOBS_DOCTYPE)
await jobColl.create('service', { slug: 'banks', name: 'export' }, {}, true)
}
}
96 changes: 96 additions & 0 deletions src/ducks/export/helspers.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { launchExportJob, isExportJobInProgress } from './helpers'

describe('Export Job', () => {
const makeJob = ({ name, slug, state }) => {
return {
type: 'io.cozy.jobs',
attributes: {
worker: 'service',
message: {
name,
slug
},
state
}
}
}

const setup = ({
mockQueued = jest.fn(),
mockCreate = jest.fn(),
mockDownload = jest.fn()
} = {}) => {
const client = {
collection: jest.fn(() => ({
queued: mockQueued,
create: mockCreate,
download: mockDownload
}))
}
return client
}

describe('launchExportJob', () => {
it('should not create a new job if it already exists', async () => {
const expected = [
makeJob({ name: 'export', slug: 'banks' }),
makeJob({ name: 'otherName', slug: 'otherSlug' })
]
const mockQueued = jest.fn(() => ({ data: expected }))
const mockCreate = jest.fn(() => ({ data: expected }))
const client = setup({ mockQueued, mockCreate })

await launchExportJob(client)

expect(mockCreate).toBeCalledTimes(0)
})

it("should create a new job if it doesn't already exist with the correct arguments", async () => {
const expected = [
makeJob({ name: 'na', slug: 'na' }),
makeJob({ name: 'otherName', slug: 'otherSlug' })
]
const mockQueued = jest.fn(() => ({ data: expected }))
const mockCreate = jest.fn(() => ({ data: expected }))
const client = setup({ mockQueued, mockCreate })

await launchExportJob(client)

expect(mockCreate).toBeCalledTimes(1)
expect(mockCreate).toBeCalledWith(
'service',
{ slug: 'banks', name: 'export' },
{},
true
)
})
})

describe('isExportJobInProgress', () => {
it('should return "false" if export job is not running or queued', async () => {
const expected = [
makeJob({ name: 'na', slug: 'na' }),
makeJob({ name: 'otherName', slug: 'otherSlug' })
]
const mockQueued = jest.fn(() => ({ data: expected }))
const client = setup({ mockQueued })

const res = await isExportJobInProgress(client)

expect(res).toBe(false)
})

it('should return "true" if export job is running or queued', async () => {
const expected = [
makeJob({ name: 'export', slug: 'banks' }),
makeJob({ name: 'otherName', slug: 'otherSlug' })
]
const mockQueued = jest.fn(() => ({ data: expected }))
const client = setup({ mockQueued })

const res = await isExportJobInProgress(client)

expect(res).toBe(true)
})
})
})
2 changes: 2 additions & 0 deletions src/ducks/export/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import cozyLogger from 'cozy-logger'
export default cozyLogger.namespace('export')
Loading

0 comments on commit dbe6a86

Please sign in to comment.