Merge pull request #3 from harness/ui-template-improved
Simplify routing system
This commit is contained in:
commit
7f5a783416
|
@ -6,7 +6,6 @@ import { FocusStyleManager } from '@blueprintjs/core'
|
|||
import { tooltipDictionary } from '@harness/ng-tooltip'
|
||||
import AppErrorBoundary from 'framework/AppErrorBoundary/AppErrorBoundary'
|
||||
import { AppContextProvider } from 'AppContext'
|
||||
import { setBaseRouteInfo } from 'RouteUtils'
|
||||
import type { AppProps } from 'AppProps'
|
||||
import { buildResfulReactRequestOptions, handle401 } from 'AppUtils'
|
||||
import { RouteDestinations } from 'RouteDestinations'
|
||||
|
@ -34,7 +33,6 @@ const App: React.FC<AppProps> = props => {
|
|||
const getRequestOptions = useCallback((): Partial<RequestInit> => {
|
||||
return buildResfulReactRequestOptions(hooks.useGetToken?.() || apiToken || 'default')
|
||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
setBaseRouteInfo(accountId, baseRoutePath)
|
||||
|
||||
useEffect(() => {
|
||||
languageLoader(lang).then(setStrings)
|
||||
|
|
|
@ -2,7 +2,6 @@ import type React from 'react'
|
|||
import type * as History from 'history'
|
||||
import type { PermissionOptionsMenuButtonProps } from 'components/Permissions/PermissionsOptionsMenuButton'
|
||||
import type { LangLocale } from './framework/strings/languageLoader'
|
||||
import type { FeatureFlagMap, GitFiltersProps } from './utils/GovernanceUtils'
|
||||
|
||||
/**
|
||||
* AppProps defines an interface for host (parent) and
|
||||
|
@ -72,7 +71,6 @@ export interface AppPathProps {
|
|||
export interface AppPropsHook {
|
||||
usePermission(permissionRequest: any, deps?: Array<any>): Array<boolean>
|
||||
useGetSchemaYaml(params: any, deps?: Array<any>): Record<string, any>
|
||||
useFeatureFlags(): FeatureFlagMap
|
||||
useGetToken(): any
|
||||
useAppStore(): any
|
||||
useGitSyncStore(): any
|
||||
|
@ -91,7 +89,6 @@ export interface AppPropsComponent {
|
|||
NGBreadcrumbs: React.FC
|
||||
RbacButton: React.FC
|
||||
RbacOptionsMenuButton: React.FC<PermissionOptionsMenuButtonProps>
|
||||
GitFilters: React.FC<GitFiltersProps>
|
||||
GitSyncStoreProvider: React.FC
|
||||
GitContextForm: React.FC<any>
|
||||
NavigationCheck: React.FC<{
|
||||
|
|
|
@ -1,105 +1,19 @@
|
|||
import { toRouteURL } from 'RouteUtils'
|
||||
import type { AppPathProps } from 'AppProps'
|
||||
|
||||
export enum RoutePath {
|
||||
// Standalone-only paths
|
||||
SIGNIN = '/signin',
|
||||
SIGNUP = '/signup',
|
||||
REGISTER = '/register',
|
||||
POLICY_DASHBOARD = '/dashboard',
|
||||
POLICY_LISTING = '/policies',
|
||||
POLICY_NEW = '/policies/new',
|
||||
POLICY_VIEW = '/policies/view/:policyIdentifier',
|
||||
//POLICY_EDIT = '/policies/edit/:policyIdentifier',
|
||||
POLICY_EDIT = '/policies/edit/:policyIdentifier/:repo?/:branch?',
|
||||
POLICY_SETS_LISTING = '/policy-sets',
|
||||
POLICY_SETS_DETAIL = '/policy-sets/:policySetIdentifier',
|
||||
POLICY_EVALUATIONS_LISTING = '/policy-evaluations',
|
||||
POLICY_EVALUATION_DETAIL = '/policy-evaluations/:evaluationId'
|
||||
|
||||
// Shared paths
|
||||
DASHBOARD = '/dashboard',
|
||||
TEST_PAGE1 = '/test1',
|
||||
TEST_PAGE2 = '/test2'
|
||||
}
|
||||
|
||||
export default {
|
||||
toSignIn: (): string => toRouteURL(RoutePath.SIGNIN),
|
||||
toSignUp: (): string => toRouteURL(RoutePath.SIGNUP),
|
||||
toRegister: (): string => toRouteURL(RoutePath.REGISTER),
|
||||
toPolicyDashboard: (): string => toRouteURL(RoutePath.POLICY_DASHBOARD),
|
||||
toPolicyListing: (): string => toRouteURL(RoutePath.POLICY_LISTING),
|
||||
toPolicyNew: (): string => toRouteURL(RoutePath.POLICY_NEW),
|
||||
toPolicyView: ({ policyIdentifier }: Required<Pick<AppPathProps, 'policyIdentifier'>>): string =>
|
||||
toRouteURL(RoutePath.POLICY_VIEW, { policyIdentifier }),
|
||||
toPolicyEdit: ({ policyIdentifier }: Required<Pick<AppPathProps, 'policyIdentifier'>>): string =>
|
||||
toRouteURL(RoutePath.POLICY_EDIT, { policyIdentifier }),
|
||||
toPolicySets: (): string => toRouteURL(RoutePath.POLICY_SETS_LISTING),
|
||||
toPolicyEvaluations: (): string => toRouteURL(RoutePath.POLICY_EVALUATIONS_LISTING),
|
||||
toGovernancePolicyDashboard: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_DASHBOARD, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernancePolicyListing: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_LISTING, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernanceNewPolicy: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_NEW, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernanceEditPolicy: ({
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module,
|
||||
repo,
|
||||
branch
|
||||
}: RequireField<AppPathProps, 'policyIdentifier'>) =>
|
||||
toRouteURL(RoutePath.POLICY_EDIT, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module,
|
||||
repo,
|
||||
branch
|
||||
}),
|
||||
toGovernanceViewPolicy: ({
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module
|
||||
}: RequireField<AppPathProps, 'policyIdentifier'>) =>
|
||||
toRouteURL(RoutePath.POLICY_VIEW, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
policyIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernancePolicySetsListing: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_SETS_LISTING, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernancePolicySetDetail: ({ orgIdentifier, projectIdentifier, policySetIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_SETS_DETAIL, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module,
|
||||
policySetIdentifier
|
||||
}),
|
||||
toGovernanceEvaluationsListing: ({ orgIdentifier, projectIdentifier, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_EVALUATIONS_LISTING, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module
|
||||
}),
|
||||
toGovernanceEvaluationDetail: ({ orgIdentifier, projectIdentifier, evaluationId, module }: AppPathProps) =>
|
||||
toRouteURL(RoutePath.POLICY_EVALUATION_DETAIL, {
|
||||
orgIdentifier,
|
||||
projectIdentifier,
|
||||
module,
|
||||
evaluationId
|
||||
})
|
||||
toSignIn: (): string => RoutePath.SIGNIN,
|
||||
toSignUp: (): string => RoutePath.SIGNUP,
|
||||
|
||||
toDashboard: (): string => RoutePath.DASHBOARD,
|
||||
toTestPage1: (): string => RoutePath.TEST_PAGE1,
|
||||
toTestPage2: (): string => RoutePath.TEST_PAGE2
|
||||
}
|
||||
|
|
|
@ -1,65 +1,43 @@
|
|||
/* eslint-disable react/display-name */
|
||||
import React, { useCallback } from 'react'
|
||||
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'
|
||||
// import { SignInPage } from 'pages/signin/SignInPage'
|
||||
import { NotFoundPage } from 'pages/404/NotFoundPage'
|
||||
import { SignIn } from 'pages/SignIn/SignIn'
|
||||
import { Register } from 'pages/Register/Register'
|
||||
import { routePath, standaloneRoutePath } from './RouteUtils'
|
||||
import { SignUp } from 'pages/SignUp/SignUp'
|
||||
import { routePath } from './RouteUtils'
|
||||
import { RoutePath } from './RouteDefinitions'
|
||||
|
||||
export const RouteDestinations: React.FC<{ standalone: boolean }> = React.memo(({ standalone }) => {
|
||||
// console.log({ standalone, signin: routePath(standalone, RoutePath.DASHBOARD) })
|
||||
|
||||
const Destinations: React.FC = useCallback(
|
||||
() => (
|
||||
<Switch>
|
||||
{standalone && (
|
||||
<>
|
||||
<Route path={routePath(RoutePath.SIGNIN)}>
|
||||
<SignIn />
|
||||
</Route>
|
||||
<Route path={routePath(RoutePath.SIGNUP)}>
|
||||
<SignIn />
|
||||
</Route>
|
||||
<Route path={routePath(RoutePath.REGISTER)}>
|
||||
<Register />
|
||||
</Route>
|
||||
</>
|
||||
<Route path={routePath(standalone, RoutePath.SIGNIN)}>
|
||||
<SignIn />
|
||||
</Route>
|
||||
)}
|
||||
|
||||
<Route path={routePath(RoutePath.POLICY_DASHBOARD)}>
|
||||
<h1>Overview</h1>
|
||||
{standalone && (
|
||||
<Route path={routePath(standalone, RoutePath.SIGNUP)}>
|
||||
<SignUp />
|
||||
</Route>
|
||||
)}
|
||||
|
||||
<Route path={routePath(standalone, RoutePath.DASHBOARD)}>
|
||||
<h1>DASHBOARD</h1>
|
||||
</Route>
|
||||
|
||||
<Route path={routePath(RoutePath.POLICY_NEW)}>
|
||||
<h1>New</h1>
|
||||
<Route path={routePath(standalone, RoutePath.TEST_PAGE1)}>
|
||||
<h1>TEST_PAGE1</h1>
|
||||
</Route>
|
||||
|
||||
<Route path={routePath(RoutePath.POLICY_VIEW)}>
|
||||
<h1>View</h1>
|
||||
</Route>
|
||||
|
||||
<Route exact path={routePath(RoutePath.POLICY_EDIT)}>
|
||||
<h1>Edit</h1>
|
||||
</Route>
|
||||
|
||||
<Route path={routePath(RoutePath.POLICY_LISTING)}>
|
||||
<h1>Listing</h1>
|
||||
</Route>
|
||||
|
||||
<Route exact path={routePath(RoutePath.POLICY_SETS_LISTING)}>
|
||||
<h1>Listing 2</h1>
|
||||
</Route>
|
||||
|
||||
<Route path={routePath(RoutePath.POLICY_SETS_DETAIL)}>
|
||||
<h1>Detail 1</h1>
|
||||
</Route>
|
||||
|
||||
<Route path={routePath(RoutePath.POLICY_EVALUATION_DETAIL)}>
|
||||
<h1>Detail 2</h1>
|
||||
<Route path={routePath(standalone, RoutePath.TEST_PAGE2)}>
|
||||
<h1>TEST_PAGE2</h1>
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
{standalone ? <Redirect to={standaloneRoutePath(RoutePath.POLICY_DASHBOARD)} /> : <NotFoundPage />}
|
||||
{standalone ? <Redirect to={routePath(standalone, RoutePath.DASHBOARD) as string} /> : <NotFoundPage />}
|
||||
</Route>
|
||||
</Switch>
|
||||
),
|
||||
|
|
|
@ -1,61 +1,7 @@
|
|||
import { generatePath } from 'react-router-dom'
|
||||
import type { AppPathProps } from 'AppProps'
|
||||
|
||||
let baseRoutePath: string
|
||||
let accountId: string
|
||||
|
||||
export const setBaseRouteInfo = (_accountId: string, _baseRoutePath: string): void => {
|
||||
accountId = _accountId
|
||||
baseRoutePath = _baseRoutePath
|
||||
}
|
||||
|
||||
type Scope = Pick<AppPathProps, 'orgIdentifier' | 'projectIdentifier' | 'module'>
|
||||
|
||||
//
|
||||
// Note: This function needs to be in sync with NextGen UI's routeUtils' getScopeBasedRoute. When
|
||||
// it's out of sync, the URL routing scheme could be broken.
|
||||
// @see https://github.com/wings-software/nextgenui/blob/master/src/modules/10-common/utils/routeUtils.ts#L171
|
||||
//
|
||||
const getScopeBasedRouteURL = ({ path, scope = {} }: { path: string; scope?: Scope }): string => {
|
||||
const { orgIdentifier, projectIdentifier, module } = scope
|
||||
|
||||
// The Governance app is mounted in three places in Harness Platform
|
||||
// 1. Account Settings (account level governance)
|
||||
// 2. Org Details (org level governance)
|
||||
// 3. Project Settings (project level governance)
|
||||
if (module && orgIdentifier && projectIdentifier) {
|
||||
return `/account/${accountId}/${module}/orgs/${orgIdentifier}/projects/${projectIdentifier}/setup/governance${path}`
|
||||
} else if (orgIdentifier && projectIdentifier) {
|
||||
return `/account/${accountId}/home/orgs/${orgIdentifier}/projects/${projectIdentifier}/setup/governance${path}`
|
||||
} else if (orgIdentifier) {
|
||||
return `/account/${accountId}/settings/organizations/${orgIdentifier}/setup/governance${path}`
|
||||
}
|
||||
|
||||
return `/account/${accountId}/settings/governance${path}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate route paths to be used in RouteDefinitions.
|
||||
* @param path route path
|
||||
* @returns an array of proper route paths that works in both standalone and embedded modes across all levels of governance.
|
||||
*/
|
||||
export const routePath = (path: string): string[] => [
|
||||
`/account/:accountId/settings/governance${path}`,
|
||||
`/account/:accountId/settings/organizations/:orgIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(cd)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(ci)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(cf)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(sto)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
`/account/:accountId/:module(cv)/orgs/:orgIdentifier/projects/:projectIdentifier/setup/governance${path}`,
|
||||
]
|
||||
|
||||
export const standaloneRoutePath = (path: string): string => `${baseRoutePath || ''}${path}`
|
||||
|
||||
/**
|
||||
* Generate route URL to be used RouteDefinitions' default export (aka actual react-router link href)
|
||||
* @param path route path
|
||||
* @param params URL parameters
|
||||
* @returns a proper URL that works in both standalone and embedded modes.
|
||||
*/
|
||||
export const toRouteURL = (path: string, params?: AppPathProps): string =>
|
||||
generatePath(getScopeBasedRouteURL({ path, scope: params }), { ...params, accountId })
|
||||
export const routePath = (standalone: boolean, path: string): string | string[] =>
|
||||
standalone ? path : [`/account/:accountId/scm${path}`]
|
||||
|
|
|
@ -39,7 +39,7 @@ interface NameIdProps {
|
|||
export const NameId = (props: NameIdProps): JSX.Element => {
|
||||
const { getString } = useStrings()
|
||||
const { identifierProps, nameLabel = getString('name'), inputGroupProps = {} } = props
|
||||
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
||||
const newInputGroupProps = { placeholder: getString('namePlaceholder'), ...inputGroupProps }
|
||||
return (
|
||||
<FormInput.InputWithIdentifier inputLabel={nameLabel} inputGroupProps={newInputGroupProps} {...identifierProps} />
|
||||
)
|
||||
|
@ -156,7 +156,7 @@ function TagsDeprecated(props: TagsDeprecatedComponentProps): JSX.Element {
|
|||
export function NameIdDescriptionTags(props: NameIdDescriptionTagsProps): JSX.Element {
|
||||
const { getString } = useStrings()
|
||||
const { className, identifierProps, descriptionProps, formikProps, inputGroupProps = {}, tooltipProps } = props
|
||||
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
||||
const newInputGroupProps = { placeholder: getString('namePlaceholder'), ...inputGroupProps }
|
||||
return (
|
||||
<Container className={cx(css.main, className)}>
|
||||
<NameId identifierProps={identifierProps} inputGroupProps={newInputGroupProps} />
|
||||
|
@ -184,7 +184,7 @@ export function NameIdDescriptionTagsDeprecated<T>(props: NameIdDescriptionTagsD
|
|||
export function NameIdDescription(props: NameIdDescriptionProps): JSX.Element {
|
||||
const { getString } = useStrings()
|
||||
const { className, identifierProps, descriptionProps, formikProps, inputGroupProps = {} } = props
|
||||
const newInputGroupProps = { placeholder: getString('common.namePlaceholder'), ...inputGroupProps }
|
||||
const newInputGroupProps = { placeholder: getString('namePlaceholder'), ...inputGroupProps }
|
||||
|
||||
return (
|
||||
<Container className={cx(css.main, className)}>
|
||||
|
|
|
@ -3,7 +3,7 @@ import cx from 'classnames'
|
|||
import moment from 'moment'
|
||||
import { Container, Icon, Text } from '@harness/uicore'
|
||||
import { Color } from '@harness/design-system'
|
||||
import { useGetTrialInfo } from 'utils/GovernanceUtils'
|
||||
import { useGetTrialInfo } from 'utils/Utils'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import css from './TrialBanner.module.scss'
|
||||
|
||||
|
|
|
@ -3,53 +3,12 @@
|
|||
* Use the command `yarn strings` to regenerate this file.
|
||||
*/
|
||||
export interface StringsMap {
|
||||
AZ09: string
|
||||
ZA90: string
|
||||
action: string
|
||||
all: string
|
||||
apply: string
|
||||
back: string
|
||||
'banner.expired': string
|
||||
'banner.expiryCountdown': string
|
||||
cancel: string
|
||||
clearFilter: string
|
||||
'common.namePlaceholder': string
|
||||
'common.policies': string
|
||||
'common.policiesSets.created': string
|
||||
'common.policiesSets.enforced': string
|
||||
'common.policiesSets.entity': string
|
||||
'common.policiesSets.evaluationCriteria': string
|
||||
'common.policiesSets.event': string
|
||||
'common.policiesSets.newPolicyset': string
|
||||
'common.policiesSets.noPolicySet': string
|
||||
'common.policiesSets.noPolicySetDescription': string
|
||||
'common.policiesSets.noPolicySetResult': string
|
||||
'common.policiesSets.noPolicySetTitle': string
|
||||
'common.policiesSets.noPolicySets': string
|
||||
'common.policiesSets.policySetSearch': string
|
||||
'common.policiesSets.scope': string
|
||||
'common.policiesSets.stepOne.validId': string
|
||||
'common.policiesSets.stepOne.validIdRegex': string
|
||||
'common.policiesSets.stepOne.validName': string
|
||||
'common.policiesSets.table.enforced': string
|
||||
'common.policiesSets.table.entityType': string
|
||||
'common.policiesSets.table.name': string
|
||||
'common.policiesSets.updated': string
|
||||
'common.policy.evaluations': string
|
||||
'common.policy.newPolicy': string
|
||||
'common.policy.noPolicy': string
|
||||
'common.policy.noPolicyEvalResult': string
|
||||
'common.policy.noPolicyEvalResultTitle': string
|
||||
'common.policy.noPolicyResult': string
|
||||
'common.policy.noPolicyTitle': string
|
||||
'common.policy.noSelectInput': string
|
||||
'common.policy.permission.noEdit': string
|
||||
'common.policy.policySearch': string
|
||||
'common.policy.policysets': string
|
||||
'common.policy.table.createdAt': string
|
||||
'common.policy.table.lastModified': string
|
||||
'common.policy.table.name': string
|
||||
confirm: string
|
||||
continue: string
|
||||
delete: string
|
||||
description: string
|
||||
|
@ -57,83 +16,19 @@ export interface StringsMap {
|
|||
details: string
|
||||
edit: string
|
||||
email: string
|
||||
entity: string
|
||||
'evaluation.evaluatedPoliciesCount': string
|
||||
'evaluation.onePolicyEvaluated': string
|
||||
executionsText: string
|
||||
existingAccount: string
|
||||
failed: string
|
||||
fileOverwrite: string
|
||||
finish: string
|
||||
'governance.clearOutput': string
|
||||
'governance.deleteConfirmation': string
|
||||
'governance.deleteDone': string
|
||||
'governance.deletePolicySetConfirmation': string
|
||||
'governance.deletePolicySetDone': string
|
||||
'governance.deletePolicySetTitle': string
|
||||
'governance.deleteTitle': string
|
||||
'governance.editPolicy': string
|
||||
'governance.editPolicyMetadataTitle': string
|
||||
'governance.emptyPolicySet': string
|
||||
'governance.evaluatedOn': string
|
||||
'governance.evaluatedTime': string
|
||||
'governance.evaluationEmpty': string
|
||||
'governance.evaluations': string
|
||||
'governance.event': string
|
||||
'governance.failureHeading': string
|
||||
'governance.failureHeadingEvaluationDetail': string
|
||||
'governance.failureModalTitle': string
|
||||
'governance.formatInput': string
|
||||
'governance.inputFailedEvaluation': string
|
||||
'governance.inputSuccededEvaluation': string
|
||||
'governance.noEvaluationForPipeline': string
|
||||
'governance.noPolicySetForPipeline': string
|
||||
'governance.onCreate': string
|
||||
'governance.onRun': string
|
||||
'governance.onSave': string
|
||||
'governance.onStep': string
|
||||
'governance.policyAccountCount': string
|
||||
'governance.policyDescription': string
|
||||
'governance.policyIdentifier': string
|
||||
'governance.policyName': string
|
||||
'governance.policyOrgCount': string
|
||||
'governance.policyProjectCount': string
|
||||
'governance.policySetGroup': string
|
||||
'governance.policySetGroupAccount': string
|
||||
'governance.policySetGroupOrg': string
|
||||
'governance.policySetGroupProject': string
|
||||
'governance.policySetName': string
|
||||
'governance.policySets': string
|
||||
'governance.policySetsApplied': string
|
||||
'governance.selectInput': string
|
||||
'governance.selectSamplePolicy': string
|
||||
'governance.successHeading': string
|
||||
'governance.viewPolicy': string
|
||||
'governance.warn': string
|
||||
'governance.warning': string
|
||||
'governance.warningHeading': string
|
||||
'governance.warningHeadingEvaluationDetail': string
|
||||
'governance.wizard.fieldArray': string
|
||||
'governance.wizard.policySelector.account': string
|
||||
'governance.wizard.policySelector.org': string
|
||||
'governance.wizard.policySelector.selectPolicy': string
|
||||
'governance.wizard.policyToEval': string
|
||||
input: string
|
||||
lastUpdated: string
|
||||
name: string
|
||||
navigationCheckText: string
|
||||
navigationCheckTitle: string
|
||||
namePlaceholder: string
|
||||
no: string
|
||||
noAccount: string
|
||||
noSearchResultsFound: string
|
||||
optionalField: string
|
||||
outputLabel: string
|
||||
overview: string
|
||||
pageNotFound: string
|
||||
password: string
|
||||
samplePolicies: string
|
||||
saveOverwrite: string
|
||||
search: string
|
||||
signIn: string
|
||||
signUp: string
|
||||
source: string
|
||||
|
@ -141,12 +36,5 @@ export interface StringsMap {
|
|||
success: string
|
||||
tagsLabel: string
|
||||
type: string
|
||||
useSample: string
|
||||
'validation.identifierIsRequired': string
|
||||
'validation.identifierRequired': string
|
||||
'validation.nameRequired': string
|
||||
'validation.policySaveButtonMessage': string
|
||||
'validation.thisIsARequiredField': string
|
||||
'validation.validIdRegex': string
|
||||
yes: string
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import type { FeatureFlagMap } from '../utils/GovernanceUtils'
|
||||
|
||||
export function useStandaloneFeatureFlags(): FeatureFlagMap {
|
||||
return {
|
||||
OPA_PIPELINE_GOVERNANCE: true,
|
||||
OPA_FF_GOVERNANCE: false,
|
||||
CUSTOM_POLICY_STEP: false,
|
||||
OPA_GIT_GOVERNANCE: false,
|
||||
OPA_SECRET_GOVERNANCE: false
|
||||
}
|
||||
}
|
|
@ -29,131 +29,7 @@ tagsLabel: Tags
|
|||
yes: Yes
|
||||
no: No
|
||||
source: Source
|
||||
common:
|
||||
namePlaceholder: Enter Name
|
||||
policies: 'Policies'
|
||||
policy:
|
||||
policysets: Policy Sets
|
||||
newPolicy: New Policy
|
||||
evaluations: Evaluations
|
||||
policySearch: Search Policy by name
|
||||
noPolicy: A Harness policy is an OPA rule that can be enforced on your Harness software delivery processes to ensure governance and compliance.
|
||||
noPolicyTitle: You have no policies
|
||||
noPolicyResult: No policies found
|
||||
noPolicyEvalResultTitle: You have no policy evaluations
|
||||
noPolicyEvalResult: Policy evaluations are created when policy sets are enforced on your Harness entities.
|
||||
noSelectInput: Select appropriate options
|
||||
permission:
|
||||
noEdit: You do not have permission to edit a Policy
|
||||
table:
|
||||
name: Policy
|
||||
lastModified: Last Modified
|
||||
createdAt: Created At
|
||||
policiesSets:
|
||||
newPolicyset: New Policy Set
|
||||
noPolicySets: No Policy Sets evaluated
|
||||
evaluationCriteria: Policy evaluation criteria
|
||||
policySetSearch: Search Policy Set by name
|
||||
noPolicySetTitle: Create a Policy Set to apply Policies
|
||||
noPolicySet: A harness policy set allows you to group policies and configure where they will be enforced.
|
||||
noPolicySetResult: No Policy Sets found
|
||||
noPolicySetDescription: No Policy Set Description
|
||||
stepOne:
|
||||
validName: '{{$.validation.nameRequired}}'
|
||||
validId: '{{$.validation.identifierRequired}}'
|
||||
validIdRegex: '{{$.common.validation.formatMustBeAlphanumeric}}'
|
||||
table:
|
||||
name: Policy Set
|
||||
enforced: Enforced
|
||||
entityType: Entity Type {{name}}
|
||||
event: Event
|
||||
scope: Scope
|
||||
entity: Entity Type
|
||||
enforced: Enforced
|
||||
created: Created
|
||||
updated: Updated
|
||||
governance:
|
||||
policyAccountCount: Account ({{count}})
|
||||
policyOrgCount: Organization ({{count}})
|
||||
policyProjectCount: Project ({{count}})
|
||||
viewPolicy: View Policy
|
||||
editPolicy: Edit Policy
|
||||
editPolicyMetadataTitle: Policy Name
|
||||
formatInput: Format Input
|
||||
selectInput: Select Input
|
||||
clearOutput: Clear Output
|
||||
inputFailedEvaluation: Input failed Policy Evaluation
|
||||
inputSuccededEvaluation: Input succeeded Policy Evaluation
|
||||
warning: warning
|
||||
evaluatedTime: 'Evaluated {{time}}'
|
||||
failureHeading: Pipeline execution could not proceed due to the Policy Evaluation failures.
|
||||
warningHeading: Pipeline execution has Policy Evaluation warnings.
|
||||
failureHeadingEvaluationDetail: Policy Evaluation failed.
|
||||
warningHeadingEvaluationDetail: Policy Evaluation contains warnings.
|
||||
successHeading: All policies are passed.
|
||||
policySets: 'Policy Sets ({{count}})'
|
||||
evaluations: Evaluations {{count}}
|
||||
policySetName: 'Policy Set: {{name}}'
|
||||
emptyPolicySet: This Policy Set does not have any policies attached to it.
|
||||
failureModalTitle: Policy Set Evaluations
|
||||
policySetsApplied: '{{pipelineName}}: Policy Sets applied'
|
||||
warn: warning {{count}}
|
||||
event: Pipeline Event
|
||||
evaluatedOn: Evaluated On
|
||||
onRun: On Run
|
||||
onSave: On Save
|
||||
onCreate: On Create
|
||||
onStep: On Step
|
||||
policyName: 'Policy Name: {{name}}'
|
||||
policyIdentifier: 'Policy Identifier: {{policyIdentifier}}'
|
||||
policyDescription: 'Policy Desctiption: {{policyDescription}}'
|
||||
deleteTitle: Delete Policy
|
||||
deleteConfirmation: Are you sure you want to delete Policy "{{name}}"? This action cannot be undone.
|
||||
deleteDone: Policy "{{name}}" deleted.
|
||||
deletePolicySetTitle: Delete Policy Set
|
||||
deletePolicySetConfirmation: Are you sure you want to delete Policy Set "{{name}}"? This action cannot be undone.
|
||||
deletePolicySetDone: Policy Set "{{name}}" deleted.
|
||||
selectSamplePolicy: Select a Policy example
|
||||
evaluationEmpty: No Policy is linked for this evaluation.
|
||||
noPolicySetForPipeline: No Policy Set applied for this pipeline.
|
||||
noEvaluationForPipeline: No Evaluation found for this pipeline.
|
||||
wizard:
|
||||
policyToEval: Policy to Evaluate
|
||||
fieldArray: Applies to Pipeline on the following events
|
||||
policySelector:
|
||||
selectPolicy: Select Policy
|
||||
account: Account
|
||||
org: Org {{name}}
|
||||
policySetGroup: Policy Set Group
|
||||
policySetGroupAccount: Account {{name}}
|
||||
policySetGroupOrg: Organization {{name}}
|
||||
policySetGroupProject: Project {{name}}
|
||||
validation:
|
||||
identifierIsRequired: '{{$.validation.identifierRequired}}'
|
||||
validIdRegex: Identifier must start with an letter or _ and can then be followed by alphanumerics, _, or $
|
||||
thisIsARequiredField: This setting is required
|
||||
nameRequired: Name is required
|
||||
identifierRequired: Identifier is required
|
||||
policySaveButtonMessage: '{{type}} is required'
|
||||
lastUpdated: Last Updated
|
||||
AZ09: A - Z, 0 - 9
|
||||
ZA90: Z - A, 9 - 0
|
||||
evaluation:
|
||||
onePolicyEvaluated: 1 Policy Evaluated
|
||||
evaluatedPoliciesCount: '{{count}} Policies Evaluated'
|
||||
all: All
|
||||
entity: Entity
|
||||
fileOverwrite: File Overwrite
|
||||
saveOverwrite: Are you sure you want to overwrite this file? Your unsaved work will be lost.
|
||||
confirm: Confirm
|
||||
useSample: Use This Sample
|
||||
samplePolicies: Sample Policies
|
||||
search: Search
|
||||
input: Input
|
||||
navigationCheckText: 'You have unsaved changes. Are you sure you want to leave this page without saving?'
|
||||
navigationCheckTitle: 'Close without saving?'
|
||||
noSearchResultsFound: No search results found for '{{searchTerm}}'.
|
||||
clearFilter: Clear Filter
|
||||
namePlaceholder: Enter Name
|
||||
banner:
|
||||
expired: expired {{ days }} days ago
|
||||
expiryCountdown: expires in {{ days }} days
|
||||
|
|
|
@ -45,7 +45,7 @@ export const Login: React.FC = () => {
|
|||
mutate(formData as unknown as void)
|
||||
.then(_data => {
|
||||
setToken(get(_data, 'access_token' as string))
|
||||
history.replace(routes.toPolicyDashboard())
|
||||
history.replace(routes.toDashboard())
|
||||
})
|
||||
.catch(error => {
|
||||
showError(`Error: ${error}`)
|
||||
|
@ -54,7 +54,7 @@ export const Login: React.FC = () => {
|
|||
mutateRegister(formData as unknown as void)
|
||||
.then(_data => {
|
||||
setToken(get(_data, 'access_token' as string))
|
||||
history.replace(routes.toPolicyDashboard())
|
||||
history.replace(routes.toDashboard())
|
||||
})
|
||||
.catch(error => {
|
||||
showError(`Error: ${error}`)
|
||||
|
@ -89,16 +89,16 @@ export const Login: React.FC = () => {
|
|||
<FormInput.Text name="email" label={getString('email')} />
|
||||
<FormInput.Text name="password" label={getString('password')} inputGroup={{ type: 'password' }} />
|
||||
<Button type="submit" intent="primary" width="100%">
|
||||
{pathname === '/login' ? getString('signIn') : getString('signUp')}
|
||||
{pathname === '/signin' ? getString('signIn') : getString('signUp')}
|
||||
</Button>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
</Container>
|
||||
|
||||
<Layout.Horizontal margin={{ top: 'xxxlarge' }} spacing="xsmall">
|
||||
<Text>{pathname === '/login' ? getString('noAccount') : getString('existingAccount')}</Text>
|
||||
<Link to={pathname === '/login' ? routes.toRegister() : routes.toSignIn()}>
|
||||
{pathname === '/login' ? getString('signUp') : getString('signIn')}
|
||||
<Text>{pathname === '/signin' ? getString('noAccount') : getString('existingAccount')}</Text>
|
||||
<Link to={pathname === '/signin' ? routes.toSignUp() : routes.toSignIn()}>
|
||||
{pathname === '/signin' ? getString('signUp') : getString('signIn')}
|
||||
</Link>
|
||||
</Layout.Horizontal>
|
||||
</div>
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
import React, { useRef, useState, useCallback } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { useOnRegister } from 'services/pm'
|
||||
import routes from 'RouteDefinitions'
|
||||
|
||||
import Link from '../../components/Link/Link'
|
||||
import Input from '../../components/Input/input'
|
||||
import Button from '../../components/Button/button'
|
||||
import logo from '../../logo.svg'
|
||||
import styles from './Register.module.scss'
|
||||
|
||||
// Renders the Register page.
|
||||
export const Register = () => {
|
||||
const history = useHistory()
|
||||
const [error, setError] = useState(null)
|
||||
const [fullname, setFullname] = useState('')
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const { mutate } = useOnRegister({})
|
||||
|
||||
const handleRegister = useCallback(() => {
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('fullname', fullname)
|
||||
formData.append('password', password)
|
||||
formData.append('username', username)
|
||||
|
||||
mutate(formData)
|
||||
.then(() => {
|
||||
history.replace(routes.toLogin())
|
||||
})
|
||||
.catch(error => {
|
||||
// TODO: Use toaster to show error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error({ error })
|
||||
setError(error)
|
||||
})
|
||||
}, [mutate, username, password, fullname, history])
|
||||
|
||||
const alert = error && error.message ? <div class="alert">{error.message}</div> : undefined
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} />
|
||||
</div>
|
||||
<h2>Sign up for a new account</h2>
|
||||
{alert}
|
||||
<div className={styles.field}>
|
||||
<label>Full Name</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="fullname"
|
||||
placeholder="Full Name"
|
||||
className={styles.input}
|
||||
onChange={e => setFullname(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label>Email</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Email"
|
||||
className={styles.input}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label>Password</label>
|
||||
<Input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
className={styles.input}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={handleRegister} className={styles.submit}>
|
||||
Sign Up
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<span>
|
||||
Already have an account? <Link href="/login">Sign In</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react'
|
||||
import styles from './SignUp.module.scss'
|
||||
|
||||
// Renders the Register page.
|
||||
export const SignUp: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<h2>Sign up for a new account</h2>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,442 +0,0 @@
|
|||
import { Intent, IToaster, IToastProps, Position, Toaster } from '@blueprintjs/core'
|
||||
import type { editor as EDITOR } from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
import { Color } from '@harness/uicore'
|
||||
import { get } from 'lodash-es'
|
||||
import moment from 'moment'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useEffect } from 'react'
|
||||
import type { StringsContextValue } from 'framework/strings/StringsContext'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { useStandaloneFeatureFlags } from '../hooks/useStandaloneFeatureFlags'
|
||||
|
||||
/** This utility shows a toaster without being bound to any component.
|
||||
* It's useful to show cross-page/component messages */
|
||||
export function showToaster(message: string, props?: Partial<IToastProps>): IToaster {
|
||||
const toaster = Toaster.create({ position: Position.TOP })
|
||||
toaster.show({ message, intent: Intent.SUCCESS, ...props })
|
||||
return toaster
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const getErrorMessage = (error: any): string =>
|
||||
get(error, 'data.error', get(error, 'data.message', error?.message))
|
||||
|
||||
export const MonacoEditorOptions = {
|
||||
ignoreTrimWhitespace: true,
|
||||
minimap: { enabled: false },
|
||||
codeLens: false,
|
||||
scrollBeyondLastLine: false,
|
||||
smartSelect: false,
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
overviewRulerBorder: false
|
||||
}
|
||||
|
||||
export const MonacoEditorJsonOptions = {
|
||||
...MonacoEditorOptions,
|
||||
tabSize: 2
|
||||
}
|
||||
|
||||
// Monaco editor has a bug where when its value is set, the value
|
||||
// is selected all by default.
|
||||
// Fix by set selection range to zero
|
||||
export const deselectAllMonacoEditor = (editor?: EDITOR.IStandaloneCodeEditor): void => {
|
||||
editor?.focus()
|
||||
setTimeout(() => {
|
||||
editor?.setSelection(new monaco.Selection(0, 0, 0, 0))
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export const ENTITIES = {
|
||||
pipeline: {
|
||||
label: 'Pipeline',
|
||||
value: 'pipeline',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Pipeline Evaluation',
|
||||
value: 'evaluation'
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{ label: 'On Run', value: 'onrun' },
|
||||
{ label: 'On Save', value: 'onsave' }
|
||||
// {
|
||||
// label: 'On Step',
|
||||
// value: 'onstep',
|
||||
// enableAction: flags => {
|
||||
// return flags?.CUSTOM_POLICY_STEP
|
||||
// }
|
||||
// }
|
||||
],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_PIPELINE_GOVERNANCE
|
||||
}
|
||||
},
|
||||
flag: {
|
||||
label: 'Feature Flag',
|
||||
value: 'flag',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Flag Evaluation',
|
||||
value: 'flag_evaluation'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Save', value: 'onsave' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_FF_GOVERNANCE
|
||||
}
|
||||
},
|
||||
connector: {
|
||||
label: 'Connector',
|
||||
value: 'connector',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Connector Evaluation',
|
||||
value: 'connector_evaluation'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Save', value: 'onsave' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_CONNECTOR_GOVERNANCE
|
||||
}
|
||||
},
|
||||
secret: {
|
||||
label: 'Secret',
|
||||
value: 'secret',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'On Save',
|
||||
value: 'onsave'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Save', value: 'onsave' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.OPA_SECRET_GOVERNANCE
|
||||
}
|
||||
},
|
||||
custom: {
|
||||
label: 'Custom',
|
||||
value: 'custom',
|
||||
eventTypes: [
|
||||
{
|
||||
label: 'Custom Evaluation',
|
||||
value: 'custom_evaluation'
|
||||
}
|
||||
],
|
||||
actions: [{ label: 'On Step', value: 'onstep' }],
|
||||
enabledFunc: flags => {
|
||||
return flags?.CUSTOM_POLICY_STEP
|
||||
}
|
||||
}
|
||||
} as Entities
|
||||
|
||||
export const getEntityLabel = (entity: keyof Entities): string => {
|
||||
return ENTITIES[entity].label
|
||||
}
|
||||
|
||||
export function useEntities(): Entities {
|
||||
const {
|
||||
hooks: { useFeatureFlags = useStandaloneFeatureFlags }
|
||||
} = useAppContext()
|
||||
const flags = useFeatureFlags()
|
||||
const availableEntities = { ...ENTITIES }
|
||||
|
||||
for (const key in ENTITIES) {
|
||||
if (!ENTITIES[key as keyof Entities].enabledFunc(flags)) {
|
||||
delete availableEntities[key as keyof Entities]
|
||||
continue
|
||||
}
|
||||
|
||||
// temporary(?) feature flagging of actions
|
||||
availableEntities[key as keyof Entities].actions = availableEntities[key as keyof Entities].actions.filter(
|
||||
action => {
|
||||
return action.enableAction ? action.enableAction(flags) : true
|
||||
}
|
||||
)
|
||||
}
|
||||
return availableEntities
|
||||
}
|
||||
|
||||
export const getActionType = (type: string | undefined, action: string | undefined): string => {
|
||||
return ENTITIES[type as keyof Entities].actions.find(a => a.value === action)?.label || 'Unrecognised Action Type'
|
||||
}
|
||||
|
||||
export type FeatureFlagMap = Partial<Record<FeatureFlag, boolean>>
|
||||
|
||||
export enum FeatureFlag {
|
||||
OPA_PIPELINE_GOVERNANCE = 'OPA_PIPELINE_GOVERNANCE',
|
||||
OPA_FF_GOVERNANCE = 'OPA_FF_GOVERNANCE',
|
||||
CUSTOM_POLICY_STEP = 'CUSTOM_POLICY_STEP',
|
||||
OPA_CONNECTOR_GOVERNANCE = 'OPA_CONNECTOR_GOVERNANCE',
|
||||
OPA_GIT_GOVERNANCE = 'OPA_GIT_GOVERNANCE',
|
||||
OPA_SECRET_GOVERNANCE = 'OPA_SECRET_GOVERNANCE'
|
||||
}
|
||||
|
||||
export type Entity = {
|
||||
label: string
|
||||
value: string
|
||||
eventTypes: Event[]
|
||||
actions: Action[]
|
||||
enabledFunc: (flags: FeatureFlagMap) => boolean
|
||||
}
|
||||
|
||||
export type Event = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type Action = {
|
||||
label: string
|
||||
value: string
|
||||
enableAction?: (flags: FeatureFlagMap) => boolean
|
||||
}
|
||||
|
||||
export type Entities = {
|
||||
pipeline: Entity
|
||||
flag: Entity
|
||||
connector: Entity
|
||||
secret: Entity
|
||||
custom: Entity
|
||||
}
|
||||
|
||||
export enum EvaluationStatus {
|
||||
ERROR = 'error',
|
||||
PASS = 'pass',
|
||||
WARNING = 'warning'
|
||||
}
|
||||
|
||||
export const isEvaluationFailed = (status?: string): boolean =>
|
||||
status === EvaluationStatus.ERROR || status === EvaluationStatus.WARNING
|
||||
|
||||
export const LIST_FETCHING_PAGE_SIZE = 20
|
||||
|
||||
// TODO - we should try and drive all these from the ENTITIES const ^ as well
|
||||
// theres still a little duplication going on
|
||||
export enum PipeLineEvaluationEvent {
|
||||
ON_RUN = 'onrun',
|
||||
ON_SAVE = 'onsave',
|
||||
ON_CREATE = 'oncreate',
|
||||
ON_STEP = 'onstep'
|
||||
}
|
||||
|
||||
// TODO - we should try and drive all these from the ENTITIES const ^ as well
|
||||
// theres still a little duplication going on
|
||||
export enum PolicySetType {
|
||||
PIPELINE = 'pipeline',
|
||||
FEATURE_FLAGS = 'flag',
|
||||
CUSTOM = 'custom',
|
||||
CONNECTOR = 'connector'
|
||||
}
|
||||
|
||||
export const getEvaluationEventString = (
|
||||
getString: StringsContextValue['getString'],
|
||||
evaluation: PipeLineEvaluationEvent
|
||||
): string => {
|
||||
if (!getString) return ''
|
||||
|
||||
const evaluations = {
|
||||
onrun: getString('governance.onRun'),
|
||||
onsave: getString('governance.onSave'),
|
||||
oncreate: getString('governance.onCreate'),
|
||||
onstep: getString('governance.onStep')
|
||||
}
|
||||
|
||||
return evaluations[evaluation]
|
||||
}
|
||||
|
||||
export const getEvaluationNameString = (evaluationMetadata: string): string | undefined => {
|
||||
try {
|
||||
const entityMetadata = JSON.parse(decodeURIComponent(evaluationMetadata as string))
|
||||
if (entityMetadata.entityName) {
|
||||
return entityMetadata.entityName
|
||||
} else if (entityMetadata['pipelineName']) {
|
||||
return entityMetadata['pipelineName'] //temporary until pipelineName is not being used
|
||||
} else {
|
||||
return 'Unknown'
|
||||
}
|
||||
} catch {
|
||||
return 'Unknown'
|
||||
}
|
||||
}
|
||||
|
||||
export const evaluationStatusToColor = (status: string): Color => {
|
||||
switch (status) {
|
||||
case EvaluationStatus.ERROR:
|
||||
return Color.ERROR
|
||||
case EvaluationStatus.WARNING:
|
||||
return Color.WARNING
|
||||
}
|
||||
|
||||
return Color.SUCCESS
|
||||
}
|
||||
|
||||
// @see https://github.com/drone/policy-mgmt/issues/270
|
||||
// export const QUERY_PARAM_VALUE_ALL = '*'
|
||||
|
||||
export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a'
|
||||
|
||||
interface SetPageNumberProps {
|
||||
setPage: (value: React.SetStateAction<number>) => void
|
||||
pageItemsCount?: number
|
||||
page: number
|
||||
}
|
||||
|
||||
export const setPageNumber = ({ setPage, pageItemsCount, page }: SetPageNumberProps): void => {
|
||||
if (pageItemsCount === 0 && page > 0) {
|
||||
setPage(page - 1)
|
||||
}
|
||||
}
|
||||
|
||||
export const ILLEGAL_IDENTIFIERS = [
|
||||
'or',
|
||||
'and',
|
||||
'eq',
|
||||
'ne',
|
||||
'lt',
|
||||
'gt',
|
||||
'le',
|
||||
'ge',
|
||||
'div',
|
||||
'mod',
|
||||
'not',
|
||||
'null',
|
||||
'true',
|
||||
'false',
|
||||
'new',
|
||||
'var',
|
||||
'return'
|
||||
]
|
||||
|
||||
export const REGO_MONACO_LANGUAGE_IDENTIFIER = 'rego'
|
||||
|
||||
export const omit = (originalObj = {}, keysToOmit: string[]) =>
|
||||
Object.fromEntries(Object.entries(originalObj).filter(([key]) => !keysToOmit.includes(key)))
|
||||
|
||||
export const displayDateTime = (value: number): string | null => {
|
||||
return value ? moment.unix(value / 1000).format(DEFAULT_DATE_FORMAT) : null
|
||||
}
|
||||
|
||||
export interface GitFilterScope {
|
||||
repo?: string
|
||||
branch?: GitBranchDTO['branchName']
|
||||
getDefaultFromOtherRepo?: boolean
|
||||
}
|
||||
|
||||
export interface GitFiltersProps {
|
||||
defaultValue?: GitFilterScope
|
||||
onChange: (value: GitFilterScope) => void
|
||||
className?: string
|
||||
branchSelectClassName?: string
|
||||
showRepoSelector?: boolean
|
||||
showBranchSelector?: boolean
|
||||
showBranchIcon?: boolean
|
||||
shouldAllowBranchSync?: boolean
|
||||
getDisabledOptionTitleText?: () => string
|
||||
}
|
||||
|
||||
export interface GitBranchDTO {
|
||||
branchName?: string
|
||||
branchSyncStatus?: 'SYNCED' | 'SYNCING' | 'UNSYNCED'
|
||||
}
|
||||
|
||||
type Module = 'cd' | 'cf' | 'ci' | undefined
|
||||
|
||||
export const useGetModuleQueryParam = (): Module => {
|
||||
const { projectIdentifier, module } = useParams<Record<string, string>>()
|
||||
return projectIdentifier ? (module as Module) : undefined
|
||||
}
|
||||
|
||||
export enum Editions {
|
||||
ENTERPRISE = 'ENTERPRISE',
|
||||
TEAM = 'TEAM',
|
||||
FREE = 'FREE',
|
||||
COMMUNITY = 'COMMUNITY'
|
||||
}
|
||||
|
||||
export interface License {
|
||||
accountIdentifier?: string
|
||||
createdAt?: number
|
||||
edition?: 'COMMUNITY' | 'FREE' | 'TEAM' | 'ENTERPRISE'
|
||||
expiryTime?: number
|
||||
id?: string
|
||||
lastModifiedAt?: number
|
||||
licenseType?: 'TRIAL' | 'PAID'
|
||||
moduleType?: 'CD' | 'CI' | 'CV' | 'CF' | 'CE' | 'STO' | 'CORE' | 'PMS' | 'TEMPLATESERVICE' | 'GOVERNANCE'
|
||||
premiumSupport?: boolean
|
||||
selfService?: boolean
|
||||
startTime?: number
|
||||
status?: 'ACTIVE' | 'DELETED' | 'EXPIRED'
|
||||
trialExtended?: boolean
|
||||
}
|
||||
|
||||
export interface LicenseInformation {
|
||||
[key: string]: License
|
||||
}
|
||||
|
||||
export const findEnterprisePaid = (licenseInformation: LicenseInformation): boolean => {
|
||||
return !!Object.values(licenseInformation).find(
|
||||
(license: License) => license.edition === Editions.ENTERPRISE && license.licenseType === 'PAID'
|
||||
)
|
||||
}
|
||||
|
||||
export const useAnyTrialLicense = (): boolean => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
|
||||
const hasEnterprisePaid = findEnterprisePaid(licenseInformation)
|
||||
if (hasEnterprisePaid) return false
|
||||
|
||||
const anyTrialEntitlements = Object.values(licenseInformation).find(
|
||||
(license: License) => license?.edition === Editions.ENTERPRISE && license?.licenseType === 'TRIAL'
|
||||
)
|
||||
|
||||
return !!anyTrialEntitlements
|
||||
}
|
||||
|
||||
export const useGetTrialInfo = (): any => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
|
||||
const hasEnterprisePaid = findEnterprisePaid(licenseInformation)
|
||||
if (hasEnterprisePaid) return
|
||||
|
||||
const allEntitlements = Object.keys(licenseInformation).map(module => {
|
||||
return licenseInformation[module]
|
||||
})
|
||||
|
||||
const trialEntitlement = allEntitlements
|
||||
.sort((a: License, b: License) => (b.expiryTime ?? 0) - (a.expiryTime ?? 0))
|
||||
.find((license: License) => license?.edition === Editions.ENTERPRISE && license?.licenseType === 'TRIAL')
|
||||
|
||||
return trialEntitlement
|
||||
}
|
||||
|
||||
export const useFindActiveEnterprise = (): boolean => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
return Object.values(licenseInformation).some(
|
||||
(license: License) => license.edition === Editions.ENTERPRISE && license.status === 'ACTIVE'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the target element to top when any dependency changes
|
||||
* @param {string} target Target element className selector
|
||||
* @param {array} dependencies Dependencies to watch
|
||||
* @returns {void}
|
||||
*/
|
||||
export const useScrollToTop = (target: string, dependencies: unknown[]): void => {
|
||||
useEffect(() => {
|
||||
const element = document.querySelector(`.${target}`)
|
||||
if (element) {
|
||||
element.scrollTop = 0
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dependencies])
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
import { Intent, IToaster, IToastProps, Position, Toaster } from '@blueprintjs/core'
|
||||
import type { editor as EDITOR } from 'monaco-editor/esm/vs/editor/editor.api'
|
||||
import { get } from 'lodash-es'
|
||||
import moment from 'moment'
|
||||
import { useEffect } from 'react'
|
||||
import { useAppContext } from 'AppContext'
|
||||
|
||||
/** This utility shows a toaster without being bound to any component.
|
||||
* It's useful to show cross-page/component messages */
|
||||
export function showToaster(message: string, props?: Partial<IToastProps>): IToaster {
|
||||
const toaster = Toaster.create({ position: Position.TOP })
|
||||
toaster.show({ message, intent: Intent.SUCCESS, ...props })
|
||||
return toaster
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const getErrorMessage = (error: any): string =>
|
||||
get(error, 'data.error', get(error, 'data.message', error?.message))
|
||||
|
||||
export const MonacoEditorOptions = {
|
||||
ignoreTrimWhitespace: true,
|
||||
minimap: { enabled: false },
|
||||
codeLens: false,
|
||||
scrollBeyondLastLine: false,
|
||||
smartSelect: false,
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
overviewRulerBorder: false
|
||||
}
|
||||
|
||||
export const MonacoEditorJsonOptions = {
|
||||
...MonacoEditorOptions,
|
||||
tabSize: 2
|
||||
}
|
||||
|
||||
// Monaco editor has a bug where when its value is set, the value
|
||||
// is selected all by default.
|
||||
// Fix by set selection range to zero
|
||||
export const deselectAllMonacoEditor = (editor?: EDITOR.IStandaloneCodeEditor): void => {
|
||||
editor?.focus()
|
||||
setTimeout(() => {
|
||||
editor?.setSelection(new monaco.Selection(0, 0, 0, 0))
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export const LIST_FETCHING_PAGE_SIZE = 20
|
||||
export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY hh:mm a'
|
||||
|
||||
export const displayDateTime = (value: number): string | null => {
|
||||
return value ? moment.unix(value / 1000).format(DEFAULT_DATE_FORMAT) : null
|
||||
}
|
||||
|
||||
export enum Editions {
|
||||
ENTERPRISE = 'ENTERPRISE',
|
||||
TEAM = 'TEAM',
|
||||
FREE = 'FREE',
|
||||
COMMUNITY = 'COMMUNITY'
|
||||
}
|
||||
|
||||
export interface License {
|
||||
accountIdentifier?: string
|
||||
createdAt?: number
|
||||
edition?: 'COMMUNITY' | 'FREE' | 'TEAM' | 'ENTERPRISE'
|
||||
expiryTime?: number
|
||||
id?: string
|
||||
lastModifiedAt?: number
|
||||
licenseType?: 'TRIAL' | 'PAID'
|
||||
moduleType?: 'CD' | 'CI' | 'CV' | 'CF' | 'CE' | 'STO' | 'CORE' | 'PMS' | 'TEMPLATESERVICE' | 'GOVERNANCE'
|
||||
premiumSupport?: boolean
|
||||
selfService?: boolean
|
||||
startTime?: number
|
||||
status?: 'ACTIVE' | 'DELETED' | 'EXPIRED'
|
||||
trialExtended?: boolean
|
||||
}
|
||||
|
||||
export interface LicenseInformation {
|
||||
[key: string]: License
|
||||
}
|
||||
|
||||
export const findEnterprisePaid = (licenseInformation: LicenseInformation): boolean => {
|
||||
return !!Object.values(licenseInformation).find(
|
||||
(license: License) => license.edition === Editions.ENTERPRISE && license.licenseType === 'PAID'
|
||||
)
|
||||
}
|
||||
|
||||
export const useAnyTrialLicense = (): boolean => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
|
||||
const hasEnterprisePaid = findEnterprisePaid(licenseInformation)
|
||||
if (hasEnterprisePaid) return false
|
||||
|
||||
const anyTrialEntitlements = Object.values(licenseInformation).find(
|
||||
(license: License) => license?.edition === Editions.ENTERPRISE && license?.licenseType === 'TRIAL'
|
||||
)
|
||||
|
||||
return !!anyTrialEntitlements
|
||||
}
|
||||
|
||||
export const useGetTrialInfo = (): any => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
|
||||
const hasEnterprisePaid = findEnterprisePaid(licenseInformation)
|
||||
if (hasEnterprisePaid) return
|
||||
|
||||
const allEntitlements = Object.keys(licenseInformation).map(module => {
|
||||
return licenseInformation[module]
|
||||
})
|
||||
|
||||
const trialEntitlement = allEntitlements
|
||||
.sort((a: License, b: License) => (b.expiryTime ?? 0) - (a.expiryTime ?? 0))
|
||||
.find((license: License) => license?.edition === Editions.ENTERPRISE && license?.licenseType === 'TRIAL')
|
||||
|
||||
return trialEntitlement
|
||||
}
|
||||
|
||||
export const useFindActiveEnterprise = (): boolean => {
|
||||
const {
|
||||
hooks: { useLicenseStore = () => ({}) }
|
||||
} = useAppContext()
|
||||
const { licenseInformation }: { licenseInformation: LicenseInformation } = useLicenseStore()
|
||||
return Object.values(licenseInformation).some(
|
||||
(license: License) => license.edition === Editions.ENTERPRISE && license.status === 'ACTIVE'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the target element to top when any dependency changes
|
||||
* @param {string} target Target element className selector
|
||||
* @param {array} dependencies Dependencies to watch
|
||||
* @returns {void}
|
||||
*/
|
||||
export const useScrollToTop = (target: string, dependencies: unknown[]): void => {
|
||||
useEffect(() => {
|
||||
const element = document.querySelector(`.${target}`)
|
||||
if (element) {
|
||||
element.scrollTop = 0
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dependencies])
|
||||
}
|
Loading…
Reference in New Issue