diff --git a/package-lock.json b/package-lock.json index 3f6cbf6392..45e8f0922f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "slate-history": "^0.93.0", "slate-hyperscript": "^0.77.0", "slate-react": "^0.99.0", - "terraso-client-shared": "github:techmatters/terraso-client-shared#f211e372131f71d40f66b5e3a77f629db879d837", + "terraso-client-shared": "github:techmatters/terraso-client-shared#6cc1a24717090875f8d1822986529eeb6237c746", "use-debounce": "^9.0.4", "uuid": "^9.0.0", "web-vitals": "^3.5.0", @@ -27360,20 +27360,20 @@ }, "node_modules/terraso-backend": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/techmatters/terraso-backend.git#88c5e4a630c9ac5d1b206130bc2c96ad75b737b8", - "integrity": "sha512-XZZcn/sQ7vVYRJx0jUiKpLnBFjKc4Zum/7k6O7EPSPhiPx68E3JKrCtXsAZ+km1kGA/Feh6vVLvtOi7IfwkS8w==" + "resolved": "git+ssh://git@github.com/techmatters/terraso-backend.git#93e844d8c4f031320b9f04c98b89144cdc51caba", + "integrity": "sha512-z0VltMm7mu82rpMyaVYKiXYKD/GLhMdigqIkOfcpABW6KbwlTjbOvyeWvqNxivw3ONkcWvKgNl+u6kkX9qiWSw==" }, "node_modules/terraso-client-shared": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/techmatters/terraso-client-shared.git#f211e372131f71d40f66b5e3a77f629db879d837", - "integrity": "sha512-TxaNj0v7KGvUhFmE6qViwnJtyXTG2U4WlSiPlfzVvNDXV6vXMSrimwNAP/ehN37NnjnQFUGzXU3bv0m+5hagPA==", + "resolved": "git+ssh://git@github.com/techmatters/terraso-client-shared.git#6cc1a24717090875f8d1822986529eeb6237c746", + "integrity": "sha512-L8z7HzeP5uf8SmRc/udT+MeG4u2QTdjFrEwJVtyEyldpjOTqtjNdaMwgn4VGENBxXp/qpplTgKubm/F/JbelVA==", "dependencies": { "@reduxjs/toolkit": "^1.9.7", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", "react": "^18.2.0", "react-redux": "^8.1.2", - "terraso-backend": "github:techmatters/terraso-backend#88c5e4a630c9ac5d1b206130bc2c96ad75b737b8", + "terraso-backend": "github:techmatters/terraso-backend#93e844d8c4f031320b9f04c98b89144cdc51caba", "uuid": "^9.0.1" } }, diff --git a/package.json b/package.json index acfa14a337..1acfd23814 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "slate-history": "^0.93.0", "slate-hyperscript": "^0.77.0", "slate-react": "^0.99.0", - "terraso-client-shared": "github:techmatters/terraso-client-shared#f211e372131f71d40f66b5e3a77f629db879d837", + "terraso-client-shared": "github:techmatters/terraso-client-shared#6cc1a24717090875f8d1822986529eeb6237c746", "use-debounce": "^9.0.4", "uuid": "^9.0.0", "web-vitals": "^3.5.0", diff --git a/src/group/groupFragments.ts b/src/group/groupFragments.ts index 66d7e6cec7..c2b0de442c 100644 --- a/src/group/groupFragments.ts +++ b/src/group/groupFragments.ts @@ -35,13 +35,17 @@ export const groupsListFields = /* GraphQL */ ` } `; -export const dataEntries = /* GraphQL */ ` - fragment dataEntries on GroupNode { - dataEntries(resourceType_In: $resourceTypes) { +export const groupDataEntries = /* GraphQL */ ` + fragment groupDataEntries on GroupNode { + sharedResources(source_DataEntry_ResourceType_In: $resourceTypes) { edges { node { - ...dataEntry - ...dataEntryVisualizations + source { + ... on DataEntryNode { + ...dataEntry + ...dataEntryVisualizations + } + } } } } diff --git a/src/group/groupService.ts b/src/group/groupService.ts index 91b4e8af51..38571e6bca 100644 --- a/src/group/groupService.ts +++ b/src/group/groupService.ts @@ -27,6 +27,8 @@ import { extractDataEntries } from 'sharedData/sharedDataUtils'; import type { Group } from './groupSlice'; +import { SHARED_DATA_ACCEPTED_EXTENSIONS } from 'config'; + export const fetchGroupToUpdate = (slug: string) => { const query = graphql(` query groupToUpdate($slug: String!) { @@ -48,7 +50,7 @@ export const fetchGroupToUpdate = (slug: string) => { export const fetchGroupToView = async (slug: string) => { const query = graphql(` - query groupToView($slug: String!) { + query groupToView($slug: String!, $resourceTypes: [String]!) { groups(slug: $slug) { edges { node { @@ -56,13 +58,17 @@ export const fetchGroupToView = async (slug: string) => { ...groupMembersInfo ...groupMembersPending ...accountMembership + ...groupDataEntries } } } } `); return terrasoApi - .requestGraphQL(query, { slug }) + .requestGraphQL(query, { + slug, + resourceTypes: SHARED_DATA_ACCEPTED_EXTENSIONS, + }) .then(resp => resp.groups?.edges.at(0)?.node || Promise.reject('not_found')) .then(group => ({ ..._.omit(['memberships', 'membershipsCount'], group), diff --git a/src/landscape/components/LandscapeSharedDataVisualization.js b/src/landscape/components/LandscapeSharedDataVisualization.js index 918bfe1842..bdafc4a462 100644 --- a/src/landscape/components/LandscapeSharedDataVisualization.js +++ b/src/landscape/components/LandscapeSharedDataVisualization.js @@ -58,10 +58,9 @@ const LandscapeSharedDataVisualization = () => { } return ( - + navigate(`/landscapes/${landscapeSlug}`)} /> diff --git a/src/landscape/components/LandscapeSharedDataVisualization.test.js b/src/landscape/components/LandscapeSharedDataVisualization.test.js index 41e3443e4b..c00ac98a16 100644 --- a/src/landscape/components/LandscapeSharedDataVisualization.test.js +++ b/src/landscape/components/LandscapeSharedDataVisualization.test.js @@ -256,4 +256,12 @@ test('LandscapeSharedDataVisualization: Display visualization', async () => { expect(domElement.querySelector('p').textContent).toEqual( 'Custom Label: val11' ); + + // Validate fetch input + const fetchCall = terrasoApi.requestGraphQL.mock.calls[3]; + expect(fetchCall[1]).toStrictEqual({ + configSlug: 'config-slug', + ownerSlug: 'jose-landscape-deafult-test-4', + ownerType: 'landscape', + }); }); diff --git a/src/landscape/components/LandscapeSharedDataVisualizationConfig.js b/src/landscape/components/LandscapeSharedDataVisualizationConfig.js index 232ea2cc3f..47d095ad12 100644 --- a/src/landscape/components/LandscapeSharedDataVisualizationConfig.js +++ b/src/landscape/components/LandscapeSharedDataVisualizationConfig.js @@ -71,7 +71,7 @@ const LandscapeSharedDataVisualizationConfig = () => { return ( - + }, }); } - if (trimmedQuery.startsWith('query group')) { + if (trimmedQuery.startsWith('query dataEntries')) { return Promise.resolve({ - groups: { + dataEntries: { edges: [ { node: { - dataEntries: { - edges: [ - { - node: { - createdAt: '2022-05-17T23:32:50.606587+00:00', - createdBy: { - id: 'dc695d00-d6b4-45b2-ab8d-f48206d998da', - lastName: 'Buitrón', - firstName: 'José', - }, - description: '', - id: 'f00c5564-cf93-471a-94c2-b930cbb0a4f8', - name: 'File 1', - resourceType: 'text/csv', - size: 3565, - url: 'https://file-url', - visualizations: { edges: [] }, - }, - }, - ], + createdAt: '2022-05-17T23:32:50.606587+00:00', + createdBy: { + id: 'dc695d00-d6b4-45b2-ab8d-f48206d998da', + lastName: 'Buitrón', + firstName: 'José', }, + description: '', + id: 'f00c5564-cf93-471a-94c2-b930cbb0a4f8', + name: 'File 1', + resourceType: 'text/csv', + size: 3565, + url: 'https://file-url', + visualizations: { edges: [] }, }, }, ], @@ -327,6 +319,14 @@ test('LandscapeSharedDataVisualizationConfig: Create visualization', async () => await testAnnotateStep(); await testPreviewStep(map, events); + // Fetch data entries validation + const fetchCall = terrasoApi.requestGraphQL.mock.calls[3]; + expect(fetchCall[1]).toStrictEqual({ + resourceTypes: ['csv', 'xls', 'xlsx'], + slug: 'landscape-slug', + type: 'landscape', + }); + // Save await act(async () => fireEvent.click(screen.getByRole('button', { name: 'Publish' })) @@ -335,7 +335,8 @@ test('LandscapeSharedDataVisualizationConfig: Create visualization', async () => const saveCall = terrasoApi.requestGraphQL.mock.calls[4]; expect(saveCall[1]).toStrictEqual({ input: { - groupId: '6a625efb-4ec8-45e8-ad6a-eb052cc3fe65', + ownerId: 'e9a65bef-4ef1-4058-bba3-fc73b53eb779', + ownerType: 'landscape', title: 'Test Title', configuration: JSON.stringify({ datasetConfig: { diff --git a/src/landscape/landscapeService.js b/src/landscape/landscapeService.js index c69f7c4ad6..0310fc0381 100644 --- a/src/landscape/landscapeService.js +++ b/src/landscape/landscapeService.js @@ -34,6 +34,8 @@ import { extractPartnership, } from './landscapeUtils'; +import { SHARED_DATA_ACCEPTED_EXTENSIONS } from 'config'; + const cleanLandscape = landscape => _.flow( _.pick([ @@ -143,13 +145,14 @@ const getDefaultGroup = landscape => { export const fetchLandscapeToView = slug => { const query = graphql(` - query landscapesToView($slug: String!) { + query landscapesToView($slug: String!, $resourceTypes: [String]!) { landscapes(slug: $slug) { edges { node { ...landscapeFields ...landscapePartnershipField ...defaultGroupWithMembersSample + ...landscapeDataEntries areaPolygon } } @@ -157,7 +160,10 @@ export const fetchLandscapeToView = slug => { } `); return terrasoApi - .requestGraphQL(query, { slug }) + .requestGraphQL(query, { + slug, + resourceTypes: SHARED_DATA_ACCEPTED_EXTENSIONS, + }) .then(_.get('landscapes.edges[0].node')) .then(landscape => landscape || Promise.reject('not_found')) .then(landscape => ({ diff --git a/src/permissions/rules.js b/src/permissions/rules.js index fea53fc96c..5e7ca0cb56 100644 --- a/src/permissions/rules.js +++ b/src/permissions/rules.js @@ -66,9 +66,10 @@ const isAllowedToDeleteSharedData = ({ resource, user }) => { }; const isAllowedToDeleteVisualization = ({ - resource: { group, visualizationConfig }, + resource: { owner, visualizationConfig }, user, }) => { + const group = owner.defaultGroup || owner; const isManager = hasRole({ group, role: ROLE_MANAGER }); const isOwner = _.get('createdBy.id', visualizationConfig) === _.get('id', user); diff --git a/src/sharedData/sharedDataService.js b/src/sharedData/sharedDataService.js index 28c2134cd4..814ea4d0d3 100644 --- a/src/sharedData/sharedDataService.js +++ b/src/sharedData/sharedDataService.js @@ -18,7 +18,7 @@ import _ from 'lodash/fp'; import * as terrasoApi from 'terraso-client-shared/terrasoApi/api'; import { graphql } from 'terrasoApi/shared/graphqlSchema'; -import { extractDataEntries, extractDataEntry } from './sharedDataUtils'; +import { extractDataEntry } from './sharedDataUtils'; import { SHARED_DATA_ACCEPTED_EXTENSIONS } from 'config'; @@ -122,31 +122,45 @@ export const updateSharedData = ({ dataEntry }) => { .then(extractDataEntry); }; -export const fetchGroupSharedData = ({ - slug, +export const fetchSharedData = ({ + targetSlug, + targetType, resourceTypes = ALL_RESOURCE_TYPES, }) => { const query = graphql(` - query group($slug: String!, $resourceTypes: [String]!) { - groups(slug: $slug) { + query dataEntries( + $slug: String! + $type: String! + $resourceTypes: [String]! + ) { + dataEntries( + sharedResources_Target_Slug: $slug + sharedResources_TargetContentType: $type + resourceType_In: $resourceTypes + ) { edges { node { - ...dataEntries + ...dataEntry + ...dataEntryVisualizations } } } } `); return terrasoApi - .requestGraphQL(query, { slug, resourceTypes }) - .then(_.get('groups.edges[0].node')) - .then(group => group || Promise.reject('not_found')) - .then(group => extractDataEntries(group)); + .requestGraphQL(query, { + slug: targetSlug, + type: targetType, + resourceTypes, + }) + .then(_.get('dataEntries.edges')) + .then(edges => edges.map(edge => extractDataEntry(edge.node))); }; export const addVisualizationConfig = ({ title, - group, + ownerId, + ownerType, selectedFile, visualizationConfig, }) => { @@ -171,7 +185,8 @@ export const addVisualizationConfig = ({ title, configuration, dataEntryId: selectedFile.id, - groupId: group.id, + ownerId, + ownerType, }, }) .then(response => ({ @@ -195,11 +210,20 @@ export const deleteVisualizationConfig = config => { }); }; -export const fetchVisualizationConfig = ({ groupSlug, configSlug }) => { +export const fetchVisualizationConfig = ({ + ownerSlug, + ownerType, + configSlug, +}) => { const query = graphql(` - query fetchVisualizationConfig($groupSlug: String!, $configSlug: String!) { + query fetchVisualizationConfig( + $ownerSlug: String! + $ownerType: String! + $configSlug: String! + ) { visualizationConfigs( - dataEntry_SharedResources_Target_Slug: $groupSlug + dataEntry_SharedResources_Target_Slug: $ownerSlug + dataEntry_SharedResources_TargetContentType: $ownerType slug: $configSlug ) { edges { @@ -214,7 +238,7 @@ export const fetchVisualizationConfig = ({ groupSlug, configSlug }) => { } `); return terrasoApi - .requestGraphQL(query, { groupSlug, configSlug }) + .requestGraphQL(query, { ownerSlug, ownerType, configSlug }) .then(_.get('visualizationConfigs.edges[0].node')) .then( visualizationConfig => visualizationConfig || Promise.reject('not_found') diff --git a/src/sharedData/sharedDataSlice.js b/src/sharedData/sharedDataSlice.js index 1e58b4fb58..3f697702dd 100644 --- a/src/sharedData/sharedDataSlice.js +++ b/src/sharedData/sharedDataSlice.js @@ -74,9 +74,9 @@ export const updateSharedData = createAsyncThunk( params: { name: dataEntry.name }, }) ); -export const fetchGroupSharedData = createAsyncThunk( - 'sharedData/fetchGroupSharedData', - sharedDataService.fetchGroupSharedData +export const fetchSharedData = createAsyncThunk( + 'sharedData/fetchSharedData', + sharedDataService.fetchSharedData ); export const addVisualizationConfig = createAsyncThunk( @@ -226,7 +226,7 @@ const sharedDataSlice = createSlice({ ) ); - builder.addCase(fetchGroupSharedData.pending, (state, action) => ({ + builder.addCase(fetchSharedData.pending, (state, action) => ({ ...state, list: { fetching: true, @@ -234,7 +234,7 @@ const sharedDataSlice = createSlice({ }, })); - builder.addCase(fetchGroupSharedData.fulfilled, (state, action) => ({ + builder.addCase(fetchSharedData.fulfilled, (state, action) => ({ ...state, list: { fetching: false, diff --git a/src/sharedData/visualization/components/VisualizationConfigForm/PreviewStep.js b/src/sharedData/visualization/components/VisualizationConfigForm/PreviewStep.js index a7415f9639..3e5fa35cfb 100644 --- a/src/sharedData/visualization/components/VisualizationConfigForm/PreviewStep.js +++ b/src/sharedData/visualization/components/VisualizationConfigForm/PreviewStep.js @@ -44,7 +44,7 @@ const PreviewStep = props => { const [viewportConfig, setViewportConfig] = useState( visualizationConfig.viewportConfig ); - const { owner, entityTypeLocalized, group } = useGroupContext(); + const { owner, entityTypeLocalized, entityType } = useGroupContext(); useEffect(() => { setViewportConfig(visualizationConfig.viewportConfig); @@ -88,7 +88,8 @@ const PreviewStep = props => { title: _.get('annotateConfig.mapTitle', completeConfig), visualizationConfig: filteredConfig, selectedFile, - group, + ownerId: owner.id, + ownerType: entityType, }) ).then(data => { const success = _.get('meta.requestStatus', data) === 'fulfilled'; @@ -106,9 +107,10 @@ const PreviewStep = props => { }); }, [ dispatch, - group, onSaved, owner.name, + owner.id, + entityType, selectedFile, trackEvent, viewportConfig, diff --git a/src/sharedData/visualization/components/VisualizationConfigForm/SelectDataFileStep.js b/src/sharedData/visualization/components/VisualizationConfigForm/SelectDataFileStep.js index e49bfe7d2a..0de9732a64 100644 --- a/src/sharedData/visualization/components/VisualizationConfigForm/SelectDataFileStep.js +++ b/src/sharedData/visualization/components/VisualizationConfigForm/SelectDataFileStep.js @@ -36,7 +36,7 @@ import PageLoader from 'layout/PageLoader'; import { formatDate } from 'localization/utils'; import { useGroupContext } from 'group/groupContext'; import SharedFileIcon from 'sharedData/components/SharedFileIcon'; -import { fetchGroupSharedData } from 'sharedData/sharedDataSlice'; +import { fetchSharedData } from 'sharedData/sharedDataSlice'; import { useVisualizationContext } from 'sharedData/visualization/visualizationContext'; const ACCEPTED_RESOURCE_TYPES = ['csv', 'xls', 'xlsx']; @@ -50,7 +50,7 @@ const SelectDataFileStep = props => { const { i18n, t } = useTranslation(); const { visualizationConfig } = useVisualizationContext(); const { onNext, onBack } = props; - const { group } = useGroupContext(); + const { owner, entityType } = useGroupContext(); const { data: sharedFiles, fetching } = useSelector(_.get('sharedData.list')); const [selected, setSelected] = useState(); @@ -62,12 +62,13 @@ const SelectDataFileStep = props => { useEffect(() => { dispatch( - fetchGroupSharedData({ - slug: group.slug, + fetchSharedData({ + targetSlug: owner.slug, + targetType: entityType, resourceTypes: ACCEPTED_RESOURCE_TYPES, }) ); - }, [dispatch, group.slug]); + }, [dispatch, owner.slug, entityType]); // If there are no files to map, go back to the landscape/group index page. useEffect(() => { diff --git a/src/sharedData/visualization/components/VisualizationWrapper.js b/src/sharedData/visualization/components/VisualizationWrapper.js index 1971787c95..05ad6f6196 100644 --- a/src/sharedData/visualization/components/VisualizationWrapper.js +++ b/src/sharedData/visualization/components/VisualizationWrapper.js @@ -60,16 +60,22 @@ const VisualizationWrapper = props => { const dispatch = useDispatch(); const { i18n, t } = useTranslation(); const { downloadFile } = useSharedData(); - const { groupSlug, configSlug, onDeleted } = props; + const { configSlug, onDeleted } = props; const { data, fetching, deleting } = useSelector( state => state.sharedData.visualizationConfig ); - const { group, owner, entityType } = useGroupContext(); + const { owner, entityType } = useGroupContext(); const [imagePrinter, setImagePrinter] = useState(); useEffect(() => { - dispatch(fetchVisualizationConfig({ groupSlug, configSlug })); - }, [dispatch, groupSlug, configSlug]); + dispatch( + fetchVisualizationConfig({ + ownerSlug: owner.slug, + ownerType: entityType, + configSlug, + }) + ); + }, [dispatch, owner.slug, entityType, configSlug]); const visualizationConfig = useMemo( () => @@ -133,7 +139,7 @@ const VisualizationWrapper = props => {