Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 5 additions & 28 deletions frontend/common/services/useIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import transformCorePaging from 'common/transformCorePaging'
import transformCursorPaging from 'common/transformCursorPaging'
import Utils from 'common/utils/utils'

const getIdentityEndpoint = (environmentId: string, isEdge: boolean) => {
Expand Down Expand Up @@ -87,34 +88,10 @@ export const identityService = service
}
},
transformResponse(baseQueryReturnValue: Res['identities'], meta, req) {
const {
isEdge,
page = 1,
page_size = 10,
pageType,
pages: _pages,
} = req
if (isEdge) {
// For edge, we create our own paging
let pages = _pages ? _pages.concat([]) : []
const next_evaluated_key = baseQueryReturnValue.last_evaluated_key
if (pageType === 'NEXT') {
pages.push(next_evaluated_key)
} else if (pageType === 'PREVIOUS') {
pages.unshift()
} else {
pages = []
}

if (req.isEdge) {
// For edge, identities are cursor-paginated.
return {
...baseQueryReturnValue,
next:
baseQueryReturnValue.results.length < page_size
? undefined
: '1',
pages,
//
previous: pages.length ? '1' : undefined,
...transformCursorPaging(req, baseQueryReturnValue),
results: baseQueryReturnValue.results?.map((v) => {
if (v.id) {
return v
Expand All @@ -123,7 +100,7 @@ export const identityService = service
...v,
id: v.identity_uuid,
}
}), //
}),
}
}
return transformCorePaging(req, baseQueryReturnValue)
Expand Down
59 changes: 59 additions & 0 deletions frontend/common/services/useSegmentMembers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'
import Utils from 'common/utils/utils'
import transformCursorPaging from 'common/transformCursorPaging'

export const segmentMembersService = service
.enhanceEndpoints({ addTagTypes: ['SegmentMembers'] })
.injectEndpoints({
endpoints: (builder) => ({
getSegmentMembers: builder.query<
Res['segmentMembers'],
Req['getSegmentMembers']
>({
providesTags: (_res, _err, arg) => [
{ id: arg.id, type: 'SegmentMembers' },
],
query: ({ environment, id, page_size = 10, pages, projectId, q }) => {
// The cursor for the current page is the last entry on the stack the
// component has stepped through; the first page sends no cursor.
const cursor = pages?.[pages.length - 1]
return {
url: `projects/${projectId}/segments/${id}/members/?${Utils.toParam(
{ cursor, environment, limit: page_size, q },
)}`,
}
},
transformResponse: (
res: Res['segmentMembers'],
_meta,
req: Req['getSegmentMembers'],
) => transformCursorPaging(req, res),
}),
// END OF ENDPOINTS
}),
})

export async function getSegmentMembers(
store: any,
data: Req['getSegmentMembers'],
options?: Parameters<
typeof segmentMembersService.endpoints.getSegmentMembers.initiate
>[1],
) {
return store.dispatch(
segmentMembersService.endpoints.getSegmentMembers.initiate(data, options),
)
}
// END OF FUNCTION_EXPORTS

export const {
useGetSegmentMembersQuery,
// END OF EXPORTS
} = segmentMembersService

/* Usage examples:
const { data, isLoading } = useGetSegmentMembersQuery({ id: 2, projectId: 1, environment: 3 }, {}) //get hook
segmentMembersService.endpoints.getSegmentMembers.select({ id: 2, projectId: 1, environment: 3 })(store.getState()) //access data from any function
*/
27 changes: 27 additions & 0 deletions frontend/common/transformCursorPaging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PagedResponse } from './types/responses'

export type CursorPagedRequest = {
page_size?: number
// Stack of cursors the calling component has stepped through to reach the
// current page. The last entry is the cursor for the current page; empty or
// undefined means the first page.
pages?: (string | undefined)[]
}

