• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alkem-io / client-web / #9048

11 Oct 2024 01:42PM UTC coverage: 5.943%. First build
#9048

Pull #7022

travis-ci

Pull Request #7022: [v0.74.0] Roles API + Unauthenticated Explore page

202 of 10241 branches covered (1.97%)

Branch coverage included in aggregate %.

63 of 431 new or added lines in 60 files covered. (14.62%)

1468 of 17861 relevant lines covered (8.22%)

0.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/topLevelPages/topLevelSpaces/SpaceExplorerContainer.tsx
1
import { Dispatch, useEffect, useMemo, useState } from 'react';
2
import {
3
  useMySpacesExplorerPageQuery,
4
  useSpaceExplorerAllSpacesQuery,
5
  useSpaceExplorerMemberSpacesQuery,
6
  useSpaceExplorerSubspacesLazyQuery,
7
  useSpaceExplorerSearchQuery,
8
} from '@/core/apollo/generated/apollo-hooks';
9
import { useCurrentUserContext } from '@/domain/community/userCurrent/useCurrentUserContext';
10
import {
11
  SearchCategory,
12
  AuthorizationPrivilege,
13
  CommunityMembershipStatus,
14
  SearchResultType,
15
  SpaceExplorerSubspacesQuery,
16
  SpaceExplorerSearchSpaceFragment,
17
} from '@/core/apollo/generated/graphql-schema';
18
import { TypedSearchResult } from '@/main/search/SearchView';
19
import { ITEMS_LIMIT, SpacesExplorerMembershipFilter, SpaceWithParent } from './SpaceExplorerView';
20
import usePaginatedQuery from '@/domain/shared/pagination/usePaginatedQuery';
21
import { SimpleContainerProps } from '@/core/container/SimpleContainer';
22
import { uniqBy } from 'lodash';
23

24
export interface SpacesExplorerContainerEntities {
25
  spaces: SpaceWithParent[] | undefined;
26
  searchTerms: string[];
27
  membershipFilter: SpacesExplorerMembershipFilter;
28
  onMembershipFilterChange: Dispatch<SpacesExplorerMembershipFilter>;
29
  fetchMore: () => Promise<void>;
30
  loading: boolean;
31
  hasMore: boolean | undefined;
32
  authenticated: boolean;
33
  setSearchTerms: React.Dispatch<React.SetStateAction<string[]>>;
34
  loadingSearchResults: boolean | null;
35
}
36

37
interface SpaceExplorerContainerProps extends SimpleContainerProps<SpacesExplorerContainerEntities> {}
38

