-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
958 export csv #2850
958 export csv #2850
Conversation
Still very much ongoing review, but I am seeing a lot of :any types on the .ts files. I understand it's the quickest way, but not sure if we should make an extra attempt to not do that unless there is no other way around? It kind of defeats the purpose of the typing. On tests I think we can be much more lenient, but on actual code, I think it's important to try to capture the actual types of variables? What do you think @daneryl @konzz? |
@RafaPolit I actually agree with you! Most of the any-typed variables refer to arbitrary objects for which I will need to define types/interfaces (because, right, it was quicker to implement). But now since the code has its iterated design I should be able to make strong typing my next priority after unit-testing. As for the SearchResults type: I couldn't find a type for that in the application. Do you know if we have a type definition? If not I will continue to expand the one I'm using. It won't probably be complete enough to put it under the shared/types (or similar) directory, IMHO. Do you thing it's worth to define it as an application-wide type? |
app/api/csv/typeFormatters.ts
Outdated
[key: string]: (field: any, options?: any) => string; | ||
} = { | ||
select: (field: any) => (field && field[0] ? field[0].label : ''), | ||
multiselect: (field: any) => field?.map((item: any) => formatters.select([item])).join('|') || '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused. How can .join('|') be undefined or null, so that this will require an OR here? Perhaps I'm misreading this? Still, that is one the issues of not doing the tests first, we tend to code for non-existing scenarios.
Also, apparently every other scenario assumes field can be undefined or null (is that even a correct assumption?), yet this one just 'discards' that option with the ? in field. So, which is correct? It can be undefined and this will fail, or all others have a redundant check that is not needed? Please comment. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was not doing the checks at first, but I got errors because I got rows in the results for which the template indicated there should be a field, while it was actually not set in the row. The OR operand is related to the optional chaining of map. It will make the whole instruction return an undefined value and the OR defaults it to the empty string.
Anyways, this is clearly not the place to do those checks as the formatters should only transform an existing field. I'll remove them from here and try to reproduce the failing scenarios.
app/api/csv/typeFormatters.ts
Outdated
link: (field: any) => (field && field[0] ? `${field[0].value.label}|${field[0].value.url}` : ''), | ||
media: (field: any) => (field && field[0] ? field[0].value : ''), | ||
multidate: (field: any, { dateFormat }) => | ||
field?.map((item: any) => formatters.date([item], dateFormat)).join('|') || '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above, not sure how a join can return undefined. It can return an empty string, which would trigger the conditional to return an empty string? This is happening in some other multi-values below.
app/api/files/jsRoutes.js
Outdated
'/api/export', | ||
|
||
validation.validateRequest( | ||
Joi.object().keys({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@daneryl should we start using the new JSON schema here or keep JOI for old route files (even if the endpoint is new)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RafaPolit @fnocetti i think we should remove gradually the joi validations, for sure for new routes use the new one, example here:
Lines 98 to 106 in 988b5dd
validation.validateRequest({ | |
properties: { | |
query: { | |
properties: { | |
_id: { type: 'string' }, | |
}, | |
}, | |
}, | |
}), |
app/api/files/jsRoutes.js
Outdated
return handleError(err); | ||
} | ||
|
||
return fs.unlinkSync(temporalFilePath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if this could be fast, it is still a blocking procedure. While using this in tests may work, please us non-sync unlink here. Also, fs.unlink returns undefined, so be sure this is the expected behavior for success download.
@@ -0,0 +1,76 @@ | |||
import superagent from 'superagent'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a completely new file, should this be a TS file?
} | ||
|
||
export function exportDocuments(storeKey) { | ||
// eslint-disable-next-line max-statements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please refactor this to contain less statements instead of disabling the lint rule.
app/api/files/jsRoutes.js
Outdated
}); | ||
}) | ||
.catch(e => { | ||
fs.unlinkSync(temporalFilePath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, use regular unlink.
@@ -0,0 +1,52 @@ | |||
import PropTypes from 'prop-types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new component should be a TS file.
@@ -0,0 +1,9 @@ | |||
import { combineReducers } from 'redux'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this file TS.
import createReducer from 'app/BasicReducer'; | ||
|
||
export default combineReducers({ | ||
exportProcessing: createReducer('exportProcessing', false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if this should be better namespaced? Perhaps exportCSVProcessing? or exportLibraryProcessing? Also, there should be consistent naming across the store, because the exportEntity store entry point is missleading. This is exportSearchResults, or exportLibrary. ExportEntity should be a 'single' entity export and reserved for a different flow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, makes sense. I'll take care of it.
It probably is, not sure if this should be done at this point. For sure, we want typing of our search results. |
17f666d
to
0bd0f74
Compare
6b40f7c
to
7d95a1e
Compare
import translations from 'api/i18n/translations'; | ||
import formatters from './typeFormatters'; | ||
|
||
export type SearchResults = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything is exported: all functions, all types. Is this really necessary? The specs only import about 12 of the 16 exports here, and external modules should probably only import the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. It also suggests that the testing strategy might be wrong. I propose you not exporting the elements that are not explicitly tested right away, and assuming the tech debt of refactoring this class.
app/api/csv/csvExporter.ts
Outdated
}; | ||
|
||
export const getTypes = (searchResults: SearchResults, typesWhitelist: string[] = []) => | ||
typesWhitelist.length > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary compare operator here: typesWhitelist.length
is enough, as it will be true if > 0 and false if not.
app/api/csv/csvExporter.ts
Outdated
collection.findIndex((i: any) => Object.keys(i).every(key => i[key] === item[key])) < 0; | ||
|
||
export const excludedProperties = (property: PropertySchema) => | ||
!['geolocation', 'preview', 'markdown', 'nested'].includes(property.type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that geolocation properties are also not included? Or are they processed elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Answer: #2850 (comment)
|
||
export const concatCommonHeaders = (headers: ExportHeader[]) => | ||
headers.concat([ | ||
{ label: 'Geolocation', name: 'geolocation', common: true }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While Documents, Attachments and Published are 'higher order' properties, the _geolocation
properties reside within the metadata, they are not common to all templates, and there can be several with different labels for a single template. I'm having a hard time following the process of geolocation. I think there is something wrong in the concept here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The geolocation property is treated as a special case because I found there is some different logic going on in its displaying across the app. I may be totally wrong and had misunderstood the concept. Let's have a chat asap.
{ | ||
_id: '58ad7d240d44252fee4e6200', | ||
id: 'f6c30c8a-3013-471f-be8c-e639f6aeb031', | ||
name: 'company', | ||
type: 'text', | ||
label: 'company', | ||
}, | ||
{ | ||
_id: '58ad7d240d44252fee4e61ff', | ||
relationType: '5aae90e0bfbf88e5ae28b19e', | ||
id: '3780239a-f858-4df5-9dea-8ea4a59cfc9e', | ||
name: 'nemesis', | ||
content: '58ad7d240d44252fee4e61fb', | ||
type: 'relationship', | ||
label: 'Nemesis', | ||
}, | ||
{ | ||
_id: '5e3d19ccdeeb2652690a1258', | ||
label: 'Costume', | ||
type: 'select', | ||
content: '5e3d1853deeb2652690a0c10', | ||
showInCard: false, | ||
filter: false, | ||
name: 'costume', | ||
id: 'a5407e10-5148-4241-ad81-0c8fa78f1c43', | ||
}, | ||
{ | ||
_id: '58ad7d240d44252fee4e61fe', | ||
filter: true, | ||
id: '113d9a13-7fb9-447f-abf1-4075a9f8eb00', | ||
name: 'super_powers', | ||
content: '58ad7d240d44252fee4e6208', | ||
type: 'multiselect', | ||
label: 'Super powers', | ||
}, | ||
{ | ||
_id: '59859ad8ddb12b0ce6664927', | ||
relationType: '5a8480fac464318833d9b553', | ||
label: 'Allies', | ||
type: 'relationship', | ||
content: '58ad7d240d44252fee4e61fb', | ||
name: 'allies', | ||
id: 'c22326ac-6723-42b5-bb3e-de0fdcc2e2dc', | ||
}, | ||
{ | ||
nestedProperties: [], | ||
_id: '5e8f9509f16db8b791fec574', | ||
label: 'Geolocation', | ||
type: 'geolocation', | ||
name: 'geolocation_geolocation', | ||
id: 'e9b810a9-8f25-442f-b521-616f3f3bbcdd', | ||
}, | ||
], | ||
}, | ||
'58ad7d240d44252fee4e61fb': { | ||
_id: '58ad7d240d44252fee4e61fb', | ||
name: 'Super Villian', | ||
properties: [ | ||
{ | ||
_id: '5e3d1880deeb2652690a1036', | ||
label: 'Costume', | ||
type: 'select', | ||
content: '5e3d1853deeb2652690a0c10', | ||
name: 'costume', | ||
id: '53d6bb4a-2819-47b5-95a3-9261da5e8a69', | ||
}, | ||
{ | ||
_id: '58ad7d240d44252fee4e61fc', | ||
id: '58dd46d2-b52c-4e80-a859-6f4fadabe4c0', | ||
name: 'super_powers', | ||
content: '58ad7d240d44252fee4e6208', | ||
type: 'multiselect', | ||
label: 'Super powers', | ||
}, | ||
{ | ||
_id: '594bc3b0bee8b3829aea937f', | ||
relationType: '5aae90e0bfbf88e5ae28b1a3', | ||
label: 'Sidekick', | ||
type: 'relationship', | ||
content: '58f0aed2e147e720856a0741', | ||
name: 'sidekick', | ||
id: '0c45e6dc-0081-463e-9300-c46d13b1dcd2', | ||
}, | ||
{ | ||
_id: '594bc3b0bee8b3829aea937e', | ||
label: 'Planets conquered', | ||
type: 'numeric', | ||
name: 'planets_conquered', | ||
id: '6ec2c27a-ec30-4d0f-a7da-217a57e40ef2', | ||
}, | ||
{ | ||
_id: '594bc3b0bee8b3829aea937d', | ||
label: 'DOB', | ||
type: 'date', | ||
name: 'dob', | ||
id: '87289f51-21fb-4ff6-bb8d-8b7d66d86526', | ||
}, | ||
], | ||
}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this fixtures are long enough to merit a 'fixtrues' file like in many other api tests?
exportDocuments: (keyStore: string) => any; | ||
}; | ||
|
||
export class ExportButton extends Component<ExportButtonProps, {}> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not export the component if it is connected. You can import the connected component in the test and either use the .WrappedComponent
property of the component, or you can use '.dive()' methods to inject the store into the component. That also allows you to test the mapStateToProps directly, which in this case are not tested at all.
You can check the dive method on /app/react/components/Elements/specs/FeatureToggle.spec.tsx
app/react/UI/Icon/export-csv.js
Outdated
/** | ||
* | ||
* | ||
* @format | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer required, please remove.
@@ -88,6 +88,7 @@ import { faUsers } from '@fortawesome/free-solid-svg-icons/faUsers'; | |||
import { faUserTimes } from '@fortawesome/free-solid-svg-icons/faUserTimes'; | |||
import { faHandPaper } from '@fortawesome/free-solid-svg-icons/faHandPaper'; | |||
import { saveAndNext } from './save-and-next'; | |||
import { exportCsv } from './export-csv'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there really no icon in the entire font-awesome library that we had to create our own icon? Couldn't we have picked an icon from the library and added it here? Can you elaborate on how this happened? Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was requested by @simonfossom from his work in the new look and feel. There is a download/export icon that could be useful but the icon he proposed is a merge between a file download and a grid that better represent a CSV export.
Maybe we can have a conversation about that if we prefer avoiding to create custom icons.
package.json
Outdated
"@types/fetch-mock": "^7.3.2", | ||
"@types/hoist-non-react-statics": "^3.3.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you require 3.3.1, please also update the hoist-non-react-statistics to the ^3.3.1 please, lets not have a discrepancy of types and installed version.
I found a bug that I need to take care of. |
This LGTM. |
fixes #958
PR checklist:
QA checklist: