Skip to content
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

feat: self hosted onboarding #5057

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
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
82 changes: 82 additions & 0 deletions frontend/common/services/useBuildVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Req } from 'common/types/requests'
import { service } from 'common/service'

import { Version } from 'common/types/responses'
import Project from 'common/project'
import { StoreStateType } from 'common/store'

export const buildVersionService = service
.enhanceEndpoints({ addTagTypes: ['BuildVersion'] })
.injectEndpoints({
endpoints: (builder) => ({
getBuildVersion: builder.query<Version, Req['getBuildVersion']>({
providesTags: () => [{ id: 'BuildVersion', type: 'BuildVersion' }],
queryFn: async (args, _, _2, baseQuery) => {
// Fire both requests concurrently
const [frontendRes, backendRes] = await Promise.all([
baseQuery('/version').then(
(res: { data?: Version['frontend'] }) => {
if (res.data) {
return res.data
}
return {}
},
),
baseQuery(`${Project.api.replace('api/v1/', '')}version`).then(
(res: { data: Version['backend'] }) => res.data,
),
])

if (backendRes.error) {
return { error: backendRes.error }
}

const frontend = (frontendRes.data || {}) as Version['frontend']
const backend =
(backendRes.data as Version['backend']) ||
({} as Version['backend'])

const tag = backend?.image_tag || 'Unknown'
const backend_sha = backend?.ci_commit_sha || 'Unknown'
const frontend_sha = frontend?.ci_commit_sha || 'Unknown'

const result: Version = {
backend,
backend_sha,
frontend,
frontend_sha,
tag,
}

return { data: result }
},
}),
// END OF ENDPOINTS
}),
})

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

export const selectBuildVersion = (state: StoreStateType) => state.buildVersion

export const {
useGetBuildVersionQuery,
// END OF EXPORTS
} = buildVersionService

/* Usage examples:
const { data, isLoading } = useGetBuildVersionQuery({ id: 2 }, {}) //get hook
const [createBuildVersion, { isLoading, data, isSuccess }] = useCreateBuildVersionMutation() //create hook
buildVersionService.endpoints.getBuildVersion.select({id: 2})(store.getState()) //access data from any function
*/
46 changes: 46 additions & 0 deletions frontend/common/services/useOnboarding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'

export const onboardingService = service
.enhanceEndpoints({ addTagTypes: ['Onboarding'] })
.injectEndpoints({
endpoints: (builder) => ({
createOnboarding: builder.mutation<
Res['onboarding'],
Req['createOnboarding']
>({
invalidatesTags: [{ id: 'LIST', type: 'Onboarding' }],
query: (query: Req['createOnboarding']) => ({
body: query,
method: 'POST',
url: `onboarding`,
}),
}),
// END OF ENDPOINTS
}),
})

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

export const {
useCreateOnboardingMutation,
// END OF EXPORTS
} = onboardingService

/* Usage examples:
const { data, isLoading } = useGetOnboardingQuery({ id: 2 }, {}) //get hook
const [createOnboarding, { isLoading, data, isSuccess }] = useCreateOnboardingMutation() //create hook
onboardingService.endpoints.getOnboarding.select({id: 2})(store.getState()) //access data from any function
*/
21 changes: 21 additions & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ export type CreateVersionFeatureState = {
sha: string
featureState: FeatureState
}