39
const SpaceExplorerContainer = ({ children }: SpaceExplorerContainerProps) => {
40
  const { userModel, isAuthenticated, loading: loadingUser } = useCurrentUserContext();
41

42
  const [searchTerms, setSearchTerms] = useState<string[]>([]);
43
  const [membershipFilter, setMembershipFilter] = useState(SpacesExplorerMembershipFilter.All);
44

45
  const shouldSearch = searchTerms.length > 0;
46

47
  // PRIVATE: Spaces if the user is logged in
×
48
  const { data: spaceMembershipsData, loading: loadingUserData } = useMySpacesExplorerPageQuery({
×
49
    skip: !userModel?.id || shouldSearch || membershipFilter !== SpacesExplorerMembershipFilter.Member,
50
  });
×
51

52
  const mySpaceIds = spaceMembershipsData?.me.spaceMembershipsFlat.map(space => space.id);
×
53

54
  const { data: spacesExplorerData, loading: isLoadingMemberSpaces } = useSpaceExplorerMemberSpacesQuery({
55
    variables: {
×
56
      spaceIDs: mySpaceIds,
×
57
    },
58
    skip: !mySpaceIds || shouldSearch,
59
  });
×
60

61
  // PUBLIC: Search for spaces and subspaces
×
62
  const { data: rawSearchResults, loading: loadingSearchResults } = useSpaceExplorerSearchQuery({
63
    variables: {
64
      searchData: {
65
        terms: searchTerms,
×
66
        tagsetNames: ['skills', 'keywords'],
67
        filters: [
68
          {
69
            category: SearchCategory.Spaces,
×
70
            size: ITEMS_LIMIT,
71
          },
72
        ],
73
      },
74
    },
75
    fetchPolicy: 'no-cache',
76
    skip: !shouldSearch,
77
  });
78

79
  const usesPagination =
80
    (membershipFilter === SpacesExplorerMembershipFilter.All ||
81
      membershipFilter === SpacesExplorerMembershipFilter.Public) &&
×
82
    !shouldSearch;
83

84
  const {
85
    data: spacesData,
86
    fetchMore: fetchMoreSpaces,
87
    loading: isLoadingSpaces,
88
    hasMore: hasMoreSpaces,
89
  } = usePaginatedQuery({
90
    useQuery: useSpaceExplorerAllSpacesQuery,
91
    pageSize: ITEMS_LIMIT,
×
92
    variables: {},
93
    getPageInfo: result => result.spacesPaginated.pageInfo,
94
    options: {
95
      skip: !usesPagination,
×
96
    },
97
  });
98

99
  const fetchMore = usesPagination ? fetchMoreSpaces : () => Promise.resolve();
100

101
  const hasMore = usesPagination ? hasMoreSpaces : false;
×
102

103
  const fetchedSpaces = useMemo(() => {
×
104
    switch (membershipFilter) {
105
      case SpacesExplorerMembershipFilter.All:
×
106
      case SpacesExplorerMembershipFilter.Public:
×
107
        return spacesData?.spacesPaginated.spaces;
108
      case SpacesExplorerMembershipFilter.Member:
109
        return spacesExplorerData?.spaces;
×
110
    }
111
  }, [spacesExplorerData, spacesData, membershipFilter]);
×
112

113
  // Spaces which allow this user read their subspaces:
114
  const readableSpacesIds = useMemo(
115
    () =>
116
      fetchedSpaces
×
117
        ?.filter(space => space.authorization?.myPrivileges?.includes(AuthorizationPrivilege.Read))
118
        .map(space => space.id),
×
119
    [fetchedSpaces]
×
120
  );
×
121

122
  const [fetchSubspaces, { loading: loadingSubspaces }] = useSpaceExplorerSubspacesLazyQuery();
123
  const [fetchedSpacesWithSubspaces, setFetchedSpacesWithSubspaces] = useState<SpaceExplorerSubspacesQuery['spaces']>(
124
    []
×
125
  );
×
126

127
  useEffect(() => {
128
    const fetchSubspacesData = async () => {
129
      // Fetch the spaces that are not already fetched
×
130
      const missingSubspaces = readableSpacesIds?.filter(
×
131
        spaceId => !fetchedSpacesWithSubspaces.some(space => space.id === spaceId)
132
      );
×
133
      if (!missingSubspaces || missingSubspaces.length === 0) {
×
134
        return;
135
      }
×
136
      const { data } = await fetchSubspaces({
×
137
        variables: {
138
          IDs: missingSubspaces,
×
139
        },
140
      });
141
      setFetchedSpacesWithSubspaces(prev => uniqBy([...prev, ...(data?.spaces ?? [])], space => space.id));
142
    };
143
    fetchSubspacesData();
×
144
  }, [readableSpacesIds]);
145

×
146
  const loading =
147
    isLoadingSpaces ||
148
    isLoadingMemberSpaces ||
149
    loadingSearchResults ||
×
150
    loadingUserData ||
151
    loadingUser ||
152
    loadingSubspaces;
153

154
  const flattenedSpaces = useMemo<SpaceWithParent[] | undefined>(() => {
155
    if (shouldSearch) {
156
      return rawSearchResults?.search?.spaceResults?.results.map(result => {
×
157
        const entry = result as TypedSearchResult<SearchResultType.Space, SpaceExplorerSearchSpaceFragment>;
×
158

×
159
        if (entry.type === SearchResultType.Space || entry.type === SearchResultType.Subspace) {
×
160
          return {
161
            ...entry.space,
×
162
            parent: undefined,
×
163
            matchedTerms: entry.terms,
164
          };
165
        }
166

167
        return null as never;
168
      });
169
    }
×
170

171
    return fetchedSpaces?.flatMap<SpaceWithParent>(space => {
172
      const subspaces = fetchedSpacesWithSubspaces?.find(
173
        spaceWithSubspaces => spaceWithSubspaces.id === space.id
×
174
      )?.subspaces;
×
175

×
176
      if (!subspaces || subspaces.length === 0) {
177
        return space;
178
      }
×
179
      return [
×
180
        space,
181
        ...subspaces.map(subspace => ({
×
182
          ...subspace,
183
          parent: space,
×
184
        })),
185
      ];
186
    });
187
  }, [spacesExplorerData, spacesData, membershipFilter, rawSearchResults, fetchedSpacesWithSubspaces]);
188

189
  const filteredSpaces = useMemo(() => {
190
    if (membershipFilter === SpacesExplorerMembershipFilter.Member) {
191
      if (!shouldSearch) {
×
192
        // already filtered by the query
×
193
        return flattenedSpaces;
×
194
      }
195
      return flattenedSpaces?.filter(
×
196
        space => space.about.membership?.myMembershipStatus === CommunityMembershipStatus.Member
NEW
197
      );
×
NEW
198
    }
×
199
    if (membershipFilter === SpacesExplorerMembershipFilter.Public) {
200
      return flattenedSpaces?.filter(space => space.about.isContentPublic);
201
    }
×
202
    return flattenedSpaces;
×
203
  }, [flattenedSpaces, membershipFilter, shouldSearch]);
204

×
205
  const provided = {
206
    spaces: filteredSpaces,
207
    authenticated: isAuthenticated,
×
208
    searchTerms,
209
    membershipFilter,
×
210
    onMembershipFilterChange: setMembershipFilter,
211
    fetchMore,
212
    loading,
213
    hasMore,
214
    setSearchTerms,
215
    loadingSearchResults,
216
  };
217

218
  return <>{children(provided)}</>;
219
};
220

221
export default SpaceExplorerContainer;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc