From 05edba11ef0b5a963e1c76becca20722a23219f6 Mon Sep 17 00:00:00 2001 From: Jose Buitron Date: Tue, 24 Oct 2023 14:41:13 -0500 Subject: [PATCH] refactor: Link landscapes directly to shared files, links and visualizations (#1229) --- package-lock.json | 16 ++--- package.json | 2 +- src/group/components/GroupSharedDataUpload.js | 7 +- .../GroupSharedDataUploadFiles.test.js | 4 ++ src/group/components/GroupView.test.js | 40 ++++++++++- src/group/groupFragments.ts | 14 ++-- src/group/groupService.ts | 8 ++- src/group/groupSlice.ts | 2 + .../components/LandscapeSharedDataUpload.js | 8 ++- .../LandscapeSharedDataUploadLinks.test.js | 13 ++++ .../LandscapeSharedDataVisualization.js | 3 +- .../LandscapeSharedDataVisualization.test.js | 9 +++ .../LandscapeSharedDataVisualizationConfig.js | 2 +- ...scapeSharedDataVisualizationConfig.test.js | 47 +++++++------ .../components/LandscapeView.test.js | 35 ++++------ src/landscape/landscapeFragments.js | 17 +++++ src/landscape/landscapeService.js | 7 +- src/landscape/landscapeService.test.ts | 1 + src/landscape/landscapeSlice.js | 2 + src/permissions/rules.js | 3 +- src/sharedData/components/SharedDataCard.js | 11 +-- .../components/SharedDataUpload/index.js | 8 +-- src/sharedData/sharedDataService.js | 70 +++++++++++++------ src/sharedData/sharedDataSlice.js | 20 ++++-- .../sharedDataUtils.js} | 17 ++--- .../VisualizationConfigForm/PreviewStep.js | 8 ++- .../SelectDataFileStep.js | 11 +-- .../components/VisualizationWrapper.js | 16 +++-- 28 files changed, 266 insertions(+), 135 deletions(-) rename src/{group/groupUtils.ts => sharedData/sharedDataUtils.js} (61%) diff --git a/package-lock.json b/package-lock.json index 0d4333e14..a6873cfc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "slate-history": "^0.100.0", "slate-hyperscript": "^0.100.0", "slate-react": "^0.100.0", - "terraso-client-shared": "github:techmatters/terraso-client-shared#5e56086", + "terraso-client-shared": "github:techmatters/terraso-client-shared#d577669", "use-debounce": "^9.0.4", "uuid": "^9.0.1", "web-vitals": "^3.5.0", @@ -27391,20 +27391,20 @@ }, "node_modules/terraso-backend": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/techmatters/terraso-backend.git#4929e5f4599cebc2f107873dd0075885c4df5e46" + "resolved": "git+ssh://git@github.com/techmatters/terraso-backend.git#3d323b5d204a8a7d33579cde27d4498c5b307ecb" }, "node_modules/terraso-client-shared": { "version": "0.1.0", - "resolved": "git+ssh://git@github.com/techmatters/terraso-client-shared.git#b86117e3735b2d8b2f85ffdb68547eb9738b9f77", - "integrity": "sha512-33236wvHVdvHmd6R0icNzt1PWtVhqZq8nkRguDfhq5bYrH8H3MCKf1umcsJQHJ+Y4csYw6VlFSHKrIzbayIrZw==", + "resolved": "git+ssh://git@github.com/techmatters/terraso-client-shared.git#8ca7559da49aafeaaf78ccccfa54e2aaf89dd1b4", + "integrity": "sha512-ZOa8+yzrfIn+nwdlhBMxDS9p6OTqfAodvCgj3wJvSifKsUyoq056+H7mGmKmcjJjrLJOzPYNrv4X4BhcnwvfcQ==", "dependencies": { - "@reduxjs/toolkit": "^1.9.6", + "@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#4929e5f", - "uuid": "^9.0.0" + "react-redux": "^8.1.3", + "terraso-backend": "github:techmatters/terraso-backend#3d323b5", + "uuid": "^9.0.1" } }, "node_modules/terser": { diff --git a/package.json b/package.json index d508b240b..7449624c0 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "slate-history": "^0.100.0", "slate-hyperscript": "^0.100.0", "slate-react": "^0.100.0", - "terraso-client-shared": "github:techmatters/terraso-client-shared#5e56086", + "terraso-client-shared": "github:techmatters/terraso-client-shared#d577669", "use-debounce": "^9.0.4", "uuid": "^9.0.1", "web-vitals": "^3.5.0", diff --git a/src/group/components/GroupSharedDataUpload.js b/src/group/components/GroupSharedDataUpload.js index 29e78d8e8..baff96190 100644 --- a/src/group/components/GroupSharedDataUpload.js +++ b/src/group/components/GroupSharedDataUpload.js @@ -79,9 +79,12 @@ const GroupSharedDataUpload = () => { - + diff --git a/src/group/components/GroupSharedDataUploadFiles.test.js b/src/group/components/GroupSharedDataUploadFiles.test.js index da4338ad9..9e408f23f 100644 --- a/src/group/components/GroupSharedDataUploadFiles.test.js +++ b/src/group/components/GroupSharedDataUploadFiles.test.js @@ -215,6 +215,10 @@ test('GroupSharedDataUpload: Complete Success', async () => { await waitFor(() => expect(uploadButton).not.toHaveAttribute('disabled')); await act(async () => fireEvent.click(uploadButton)); expect(navigate.mock.calls[0]).toEqual(['/groups/slug-1']); + + const saveCall = terrasoApi.request.mock.calls[0]; + expect(saveCall[0].body.get('target_type')).toBe('group'); + expect(saveCall[0].body.get('target_slug')).toBe('slug-1'); }); test('GroupSharedDataUpload: PDF Success', async () => { diff --git a/src/group/components/GroupView.test.js b/src/group/components/GroupView.test.js index a02762582..95d74ea69 100644 --- a/src/group/components/GroupView.test.js +++ b/src/group/components/GroupView.test.js @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { render, screen } from 'tests/utils'; +import { render, screen, within } from 'tests/utils'; import React from 'react'; import { useParams } from 'react-router-dom'; import * as terrasoApi from 'terraso-client-shared/terrasoApi/api'; @@ -98,6 +98,29 @@ test('GroupView: Display data', async () => { }, })), }; + const accountMembership = { + id: 'user-id', + userRole: 'MEMBER', + membershipStatus: 'APPROVED', + }; + const sharedResources = { + edges: Array(6) + .fill(0) + .map((item, index) => ({ + node: { + source: { + id: `de-${index}`, + createdAt: '2022-05-20T16:25:21.536679+00:00', + name: `Data Entry ${index}`, + createdBy: { id: 'user-id', firstName: 'First', lastName: 'Last' }, + description: `Description ${index}`, + size: 3456, + entryType: 'FILE', + visualizations: { edges: [] }, + }, + }, + })), + }; terrasoApi.requestGraphQL.mockReturnValue( Promise.resolve({ groups: { @@ -109,6 +132,8 @@ test('GroupView: Display data', async () => { website: 'https://www.group.org', email: 'email@email.com', memberships, + accountMembership, + sharedResources, }, }, ], @@ -135,6 +160,17 @@ test('GroupView: Display data', async () => { ).toBeInTheDocument(); expect(screen.getByText(/\+2/i)).toBeInTheDocument(); expect( - screen.getByRole('button', { name: 'Join Group' }) + screen.getByRole('button', { name: 'Leave: Group name' }) + ).toBeInTheDocument(); + + // Shared Data + const sharedDataRegion = within( + screen.getByRole('region', { name: 'Shared files and Links' }) + ); + expect( + sharedDataRegion.getByRole('heading', { name: 'Shared files and Links' }) ).toBeInTheDocument(); + const entriesList = within(sharedDataRegion.getByRole('list')); + const items = entriesList.getAllByRole('listitem'); + expect(items.length).toBe(6); }); diff --git a/src/group/groupFragments.ts b/src/group/groupFragments.ts index 66d7e6cec..1a5723fb1 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 { edges { node { - ...dataEntry - ...dataEntryVisualizations + source { + ... on DataEntryNode { + ...dataEntry + ...dataEntryVisualizations + } + } } } } diff --git a/src/group/groupService.ts b/src/group/groupService.ts index 19bd1f1e9..a5f1ce769 100644 --- a/src/group/groupService.ts +++ b/src/group/groupService.ts @@ -23,6 +23,8 @@ import { import * as terrasoApi from 'terraso-client-shared/terrasoApi/api'; import { graphql } from 'terrasoApi/shared/graphqlSchema'; +import { extractDataEntries } from 'sharedData/sharedDataUtils'; + import type { Group } from './groupSlice'; export const fetchGroupToUpdate = (slug: string) => { @@ -54,18 +56,22 @@ export const fetchGroupToView = async (slug: string) => { ...groupMembersInfo ...groupMembersPending ...accountMembership + ...groupDataEntries } } } } `); return terrasoApi - .requestGraphQL(query, { slug }) + .requestGraphQL(query, { + slug, + }) .then(resp => resp.groups?.edges.at(0)?.node || Promise.reject('not_found')) .then(group => ({ ..._.omit(['memberships', 'membershipsCount'], group), membersInfo: extractMembersInfo(group), accountMembership: extractAccountMembership(group), + dataEntries: extractDataEntries(group), })); }; diff --git a/src/group/groupSlice.ts b/src/group/groupSlice.ts index 2b5f2402a..33ad9e79f 100644 --- a/src/group/groupSlice.ts +++ b/src/group/groupSlice.ts @@ -25,6 +25,7 @@ import type { Message } from 'terraso-client-shared/notifications/notificationsS import { createAsyncThunk } from 'terraso-client-shared/store/utils'; import * as groupService from 'group/groupService'; +import { setList } from 'sharedData/sharedDataSlice'; export type Group = { slug: string; @@ -45,6 +46,7 @@ export const fetchGroupView = createAsyncThunk( async (slug: string, user, { dispatch }) => { const group = await groupService.fetchGroupToView(slug); dispatch(setMemberships(getMemberships([group]))); + dispatch(setList(group.dataEntries)); return group; } ); diff --git a/src/landscape/components/LandscapeSharedDataUpload.js b/src/landscape/components/LandscapeSharedDataUpload.js index 243032a0a..f7f56d2f9 100644 --- a/src/landscape/components/LandscapeSharedDataUpload.js +++ b/src/landscape/components/LandscapeSharedDataUpload.js @@ -15,7 +15,6 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ import React, { useCallback, useEffect, useMemo } from 'react'; -import _ from 'lodash/fp'; import { usePermissionRedirect } from 'permissions'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; @@ -85,11 +84,14 @@ const LandscapeSharedDataUpload = () => { name: landscape.name, })} /> - + diff --git a/src/landscape/components/LandscapeSharedDataUploadLinks.test.js b/src/landscape/components/LandscapeSharedDataUploadLinks.test.js index bdb6f8c6b..9f20b87d8 100644 --- a/src/landscape/components/LandscapeSharedDataUploadLinks.test.js +++ b/src/landscape/components/LandscapeSharedDataUploadLinks.test.js @@ -255,4 +255,17 @@ test('LandscapeSharedDataUpload: Complete Success', async () => { search: 'scrollTo=shared-data-card-title', }, ]); + expect(terrasoApi.requestGraphQL).toHaveBeenCalledTimes(3); + const saveCall = terrasoApi.requestGraphQL.mock.calls[2]; + expect(saveCall[1]).toStrictEqual({ + input: { + description: '', + entryType: 'link', + targetType: 'landscape', + targetSlug: 'slug-1', + name: 'name 1', + resourceType: 'link', + url: 'https://test1.com', + }, + }); }); diff --git a/src/landscape/components/LandscapeSharedDataVisualization.js b/src/landscape/components/LandscapeSharedDataVisualization.js index 918bfe184..bdafc4a46 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 41e3443e4..7f56934fc 100644 --- a/src/landscape/components/LandscapeSharedDataVisualization.test.js +++ b/src/landscape/components/LandscapeSharedDataVisualization.test.js @@ -256,4 +256,13 @@ test('LandscapeSharedDataVisualization: Display visualization', async () => { expect(domElement.querySelector('p').textContent).toEqual( 'Custom Label: val11' ); + + // Validate api call input to fetch visualization config + // It should contain the owner slug and type + 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 232ea2cc3..47d095ad1 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/components/LandscapeView.test.js b/src/landscape/components/LandscapeView.test.js index a1434ad39..0ca7feef0 100644 --- a/src/landscape/components/LandscapeView.test.js +++ b/src/landscape/components/LandscapeView.test.js @@ -108,19 +108,21 @@ const baseViewTest = async (userRole = 'MEMBER') => { membershipStatus: 'APPROVED', }; - const dataEntries = { + const sharedResources = { edges: Array(6) .fill(0) .map((item, index) => ({ node: { - id: `de-${index}`, - createdAt: '2022-05-20T16:25:21.536679+00:00', - name: `Data Entry ${index}`, - createdBy: { id: 'user-id', firstName: 'First', lastName: 'Last' }, - description: `Description ${index}`, - size: 3456, - entryType: 'FILE', - visualizations: { edges: [] }, + source: { + id: `de-${index}`, + createdAt: '2022-05-20T16:25:21.536679+00:00', + name: `Data Entry ${index}`, + createdBy: { id: 'user-id', firstName: 'First', lastName: 'Last' }, + description: `Description ${index}`, + size: 3456, + entryType: 'FILE', + visualizations: { edges: [] }, + }, }, })), }; @@ -141,6 +143,7 @@ const baseViewTest = async (userRole = 'MEMBER') => { accountMembership, membershipsCount: 6, }, + sharedResources, }, }, ], @@ -161,17 +164,7 @@ const baseViewTest = async (userRole = 'MEMBER') => { memberships, accountMembership, }, - }, - }, - ], - }, - }) - .mockResolvedValueOnce({ - groups: { - edges: [ - { - node: { - dataEntries, + sharedResources, }, }, ], @@ -310,7 +303,7 @@ test('LandscapeView: Update Shared Data', async () => { }) ) ); - const saveCall = terrasoApi.requestGraphQL.mock.calls[3]; + const saveCall = terrasoApi.requestGraphQL.mock.calls[2]; expect(saveCall[1].input).toEqual({ id: 'de-3', diff --git a/src/landscape/landscapeFragments.js b/src/landscape/landscapeFragments.js index c9d292f57..a998e7232 100644 --- a/src/landscape/landscapeFragments.js +++ b/src/landscape/landscapeFragments.js @@ -116,3 +116,20 @@ export const defaultGroupWithMembersSample = /* GraphQL */ ` } } `; + +export const landscapeDataEntries = /* GraphQL */ ` + fragment landscapeDataEntries on LandscapeNode { + sharedResources { + edges { + node { + source { + ... on DataEntryNode { + ...dataEntry + ...dataEntryVisualizations + } + } + } + } + } + } +`; diff --git a/src/landscape/landscapeService.js b/src/landscape/landscapeService.js index f79784227..246a85149 100644 --- a/src/landscape/landscapeService.js +++ b/src/landscape/landscapeService.js @@ -24,6 +24,7 @@ import { graphql } from 'terrasoApi/shared/graphqlSchema'; import { countryNameForCode } from 'common/countries'; import * as gisService from 'gis/gisService'; +import { extractDataEntries } from 'sharedData/sharedDataUtils'; import { extractTerms } from 'taxonomies/taxonomiesUtils'; import { ALL_PARTNERSHIP_STATUS } from './landscapeConstants'; @@ -149,6 +150,7 @@ export const fetchLandscapeToView = slug => { ...landscapeFields ...landscapePartnershipField ...defaultGroupWithMembersSample + ...landscapeDataEntries areaPolygon } } @@ -156,7 +158,9 @@ export const fetchLandscapeToView = slug => { } `); return terrasoApi - .requestGraphQL(query, { slug }) + .requestGraphQL(query, { + slug, + }) .then(_.get('landscapes.edges[0].node')) .then(landscape => landscape || Promise.reject('not_found')) .then(landscape => ({ @@ -170,6 +174,7 @@ export const fetchLandscapeToView = slug => { : null, partnershipStatus: ALL_PARTNERSHIP_STATUS[landscape.partnershipStatus], partnership: extractPartnership(landscape), + dataEntries: extractDataEntries(landscape), })) .then(landscape => { if (landscape.areaPolygon || !landscape.location) { diff --git a/src/landscape/landscapeService.test.ts b/src/landscape/landscapeService.test.ts index 692a4675e..e62cae446 100644 --- a/src/landscape/landscapeService.test.ts +++ b/src/landscape/landscapeService.test.ts @@ -43,6 +43,7 @@ test('LandscapeService: Fetch landscape with missing fields', async () => { description: 'Landscape description', website: 'https://www.landscape.org', areaPolygon: null, + dataEntries: undefined, defaultGroup: { membersInfo: { accountMembership: undefined, diff --git a/src/landscape/landscapeSlice.js b/src/landscape/landscapeSlice.js index f8450a958..8240d04c6 100644 --- a/src/landscape/landscapeSlice.js +++ b/src/landscape/landscapeSlice.js @@ -21,6 +21,7 @@ import * as membershipsUtils from 'terraso-client-shared/memberships/memberships import { createAsyncThunk } from 'terraso-client-shared/store/utils'; import * as landscapeService from 'landscape/landscapeService'; +import { setList } from 'sharedData/sharedDataSlice'; const initialState = { list: { @@ -82,6 +83,7 @@ export const fetchLandscapeView = createAsyncThunk( currentUser ); dispatch(setMemberships(getMemberships([landscape]))); + dispatch(setList(landscape.dataEntries)); return landscape; } ); diff --git a/src/permissions/rules.js b/src/permissions/rules.js index fea53fc96..5e7ca0cb5 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/components/SharedDataCard.js b/src/sharedData/components/SharedDataCard.js index 865db59f6..9161a3401 100644 --- a/src/sharedData/components/SharedDataCard.js +++ b/src/sharedData/components/SharedDataCard.js @@ -14,12 +14,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import React, { useCallback } from 'react'; +import React from 'react'; import _ from 'lodash/fp'; import { usePermission } from 'permissions'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; -import { useFetchData } from 'terraso-client-shared/store/utils'; import { Button, Card, @@ -33,7 +32,6 @@ import { import List from 'common/components/List'; import { ScrollTo } from 'navigation/scrollTo'; import { useGroupContext } from 'group/groupContext'; -import { fetchGroupSharedData } from 'sharedData/sharedDataSlice'; import SharedDataEntryFile from './SharedDataEntryFile'; import SharedDataEntryLink from './SharedDataEntryLink'; @@ -48,13 +46,6 @@ const SharedFilesCard = props => { const { data: sharedFiles, fetching } = useSelector(_.get('sharedData.list')); const hasFiles = !_.isEmpty(sharedFiles); - useFetchData( - useCallback( - () => (allowed ? fetchGroupSharedData({ slug: group.slug }) : null), - [group.slug, allowed] - ) - ); - if (!allowed) { return null; } diff --git a/src/sharedData/components/SharedDataUpload/index.js b/src/sharedData/components/SharedDataUpload/index.js index e98b74b64..94de342e7 100644 --- a/src/sharedData/components/SharedDataUpload/index.js +++ b/src/sharedData/components/SharedDataUpload/index.js @@ -51,7 +51,7 @@ const SharedDataUpload = props => { const { trackEvent } = useAnalytics(); const { owner, entityType } = useGroupContext(); - const { groupSlug, onCancel, onCompleteSuccess } = props; + const { targetInput, onCancel, onCompleteSuccess } = props; const [section, setSection] = useState('files'); @@ -103,10 +103,10 @@ const SharedDataUpload = props => { const onSave = useCallback(() => { setShowSummary(false); const linksPromises = toUpload.links.map(link => - dispatch(addSharedDataLink({ groupSlug, link })) + dispatch(addSharedDataLink({ ...targetInput, link })) ); const filesPromises = toUpload.files.map(file => - dispatch(uploadSharedDataFile({ groupSlug, file })) + dispatch(uploadSharedDataFile({ ...targetInput, file })) ); const allPromises = [...filesPromises, ...linksPromises]; @@ -134,7 +134,7 @@ const SharedDataUpload = props => { }); setShowSummary(true); }); - }, [entityType, owner.slug, toUpload, groupSlug, dispatch, trackEvent]); + }, [entityType, owner.slug, toUpload, targetInput, dispatch, trackEvent]); const hasBlockingErrors = useMemo( () => diff --git a/src/sharedData/sharedDataService.js b/src/sharedData/sharedDataService.js index 5de08bca0..a68f39c9e 100644 --- a/src/sharedData/sharedDataService.js +++ b/src/sharedData/sharedDataService.js @@ -18,18 +18,23 @@ import _ from 'lodash/fp'; import * as terrasoApi from 'terraso-client-shared/terrasoApi/api'; import { graphql } from 'terrasoApi/shared/graphqlSchema'; -import { extractDataEntry, extractGroupDataEntries } from 'group/groupUtils'; +import { extractDataEntry } from './sharedDataUtils'; import { SHARED_DATA_ACCEPTED_EXTENSIONS } from 'config'; const ALL_RESOURCE_TYPES = [...SHARED_DATA_ACCEPTED_EXTENSIONS, 'link']; -export const uploadSharedDataFile = async ({ groupSlug, file }) => { +export const uploadSharedDataFile = async ({ + targetType, + targetSlug, + file, +}) => { const path = '/shared-data/upload/'; const body = new FormData(); const filename = `${file.name}${file.resourceType}`; - body.append('groups', groupSlug); + body.append('target_type', targetType); + body.append('target_slug', targetSlug); body.append('name', file.name); if (file.description) { body.append('description', file.description); @@ -61,7 +66,7 @@ export const deleteSharedData = ({ dataEntry }) => { }); }; -export const addSharedDataLink = ({ groupSlug, link }) => { +export const addSharedDataLink = ({ targetType, targetSlug, link }) => { const query = graphql(` mutation addDataEntry($input: DataEntryAddMutationInput!) { addDataEntry(input: $input) { @@ -80,7 +85,8 @@ export const addSharedDataLink = ({ groupSlug, link }) => { ..._.pick(['name', 'url', 'description'], link), entryType: 'link', resourceType: 'link', - groupSlug, + targetType, + targetSlug, }, }) .then(_.get('addDataEntry.dataEntry')); @@ -112,31 +118,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 => extractGroupDataEntries(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, }) => { @@ -161,7 +181,8 @@ export const addVisualizationConfig = ({ title, configuration, dataEntryId: selectedFile.id, - groupId: group.id, + ownerId, + ownerType, }, }) .then(response => ({ @@ -185,11 +206,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_Groups_Slug: $groupSlug + dataEntry_SharedResources_Target_Slug: $ownerSlug + dataEntry_SharedResources_TargetContentType: $ownerType slug: $configSlug ) { edges { @@ -204,7 +234,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 3c1870146..3f697702d 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( @@ -124,6 +124,13 @@ const sharedDataSlice = createSlice({ ...state, processing: _.omit(action.payload, state.processing), }), + setList: (state, action) => ({ + ...state, + list: { + data: action.payload, + fetching: false, + }, + }), }, extraReducers: builder => { @@ -219,7 +226,7 @@ const sharedDataSlice = createSlice({ ) ); - builder.addCase(fetchGroupSharedData.pending, (state, action) => ({ + builder.addCase(fetchSharedData.pending, (state, action) => ({ ...state, list: { fetching: true, @@ -227,7 +234,7 @@ const sharedDataSlice = createSlice({ }, })); - builder.addCase(fetchGroupSharedData.fulfilled, (state, action) => ({ + builder.addCase(fetchSharedData.fulfilled, (state, action) => ({ ...state, list: { fetching: false, @@ -278,6 +285,7 @@ const sharedDataSlice = createSlice({ }, }); -export const { resetUploads, resetProcessing } = sharedDataSlice.actions; +export const { resetUploads, resetProcessing, setList } = + sharedDataSlice.actions; export default sharedDataSlice.reducer; diff --git a/src/group/groupUtils.ts b/src/sharedData/sharedDataUtils.js similarity index 61% rename from src/group/groupUtils.ts rename to src/sharedData/sharedDataUtils.js index d533b627a..0cd316907 100644 --- a/src/group/groupUtils.ts +++ b/src/sharedData/sharedDataUtils.js @@ -14,18 +14,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { - DataEntriesFragment, - DataEntryFragment, - DataEntryVisualizationsFragment, -} from 'terrasoApi/shared/graphqlSchema/graphql'; -export const extractDataEntry = ( - dataEntry: DataEntryFragment & DataEntryVisualizationsFragment -) => ({ +export const extractDataEntry = dataEntry => ({ ...dataEntry, - visualizations: dataEntry.visualizations?.edges?.map(edge => edge.node), + visualizations: dataEntry?.visualizations?.edges?.map(edge => edge.node), }); -export const extractGroupDataEntries = (group: DataEntriesFragment) => - group.dataEntries.edges.map(edge => extractDataEntry(edge.node)); +export const extractDataEntries = parent => + parent.sharedResources?.edges + .map(edge => edge.node.source) + .map(dataEntry => extractDataEntry(dataEntry)); diff --git a/src/sharedData/visualization/components/VisualizationConfigForm/PreviewStep.js b/src/sharedData/visualization/components/VisualizationConfigForm/PreviewStep.js index a7415f963..3e5fa35cf 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 e49bfe7d2..0de9732a6 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 1971787c9..05ad6f619 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 => {