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: Adds Licensing upload tab #4934

Merged
merged 1 commit into from
Jan 17, 2025
Merged
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
50 changes: 50 additions & 0 deletions frontend/common/services/useOrganisationLicensing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Res } from 'common/types/responses'
import { Req } from 'common/types/requests'
import { service } from 'common/service'

export const organisationLicensingService = service
.enhanceEndpoints({ addTagTypes: ['OrganisationLicensing'] })
.injectEndpoints({
endpoints: (builder) => ({
uploadOrganisationLicence: builder.mutation<
Res['organisationLicence'],
Req['uploadOrganisationLicence']
>({
query: (query: Req['uploadOrganisationLicence']) => {
const formData = new FormData()
formData.append('licence_signature', query.body.licence_signature)
formData.append('licence', query.body.licence)
return {
body: formData,
method: 'PUT',
url: `organisations/${query.id}/licence`,
}
},
}),
// END OF ENDPOINTS
}),
})

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

export const {
useUploadOrganisationLicenceMutation,
// END OF EXPORTS
} = organisationLicensingService
7 changes: 7 additions & 0 deletions frontend/common/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ export type Req = {
environments?: string
}>
getOrganisations: {}
uploadOrganisationLicence: {
id: number
body: {
licence_signature: File
licence: File
}
}
getProjects: {
organisationId: string
}
Expand Down
1 change: 1 addition & 0 deletions frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ export type Res = {
segments: PagedResponse<Segment>
segment: Segment
auditLogs: PagedResponse<AuditLogItem>
organisationLicence: {}
organisations: PagedResponse<Organisation>
projects: ProjectSummary[]
project: Project
Expand Down
7 changes: 2 additions & 5 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,7 @@ const Utils = Object.assign({}, require('./base/_utils'), {
if (plan && plan.includes('start-up')) {
return planNames.startup
}
if (
global.flagsmithVersion?.backend.is_enterprise ||
(plan && plan.includes('enterprise'))
) {
if (Utils.isEnterpriseImage() || (plan && plan.includes('enterprise'))) {
return planNames.enterprise
}
return planNames.free
Expand Down Expand Up @@ -555,6 +552,7 @@ const Utils = Object.assign({}, require('./base/_utils'), {
getViewIdentitiesPermission() {
return 'VIEW_IDENTITIES'
},
isEnterpriseImage: () => global.flagsmithVersion?.backend.is_enterprise,
isMigrating() {
const model = ProjectStore.model as null | ProjectType
if (
Expand All @@ -566,7 +564,6 @@ const Utils = Object.assign({}, require('./base/_utils'), {
return false
},
isSaas: () => global.flagsmithVersion?.backend?.is_saas,

isValidNumber(value: any) {
return /^-?\d*\.?\d+$/.test(`${value}`)
},
Expand Down
110 changes: 110 additions & 0 deletions frontend/web/components/LicensingTabContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useUploadOrganisationLicenceMutation } from 'common/services/useOrganisationLicensing'
import React, { useEffect, useRef, useState } from 'react'
import Button from './base/forms/Button'
import Utils from 'common/utils/utils'

type LicensingTabContentProps = {
organisationId: number
}

const LicensingTabContent: React.FC<LicensingTabContentProps> = ({
organisationId,
}) => {
const [uploadOrganisationLicence, { error, isLoading, isSuccess }] =
useUploadOrganisationLicenceMutation()

const [licence, setLicence] = useState<File | null>(null)
const [licenceSignature, setLicenceSignature] = useState<File | null>(null)

const licenceInputRef = useRef<HTMLInputElement>(null)
const licenceSignatureInputRef = useRef<HTMLInputElement>(null)

useEffect(() => {
if (isSuccess) {
toast('Licence uploaded successfully')
}

if (!isSuccess && error?.data) {
toast(
Array.isArray(error?.data)
? error?.data[0]
: 'Upload was not successful',
'danger',
)
}
}, [isSuccess, error])

const handleUpload = () => {
if (!licence || !licenceSignature) return
uploadOrganisationLicence({
body: { licence, licence_signature: licenceSignature },
id: organisationId,
})
}

return (
<div className='mt-4'>
<h5 className='mb-5'>Upload Licensing Files</h5>
<form
className='upload-licensing-tab'
onSubmit={(e) => {
Utils.preventDefault(e)
handleUpload()
}}
>
<FormGroup>
<input
type='file'
ref={licenceInputRef}
style={{ display: 'none' }}
onChange={() =>
setLicence(licenceInputRef.current?.files?.[0] ?? null)
}
/>
<input
type='file'
ref={licenceSignatureInputRef}
style={{ display: 'none' }}
onChange={() =>
setLicenceSignature(
licenceSignatureInputRef.current?.files?.[0] ?? null,
)
}
/>
<div className='flex-row'>
<Button onClick={() => licenceInputRef.current?.click()}>
Select Licence File
</Button>
{!!licence?.name && (
<p className='mt-auto mb-auto ml-2 fs-small lh-sm'>
{licence.name}
</p>
)}
</div>
<div className='flex-row mt-4'>
<Button onClick={() => licenceSignatureInputRef.current?.click()}>
Select Signature File
</Button>
{!!licenceSignature?.name && (
<p className='mt-auto mb-auto ml-2 fs-small lh-sm'>
{licenceSignature.name}
</p>
)}
</div>
<div className='text-right'>
<Button
type='submit'
data-test='create-feature-btn'
id='create-feature-btn'
disabled={!licence || !licenceSignature}
>
{isLoading ? 'Uploading' : 'Upload Licensing Files'}
</Button>
</div>
</FormGroup>
</form>
</div>
)
}

export default LicensingTabContent
14 changes: 13 additions & 1 deletion frontend/web/components/pages/OrganisationSettingsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ import PageTitle from 'components/PageTitle'
import SamlTab from 'components/SamlTab'
import Setting from 'components/Setting'
import AccountProvider from 'common/providers/AccountProvider'
import LicensingTabContent from 'components/LicensingTabContent'
import Utils from 'common/utils/utils'

const SettingsTab = {
'Billing': 'billing',
'General': 'general',
'Keys': 'keys',
'Licensing': 'licensing',
'SAML': 'saml',
'Usage': 'usage',
'Webhooks': 'webhooks',
Expand Down Expand Up @@ -228,7 +231,7 @@ const OrganisationSettingsPage = class extends Component {
const { chargebee_email } = subscriptionMeta || {}

const displayedTabs = []

const isEnterprise = Utils.isEnterpriseImage()
if (
AccountStore.getUser() &&
AccountStore.getOrganisationRole() === 'ADMIN'
Expand All @@ -237,6 +240,7 @@ const OrganisationSettingsPage = class extends Component {
...[
SettingsTab.General,
paymentsEnabled && !isAWS ? SettingsTab.Billing : null,
isEnterprise ? SettingsTab.Licensing : null,
SettingsTab.Keys,
SettingsTab.Webhooks,
SettingsTab.SAML,
Expand Down Expand Up @@ -463,6 +467,14 @@ const OrganisationSettingsPage = class extends Component {
</TabItem>
)}

{displayedTabs.includes(SettingsTab.Licensing) && (
<TabItem tabLabel='Licensing'>
<LicensingTabContent
organisationId={organisation.id}
/>
</TabItem>
)}

{displayedTabs.includes(SettingsTab.Keys) && (
<TabItem tabLabel='API Keys'>
<AdminAPIKeys organisationId={organisation.id} />
Expand Down
Loading