// Normalises a cursor/keyset-paginated response into the `next`/`previous`
// sentinels that <Paging> understands. The cursor stack (`pages`) is owned by
// the calling component and threaded through the request; this transform only
// derives prev/next availability:
// - `next`: a full page implies there may be more rows.
// - `previous`: a non-empty cursor stack means we are past the first page.
export default function transformCursorPaging<T, R extends PagedResponse<T>>(
req: CursorPagedRequest,
res: R,
): R {
const pageSize = req.page_size ?? 10
return {
...res,
next: res.results.length < pageSize ? undefined : '1',
previous: req.pages?.length ? '1' : undefined,
}
}
6 changes: 6 additions & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ export type Req = {
tag: Omit<Tag, 'id' | 'project' | 'type' | 'is_system_tag' | 'is_permanent'>
}
getSegment: { projectId: number; id: number }
getSegmentMembers: PagedRequest<{
projectId: number
id: number
environment: number
pages?: (string | undefined)[]
}>
updateAccount: Account
deleteAccount: {
current_password: string
Expand Down
10 changes: 10 additions & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ export type SegmentMembership = {
count: number
last_synced_at: string
}
export type SegmentMember = {
identifier: string
identity_key: string
traits: Record<string, FlagsmithValue> | null
}
export type SegmentMembersResponse = PagedResponse<SegmentMember> & {
// Pass as `cursor` to fetch the next page; null when there are no more rows.
next_cursor: string | null
}
export type Segment = {
id: number
rules: SegmentRule[]
Expand Down Expand Up @@ -1260,6 +1269,7 @@ export type WarehouseConnection = {
export type Res = {
segments: PagedResponse<Segment>
segment: Segment
segmentMembers: SegmentMembersResponse
auditLogs: PagedResponse<AuditLogItem>
organisationLicence: {}
organisation: Organisation
Expand Down
14 changes: 13 additions & 1 deletion frontend/web/components/modals/CreateSegment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ type CreateSegmentType = {
onComplete?: (segment: Segment) => void
readOnly?: boolean
segment?: Segment
membersEnabled: boolean
}
type CreateSegmentError = {
status: number
Expand Down Expand Up @@ -103,6 +104,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
identities,
identitiesLoading,
identity,
membersEnabled,
onCancel,
onComplete,
page,
Expand Down Expand Up @@ -597,6 +599,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
<div className='my-4'>
<CreateSegmentUsersTabContent
projectId={projectId}
segmentId={segment.id}
environmentId={environmentId}
setEnvironmentId={setEnvironmentId}
identitiesLoading={identitiesLoading}
Expand All @@ -607,6 +610,7 @@ const CreateSegment: FC<CreateSegmentType> = ({
searchInput={searchInput}
setSearchInput={setSearchInput}
memberships={segment.membership_counts}
membersEnabled={membersEnabled}
/>
</div>
</TabItem>
Expand Down Expand Up @@ -740,6 +744,13 @@ const LoadingCreateSegment: FC<LoadingCreateSegmentType> = (props) => {

const isEdge = Utils.getIsEdge()

// When membership inspection is enabled, the Identities tab uses the
// dedicated segment members endpoint, so the legacy identities list (and its
// request) is not needed.
const membersEnabled = Utils.getFlagsmithHasFeature(
'segment_membership_inspection',
)

const { data: identities, isLoading: identitiesLoading } =
useGetIdentitiesQuery(
{
Expand All @@ -752,7 +763,7 @@ const LoadingCreateSegment: FC<LoadingCreateSegmentType> = (props) => {
q: search,
},
{
skip: !environmentId,
skip: !environmentId || membersEnabled,
},
)

Expand All @@ -772,6 +783,7 @@ const LoadingCreateSegment: FC<LoadingCreateSegmentType> = (props) => {
page={page}
environmentId={environmentId}
setEnvironmentId={setEnvironmentId}
membersEnabled={membersEnabled}
/>
)
}
Expand Down
Loading
Loading