export type LoginRequest = {
email: string
password: string
}
export type RegisterRequest = {
email: string
first_name: string
last_name: string
marketing_consent_given: boolean
password: string
}
export type Req = {
getSegments: PagedRequest<{
q?: string
Expand Down Expand Up @@ -573,5 +585,14 @@ export type Req = {
identity: string
projectId: string
}>
createOnboarding: {
first_name: string
last_name: string
email: string
password: string
contact_consent_given: boolean
organisation_name: string
}
getBuildVersion: {}
// END OF TYPES
}
19 changes: 17 additions & 2 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,23 @@ export type HealthProvider = {
webhook_url: number
}

export type Version = {
tag: string
backend_sha: string
frontend_sha: string
frontend: {
ci_commit_sha?: string
image_tag?: string
}
backend: {
ci_commit_sha: string
image_tag: string
has_email_provider: boolean
is_enterprise: boolean
is_saas: boolean
}
}

export type Webhook = {
id: number
url: string
Expand Down Expand Up @@ -795,7 +812,5 @@ export type Res = {
metadata_xml: string
}
samlAttributeMapping: PagedResponse<SAMLAttributeMapping>
identitySegments: PagedResponse<Segment>
organisationWebhooks: PagedResponse<Webhook>
// END OF TYPES
}
7 changes: 7 additions & 0 deletions frontend/common/utils/isFreeEmailDomain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import freeEmailDomains from 'free-email-domains'

export default function (value: string | null | undefined) {
if (!value) return false
const domain = value?.split('@')?.[1]
return freeEmailDomains.includes(domain)
}
18 changes: 11 additions & 7 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import WarningMessage from 'components/WarningMessage'
import Constants from 'common/constants'
import { defaultFlags } from 'common/stores/default-flags'
import Color from 'color'
import { selectBuildVersion } from 'common/services/useBuildVersion'
import { getStore } from 'common/store'

const semver = require('semver')

Expand Down Expand Up @@ -194,6 +196,7 @@ const Utils = Object.assign({}, require('./base/_utils'), {
flagsmithFeatureExists(flag: string) {
return Object.prototype.hasOwnProperty.call(flagsmith.getAllFlags(), flag)
},

getApproveChangeRequestPermission() {
return 'APPROVE_CHANGE_REQUEST'
},
Expand Down Expand Up @@ -291,7 +294,6 @@ const Utils = Object.assign({}, require('./base/_utils'), {
}
return 'identities'
},

getIntegrationData() {
return Utils.getFlagsmithJSONValue(
'integration_data',
Expand All @@ -306,6 +308,7 @@ const Utils = Object.assign({}, require('./base/_utils'), {
}
return false
},

getManageFeaturePermission(isChangeRequest: boolean) {
if (isChangeRequest) {
return 'CREATE_CHANGE_REQUEST'
Expand Down Expand Up @@ -367,6 +370,7 @@ const Utils = Object.assign({}, require('./base/_utils'), {
}
return planNames.free
},

getPlanPermission: (plan: string, feature: PaidFeature) => {
const planName = Utils.getPlanName(plan)
if (!plan || planName === planNames.free) {
Expand Down Expand Up @@ -406,7 +410,6 @@ const Utils = Object.assign({}, require('./base/_utils'), {
getProjectColour(index: number) {
return Constants.projectColors[index % (Constants.projectColors.length - 1)]
},

getRequiredPlan: (feature: PaidFeature) => {
let plan
switch (feature) {
Expand Down Expand Up @@ -560,9 +563,9 @@ const Utils = Object.assign({}, require('./base/_utils'), {
getViewIdentitiesPermission() {
return 'VIEW_IDENTITIES'
},
hasEmailProvider: () =>
global.flagsmithVersion?.backend?.has_email_provider ?? false,
isEnterpriseImage: () => global.flagsmithVersion?.backend.is_enterprise,
//todo: Remove when migrating to RTK
isEnterpriseImage: () =>
selectBuildVersion(getStore().getState())?.backend.is_enterprise,
isMigrating() {
const model = ProjectStore.model as null | ProjectType
if (
Expand All @@ -573,10 +576,11 @@ const Utils = Object.assign({}, require('./base/_utils'), {
}
return false
},
isSaas: () => global.flagsmithVersion?.backend?.is_saas,
isSaas: () => selectBuildVersion(getStore().getState())?.backend?.is_saas,
isValidNumber(value: any) {
return /^-?\d*\.?\d+$/.test(`${value}`)
},

isValidURL(value: any) {
const regex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i
return regex.test(value)
Expand Down Expand Up @@ -613,7 +617,6 @@ const Utils = Object.assign({}, require('./base/_utils'), {
zE('messenger', 'open')
}
},

removeElementFromArray(array: any[], index: number) {
return array.slice(0, index).concat(array.slice(index + 1))
},
Expand All @@ -626,6 +629,7 @@ const Utils = Object.assign({}, require('./base/_utils'), {
</Tooltip>
)
},

sanitiseDiffString: (value: FlagsmithValue) => {
if (value === undefined || value == null) {
return ''
Expand Down
2 changes: 0 additions & 2 deletions frontend/web/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import ButterBar from './ButterBar'
import AccountSettingsPage from './pages/AccountSettingsPage'
import Headway from './Headway'
import ProjectStore from 'common/stores/project-store'
import getBuildVersion from 'project/getBuildVersion'
import { Provider } from 'react-redux'
import { getStore } from 'common/store'
import { resolveAuthFlow } from '@datadog/ui-extensions-sdk'
Expand Down Expand Up @@ -104,7 +103,6 @@ const App = class extends Component {
})
amplitude.add(sessionReplayTracking)
}
getBuildVersion()
this.state.projectId = this.getProjectId(this.props)
if (this.state.projectId) {
AppActions.getProject(this.state.projectId)
Expand Down
19 changes: 4 additions & 15 deletions frontend/web/components/BuildVersion.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import { FC, useEffect, useState } from 'react'
import getBuildVersion from 'project/getBuildVersion'
import { FC } from 'react'
import { IonIcon } from '@ionic/react'
import { pricetag } from 'ionicons/icons'
import { useGetBuildVersionQuery } from 'common/services/useBuildVersion'

type BuildVersionType = {}
type Version = {
tag: string
backend_sha: string
frontend_sha: string
}
const BuildVersion: FC<BuildVersionType> = ({}) => {
const [version, setVersion] = useState<Version>()

useEffect(() => {
getBuildVersion().then((version: Version) => {
setVersion(version)
})
}, [])
const BuildVersion: FC<BuildVersionType> = ({}) => {
const { data: version } = useGetBuildVersionQuery({})
return (
<div className='text-muted position-fixed bottom-0 p-2 fs-caption'>
{version?.tag !== 'Unknown' && (
<Tooltip
html
title={
<span>
<span className='icon'>
Expand Down
24 changes: 0 additions & 24 deletions frontend/web/components/GoogleButton.js

This file was deleted.

Loading
Loading