Skip to content

Commit

Permalink
feat: restrict versioning by days (#4547)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyle-ssg authored Nov 5, 2024
1 parent af86090 commit dad864a
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 111 deletions.
12 changes: 12 additions & 0 deletions frontend/common/services/useFeatureVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'components/diff/diff-utils'
import { getSegments } from './useSegment'
import { getFeatureStates } from './useFeatureState'
import moment from 'moment'

const transformFeatureStates = (featureStates: TypedFeatureState[]) =>
featureStates?.map((v) => ({
Expand Down Expand Up @@ -322,6 +323,17 @@ export const {
// END OF EXPORTS
} = featureVersionService

export function isVersionOverLimit(
versionLimitDays: number | null | undefined,
date: string | undefined,
) {
if (!versionLimitDays) {
return false
}
const days = moment().diff(moment(date), 'days') + 1
return !!versionLimitDays && days > versionLimitDays
}

/* Usage examples:
const { data, isLoading } = useGetFeatureVersionQuery({ id: 2 }, {}) //get hook
const [createFeatureVersion, { isLoading, data, isSuccess }] = useCreateFeatureVersionMutation() //create hook
Expand Down
2 changes: 1 addition & 1 deletion frontend/common/services/useSubscriptionMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const getSubscriptionMetadataService = service
.injectEndpoints({
endpoints: (builder) => ({
getSubscriptionMetadata: builder.query<
Res['getSubscriptionMetadata'],
Res['subscriptionMetadata'],
Req['getSubscriptionMetadata']
>({
providesTags: (res) => [
Expand Down
7 changes: 2 additions & 5 deletions frontend/common/stores/organisation-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Constants from 'common/constants'
import { projectService } from 'common/services/useProject'
import { getStore } from 'common/store'
import sortBy from 'lodash/sortBy'
import { getSubscriptionMetadata } from 'common/services/useSubscriptionMetadata'

const Dispatcher = require('../dispatcher/dispatcher')
const BaseStore = require('./base/_store')
Expand Down Expand Up @@ -139,11 +140,7 @@ const controller = {
AccountStore.getOrganisationRole(id) === 'ADMIN'
? [
data.get(`${Project.api}organisations/${id}/invites/`),
data
.get(
`${Project.api}organisations/${id}/get-subscription-metadata/`,
)
.catch(() => null),
getSubscriptionMetadata(getStore(), { id }),
]
: [],
),
Expand Down
4 changes: 3 additions & 1 deletion frontend/common/types/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,8 @@ export type InviteLink = {

export type SubscriptionMeta = {
max_seats: number | null
audit_log_visibility_days: number | null
feature_history_visibility_days: number | null
max_api_calls: number | null
max_projects: number | null
payment_source: string | null
Expand Down Expand Up @@ -700,7 +702,7 @@ export type Res = {
rolesPermissionUsers: PagedResponse<RolePermissionUser>
createRolePermissionGroup: RolePermissionGroup
rolePermissionGroup: PagedResponse<RolePermissionGroup>
getSubscriptionMetadata: { id: string; max_api_calls: number }
subscriptionMetadata: SubscriptionMeta
environment: Environment
metadataModelFieldList: PagedResponse<MetadataModelField>
metadataModelField: MetadataModelField
Expand Down
6 changes: 5 additions & 1 deletion frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export type PaidFeature =
| 'FORCE_2FA'
| '4_EYES'
| 'STALE_FLAGS'
| 'VERSIONING'
| 'VERSIONING_DAYS'
| 'AUDIT_DAYS'
| 'AUTO_SEATS'
| 'METADATA'
| 'REALTIME'
Expand Down Expand Up @@ -304,6 +305,9 @@ const Utils = Object.assign({}, require('./base/_utils'), {
},
getNextPlan: (skipFree?: boolean) => {
const currentPlan = Utils.getPlanName(AccountStore.getActiveOrgPlan())
if (currentPlan !== planNames.enterprise && !Utils.isSaas()) {
return planNames.enterprise
}
switch (currentPlan) {
case planNames.free: {
return skipFree ? planNames.startup : planNames.scaleUp
Expand Down
182 changes: 119 additions & 63 deletions frontend/web/components/AuditLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import PanelSearch from './PanelSearch'
import JSONReference from './JSONReference'
import moment from 'moment'
import PlanBasedBanner from './PlanBasedAccess'
import { useGetSubscriptionMetadataQuery } from 'common/services/useSubscriptionMetadata'
import AccountStore from 'common/stores/account-store'
import { isVersionOverLimit } from 'common/services/useFeatureVersion'
import Tooltip from './Tooltip'

type AuditLogType = {
environmentId: string
Expand All @@ -36,6 +40,9 @@ const AuditLog: FC<AuditLogType> = (props) => {
setPage(1)
},
)
const { data: subscriptionMeta } = useGetSubscriptionMetadataQuery({
id: AccountStore.getOrganisation()?.id,
})
const [environments, setEnvironments] = useState(props.environmentId)

useEffect(() => {
Expand Down Expand Up @@ -96,17 +103,49 @@ const AuditLog: FC<AuditLogType> = (props) => {
})
const colour = index === -1 ? 0 : index
let link: ReactNode = null
if (
related_object_uuid &&
related_object_type === 'EF_VERSION' &&
environment
) {
const date = moment(created_date)
const isVersionEvent =
related_object_uuid && related_object_type === 'EF_VERSION' && environment
const versionLimitDays = subscriptionMeta?.feature_history_visibility_days

const isOverLimit = isVersionEvent
? isVersionOverLimit(versionLimitDays, created_date)
: false
const VersionButton = (
<Button disabled={isOverLimit} theme='text'>
View version
</Button>
)

if (isVersionEvent) {
link = (
<Link
to={`/project/${project.id}/environment/${environment.api_key}/history/${related_object_uuid}/`}
<Tooltip
title={
<div className='d-flex gap-2'>
{isOverLimit ? (
VersionButton
) : (
<Link
to={`/project/${project.id}/environment/${environment.api_key}/history/${related_object_uuid}/`}
>
{VersionButton}
</Link>
)}
<PlanBasedBanner
force={isOverLimit}
feature={'VERSIONING_DAYS'}
theme={'badge'}
/>
</div>
}
>
<Button theme='text'>View version</Button>
</Link>
{isOverLimit
? `<div>
Unlock your feature's entire history.<br/>Currently limited to${' '}
<strong>${versionLimitDays} days</strong>.
</div>`
: ''}
</Tooltip>
)
}
const inner = (
Expand All @@ -115,7 +154,7 @@ const AuditLog: FC<AuditLogType> = (props) => {
className='table-column px-3 fs-small ln-sm'
style={{ width: widths[0] }}
>
{moment(created_date).format('Do MMM YYYY HH:mma')}
{date.format('Do MMM YYYY HH:mma')}
</div>
<div
className='table-column fs-small ln-sm'
Expand Down Expand Up @@ -164,63 +203,80 @@ const AuditLog: FC<AuditLogType> = (props) => {
}

const { env: envFilter } = Utils.fromParam()
const auditLimitDays = subscriptionMeta?.audit_log_visibility_days

return (
<PanelSearch
id='messages-list'
title='Log entries'
isLoading={isFetching}
className='no-pad'
items={projectAuditLog?.results}
filter={envFilter}
search={searchInput}
searchPanel={props.searchPanel}
onChange={(e: InputEvent) => {
setSearchInput(Utils.safeParseEventValue(e))
}}
paging={{
...(projectAuditLog || {}),
page,
pageSize: props.pageSize,
}}
nextPage={() => {
setPage(page + 1)
}}
prevPage={() => {
setPage(page - 1)
}}
goToPage={(page: number) => {
setPage(page)
}}
filterRow={() => true}
renderRow={renderRow}
header={
<Row className='table-header'>
<div className='table-column px-3' style={{ width: widths[0] }}>
Date
</div>
<div className='table-column' style={{ width: widths[1] }}>
User
</div>
<div className='table-column' style={{ width: widths[2] }}>
Environment
</div>
<Flex className='table-column'>Content</Flex>
</Row>
}
renderFooter={() => (
<JSONReference
className='mt-4 ml-2'
title={'Audit'}
json={projectAuditLog?.results}
<>
{!!auditLimitDays && (
<PlanBasedBanner
className='mb-4'
force
feature={'AUDIT_DAYS'}
title={
<div>
Unlock your audit log history. Currently limited to{' '}
<strong>{auditLimitDays} days</strong>.
</div>
}
theme={'description'}
/>
)}
renderNoResults={
<FormGroup className='text-center'>
You have no log messages for your project.
</FormGroup>
}
/>
<PanelSearch
id='messages-list'
title='Log entries'
isLoading={isFetching}
className='no-pad'
items={projectAuditLog?.results}
filter={envFilter}
search={searchInput}
searchPanel={props.searchPanel}
onChange={(e: InputEvent) => {
setSearchInput(Utils.safeParseEventValue(e))
}}
paging={{
...(projectAuditLog || {}),
page,
pageSize: props.pageSize,
}}
nextPage={() => {
setPage(page + 1)
}}
prevPage={() => {
setPage(page - 1)
}}
goToPage={(page: number) => {
setPage(page)
}}
filterRow={() => true}
renderRow={renderRow}
header={
<Row className='table-header'>
<div className='table-column px-3' style={{ width: widths[0] }}>
Date
</div>
<div className='table-column' style={{ width: widths[1] }}>
User
</div>
<div className='table-column' style={{ width: widths[2] }}>
Environment
</div>
<Flex className='table-column'>Content</Flex>
</Row>
}
renderFooter={() => (
<JSONReference
className='mt-4 ml-2'
title={'Audit'}
json={projectAuditLog?.results}
/>
)}
renderNoResults={
<FormGroup className='text-center'>
You have no log messages for your project.
</FormGroup>
}
/>
</>
)
}

Expand Down
40 changes: 24 additions & 16 deletions frontend/web/components/FeatureHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import InlineModal from './InlineModal'
import TableFilterItem from './tables/TableFilterItem'
import moment from 'moment'
import DateList from './DateList'
import PlanBasedBanner from './PlanBasedAccess'
import classNames from 'classnames'
import PlanBasedBanner from './PlanBasedAccess'
import { useGetSubscriptionMetadataQuery } from 'common/services/useSubscriptionMetadata'

const widths = [250, 150]
type FeatureHistoryPageType = {
Expand All @@ -28,6 +29,12 @@ const FeatureHistory: FC<FeatureHistoryPageType> = ({
projectId,
}) => {
const [open, setOpen] = useState(false)
const { data: subscriptionMeta } = useGetSubscriptionMetadataQuery({
id: AccountStore.getOrganisation()?.id,
})
const versionLimitDays = subscriptionMeta?.feature_history_visibility_days

// @ts-ignore
const { data: users } = useGetUsersQuery({
organisationId: AccountStore.getOrganisation().id,
})
Expand All @@ -45,7 +52,6 @@ const FeatureHistory: FC<FeatureHistoryPageType> = ({
const live = data?.results?.[0]
const [compareToLive, setCompareToLive] = useState(false)
const [diff, setDiff] = useState<null | string>(null)
const versionLimit = 3
return (
<div>
<h5>Change History</h5>
Expand All @@ -54,30 +60,32 @@ const FeatureHistory: FC<FeatureHistoryPageType> = ({
segment overrides.
</div>
<div className='mt-4'>
{/*{!!versionLimit && (*/}
{/* <PlanBasedBanner*/}
{/* className='mb-4'*/}
{/* force*/}
{/* feature={'VERSIONING'}*/}
{/* theme={'page'}*/}
{/* />*/}
{/*)}*/}
{!!versionLimitDays && (
<PlanBasedBanner
className='mb-4'
force
feature={'VERSIONING_DAYS'}
title={
<div>
Unlock your feature's entire history. Currently limited to{' '}
<strong>{versionLimitDays} days</strong>.
</div>
}
theme={'page'}
/>
)}
<DateList<TFeatureVersion>
items={data}
isLoading={isLoading}
nextPage={() => setPage(page + 1)}
prevPage={() => setPage(page + 1)}
goToPage={setPage}
dateProperty={'live_from'}
renderRow={(v: TFeatureVersion, i: number) => {
const isOverLimit = false
const user = users?.find((user) => v.published_by === user.id)

return (
<Row
className={classNames('list-item py-2 mh-auto', {
'blur no-pointer': isOverLimit,
})}
>
<Row className={'list-item py-2 mh-auto'}>
<div
className={classNames('flex-fill', {
'overflow-hidden': !open,
Expand Down
Loading

0 comments on commit dad864a

Please sign in to comment.