Skip to content

Commit 9d5e76a

Browse files
committed
chore: API token flag integration
1 parent 25bb1e8 commit 9d5e76a

File tree

12 files changed

+178
-38
lines changed

12 files changed

+178
-38
lines changed

src/Pages/GlobalConfigurations/Authorization/APITokens/ApiTokens.component.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ const ApiTokens = () => {
8080

8181
const handleFilterChanges = (_searchText: string): void => {
8282
const _searchTextTrimmed = _searchText.trim()
83-
const _filteredData = tokenList.filter((_tokenData) => _tokenData.name.indexOf(_searchTextTrimmed) >= 0)
83+
const _filteredData = tokenList.filter(
84+
(_tokenData) =>
85+
_tokenData.name.indexOf(_searchTextTrimmed) >= 0 || _tokenData.token.indexOf(_searchTextTrimmed) >= 0,
86+
)
8487
setFilteredTokenList(_filteredData)
8588
setNoResults(_filteredData.length === 0)
8689
}
@@ -95,6 +98,7 @@ const ApiTokens = () => {
9598
token: '',
9699
userId: 0,
97100
userIdentifier: 'API-TOKEN:test',
101+
hideApiToken: false,
98102
})
99103

100104
const renderSearchToken = () => (

src/Pages/GlobalConfigurations/Authorization/APITokens/CreateAPIToken.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ const CreateAPIToken = ({
210210
const userPermissionPayload = createUserPermissionPayload({
211211
id: result.userId,
212212
userIdentifier: result.userIdentifier,
213+
hideApiToken: result.hideApiToken,
213214
userRoleGroups,
214215
serverMode,
215216
directPermission,
@@ -311,10 +312,11 @@ const CreateAPIToken = ({
311312

312313
<GenerateModal
313314
close={handleGenerateTokenActionButton}
314-
token={tokenResponse.token}
315+
token={tokenResponse?.token}
315316
reload={reload}
316317
redirectToTokenList={redirectToTokenList}
317318
open={showGenerateModal}
319+
hideApiToken={tokenResponse?.hideApiToken}
318320
/>
319321
</div>
320322
)

src/Pages/GlobalConfigurations/Authorization/APITokens/EditAPIToken.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
Button,
2424
ButtonStyleType,
2525
ButtonVariantType,
26+
ClipboardButton,
2627
CustomInput,
2728
Icon,
2829
InfoBlock,
@@ -124,6 +125,7 @@ const EditAPIToken = ({
124125
const userPermissionPayload = createUserPermissionPayload({
125126
id: editData.userId,
126127
userIdentifier: editData.userIdentifier,
128+
hideApiToken: result.hideApiToken,
127129
userRoleGroups,
128130
serverMode,
129131
directPermission,
@@ -243,6 +245,19 @@ const EditAPIToken = ({
243245
placeholder="Enter a description to remember where you have used this token"
244246
error={invalidDescription ? 'Max 350 characters allowed.' : null}
245247
/>
248+
{!!editData?.token?.length && (
249+
<label className="form__row">
250+
<span className="form__label">Token</span>
251+
<div className="flex dc__content-space top cn-9">
252+
<span data-testid="api-token-string" className="mono fs-14 dc__word-break">
253+
{editData.token}
254+
</span>
255+
<div className="icon-dim-16 ml-8">
256+
<ClipboardButton content={editData.token} />
257+
</div>
258+
</div>
259+
</label>
260+
)}
246261
<div className="dc__border-top" />
247262
<PermissionConfigurationForm showUserPermissionGroupSelector isAddMode={false} />
248263
</div>

src/Pages/GlobalConfigurations/Authorization/APITokens/GenerateModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ import {
2626
} from '@devtron-labs/devtron-fe-common-lib'
2727

2828
import { GenerateTokenModalType } from './apiToken.type'
29+
import { getApiTokenHeader } from './apiToken.utils'
2930

3031
const GenerateModal = ({
3132
close,
32-
token,
33+
token = '',
3334
reload,
3435
redirectToTokenList,
3536
isRegenerationModal,
3637
open,
38+
hideApiToken = false,
3739
}: GenerateTokenModalType) => {
3840
const [copyToClipboardPromise, setCopyToClipboardPromise] = useState<ReturnType<typeof copyToClipboard>>(null)
3941
const modelType = isRegenerationModal ? 'Regenerated' : 'Generated'
@@ -60,9 +62,7 @@ const GenerateModal = ({
6062
<GenericModal.Body>
6163
<div className="flexbox-col dc__gap-20 p-20">
6264
<div className="flexbox-col dc__gap-4">
63-
<h5 className="m-0 cn-9 lh-1-5 fw-6">
64-
Copy and store this token safely, you won’t be able to view it again.
65-
</h5>
65+
<h5 className="m-0 cn-9 lh-1-5 fw-6">{getApiTokenHeader(hideApiToken)}</h5>
6666
<p className="cn-7 fs-12 lh-1-5 m-0">
6767
You can regenerate a token anytime. If you do, remember to update any scripts or
6868
applications using the old token.

src/Pages/GlobalConfigurations/Authorization/APITokens/RegenerateModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,12 @@ const RegeneratedModal = ({
112112
return showGenerateModal ? (
113113
<GenerateModal
114114
close={handleGenerateTokenActionButton}
115-
token={tokenResponse.token}
115+
token={tokenResponse?.token}
116116
reload={reload}
117117
redirectToTokenList={redirectToTokenList}
118118
isRegenerationModal
119119
open={showGenerateModal}
120+
hideApiToken={tokenResponse.hideApiToken}
120121
/>
121122
) : (
122123
<VisibleModal className="regenerate-token-modal">

src/Pages/GlobalConfigurations/Authorization/APITokens/apiToken.type.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ export interface FormType {
2626
}
2727
export interface TokenResponseType {
2828
success: boolean
29-
token: string
3029
userId: number
3130
userIdentifier: string
31+
hideApiToken: boolean
32+
token?: string
3233
}
3334

3435
export interface GenerateTokenType {
@@ -42,20 +43,21 @@ export interface GenerateTokenType {
4243
reload: () => void
4344
}
4445

45-
export interface TokenListType {
46+
export interface TokenListType extends Pick<TokenResponseType, 'token' | 'userIdentifier' | 'userId' | 'hideApiToken'> {
4647
expireAtInMs: number
4748
id: number
4849
name: string
49-
userId: number
50-
userIdentifier: string
5150
description: string
5251
lastUsedByIp?: string
5352
lastUsedAt?: string
5453
updatedAt?: string
5554
}
5655

5756
export interface EditDataType
58-
extends Pick<TokenListType, 'name' | 'description' | 'expireAtInMs' | 'id' | 'userId' | 'userIdentifier'> {}
57+
extends Pick<
58+
TokenListType,
59+
'name' | 'description' | 'expireAtInMs' | 'token' | 'id' | 'userId' | 'userIdentifier' | 'hideApiToken'
60+
> {}
5961
export interface EditTokenType {
6062
setShowRegeneratedModal: React.Dispatch<React.SetStateAction<boolean>>
6163
showRegeneratedModal: boolean
@@ -77,11 +79,12 @@ export interface GenerateActionButtonType {
7779

7880
export interface GenerateTokenModalType {
7981
close: () => void
80-
token: string
82+
token: TokenListType['token']
8183
reload: () => void
8284
redirectToTokenList: () => void
8385
isRegenerationModal?: boolean
8486
open: GenericModalProps['open']
87+
hideApiToken: TokenListType['hideApiToken']
8588
}
8689

8790
export interface APITokenListType {

src/Pages/GlobalConfigurations/Authorization/APITokens/apiToken.utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { TokenListType } from './apiToken.type'
18+
1719
export function getOptions(customDate) {
1820
return [
1921
{ value: 7, label: '7 days' },
@@ -36,3 +38,6 @@ export const isTokenExpired = (expiredDate: number): boolean => {
3638

3739
return getDateInMilliseconds(new Date().valueOf()) > getDateInMilliseconds(expiredDate)
3840
}
41+
42+
export const getApiTokenHeader = (hideApiToken: TokenListType['hideApiToken']) =>
43+
`Copy and store this token safely ${hideApiToken ? ', you won’t be able to view it again.' : ''}`

src/Pages/GlobalConfigurations/Authorization/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
} from '@devtron-labs/devtron-fe-common-lib'
3838

3939
import { SERVER_MODE } from '../../../config'
40+
import { TokenResponseType } from './APITokens/apiToken.type'
4041
import { PermissionType, UserRoleType } from './constants'
4142

4243
export interface UserAndGroupPermissionsWrapProps {
@@ -311,7 +312,8 @@ export interface CreateUserPermissionPayloadParams
311312
extends Pick<User, 'userStatus' | 'timeToLive' | 'userRoleGroups' | 'canManageAllAccess'> {
312313
id: number
313314
userGroups: Pick<UserGroupType, 'name' | 'userGroupId'>[]
314-
userIdentifier: string
315+
hideApiToken?: TokenResponseType['hideApiToken']
316+
userIdentifier: TokenResponseType['userIdentifier']
315317
serverMode: SERVER_MODE
316318
directPermission: DirectPermissionsRoleFilter[]
317319
chartPermission: ChartGroupPermissionsFilter

src/components/ciPipeline/Webhook/WebhookDetailsModal.tsx

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
stopPropagation,
3939
ButtonStyleType,
4040
Icon,
41+
SelectPicker,
4142
} from '@devtron-labs/devtron-fe-common-lib'
4243
import { useParams } from 'react-router-dom'
4344
import Tippy from '@tippyjs/react'
@@ -53,12 +54,15 @@ import { createGeneratedAPIToken } from '@Pages/GlobalConfigurations/Authorizati
5354
import {
5455
CURL_PREFIX,
5556
GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS,
57+
getWebhookTokenListOptions,
5658
PLAYGROUND_TAB_LIST,
5759
REQUEST_BODY_TAB_LIST,
5860
RESPONSE_TAB_LIST,
61+
SELECT_AUTO_GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS,
62+
TOKEN_TAB_LIST,
5963
} from './webhook.utils'
60-
import { SchemaType, TabDetailsType, WebhookDetailsType, WebhookDetailType } from './types'
61-
import { executeWebhookAPI, getExternalCIConfig } from './webhook.service'
64+
import { SchemaType, TabDetailsType, TokenListOptionsType, WebhookDetailsType, WebhookDetailType } from './types'
65+
import { executeWebhookAPI, getExternalCIConfig, getWebhookAPITokenList } from './webhook.service'
6266
import { GENERATE_TOKEN_NAME_VALIDATION } from '../../../config/constantMessaging'
6367
import { createUserPermissionPayload } from '@Pages/GlobalConfigurations/Authorization/utils'
6468
import { ChartGroupPermissionsFilter } from '@Pages/GlobalConfigurations/Authorization/types'
@@ -67,6 +71,8 @@ import {
6771
getDefaultStatusAndTimeout,
6872
getDefaultUserStatusAndTimeout,
6973
} from '@Pages/GlobalConfigurations/Authorization/libUtils'
74+
import { getApiTokenHeader } from '@Pages/GlobalConfigurations/Authorization/APITokens/apiToken.utils'
75+
import { TokenListType } from '@Pages/GlobalConfigurations/Authorization/APITokens/apiToken.type'
7076

7177
export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType) => {
7278
const { appId, webhookId } = useParams<{
@@ -84,6 +90,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
8490
const [selectedRequestBodyTab, setRequestBodyPlaygroundTab] = useState<string>(REQUEST_BODY_TAB_LIST[0].key)
8591
const [webhookResponse, setWebhookResponse] = useState<Object>(null)
8692
const [generatedAPIToken, setGeneratedAPIToken] = useState<string>(null)
93+
const [selectedTokenTab, setSelectedTokenTab] = useState<string>(TOKEN_TAB_LIST[0].key)
8794
const [showTokenSection, setShowTokenSection] = useState(false)
8895
const [isSuperAdmin, setIsSuperAdmin] = useState(false)
8996
const [samplePayload, setSamplePayload] = useState<any>(null)
@@ -94,11 +101,14 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
94101
const [tryoutAPIToken, setTryoutAPIToken] = useState<string>(null)
95102
const [showTryoutAPITokenError, setTryoutAPITokenError] = useState(false)
96103
const [webhookDetails, setWebhookDetails] = useState<WebhookDetailsType>(null)
104+
const [selectedToken, setSelectedToken] = useState<TokenListOptionsType>(null)
105+
const [tokenList, setTokenList] = useState<TokenListType[]>(undefined)
97106
const [selectedSchema, setSelectedSchema] = useState<string>('')
98107
const [errorInGetData, setErrorInGetData] = useState(false)
99108
const [copyToClipboardPromise, setCopyToClipboardPromise] = useState<ReturnType<typeof copyToClipboard>>(null)
100109
const schemaRef = useRef<Array<HTMLDivElement | null>>([])
101110

111+
102112
const clipboardContent = window.location.href
103113

104114
const handleCopyButtonClick = async () => {
@@ -184,13 +194,30 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
184194
setSampleCURL(
185195
CURL_PREFIX.replace('{webhookURL}', _webhookDetails.webhookUrl).replace('{data}', modifiedJSONString),
186196
)
197+
if (_isSuperAdmin) {
198+
const { result } = await getWebhookAPITokenList(
199+
_webhookDetails.projectName,
200+
_webhookDetails.environmentIdentifier,
201+
_webhookDetails.appName,
202+
)
203+
const sortedResult =
204+
result
205+
?.sort((a, b) => a['name'].localeCompare(b['name']))
206+
.map((tokenData) => {
207+
return { label: tokenData.name, value: tokenData.id, ...tokenData }
208+
}) || []
209+
setTokenList(sortedResult)
210+
}
187211
setLoader(false)
188212
} catch (error) {
189213
setIsSuperAdmin(false)
190214
setLoader(false)
191215
setErrorInGetData(true)
192216
}
193217
}
218+
219+
const hideApiToken = !tokenList?.[0]?.token
220+
194221
const generateToken = async (): Promise<void> => {
195222
if (!tokenName) {
196223
setTokenNameError(true)
@@ -208,6 +235,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
208235
const userPermissionPayload = createUserPermissionPayload({
209236
id: result.userId,
210237
userIdentifier: result.userIdentifier,
238+
hideApiToken: result.hideApiToken,
211239
userRoleGroups: [],
212240
serverMode: SERVER_MODE.FULL,
213241
directPermission: [],
@@ -367,7 +395,7 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
367395
return (
368396
<div className="mt-16">
369397
<InfoBlock
370-
heading="Copy and store this token safely, you won’t be able to view it again."
398+
heading={getApiTokenHeader(hideApiToken)}
371399
description={
372400
<div className="fs-13 font-roboto flexbox dc__word-break" data-testid="generated-api-token">
373401
{token}
@@ -424,6 +452,49 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
424452
)
425453
}
426454

455+
const renderSelectTokenSection = (): JSX.Element => {
456+
const handleSelectedTokenChange = (selectedToken): void => {
457+
setSelectedToken(selectedToken)
458+
}
459+
460+
return (
461+
<>
462+
<div className="w-400 h-32 mt-16">
463+
<SelectPicker
464+
inputId="select-token"
465+
name="select-token"
466+
classNamePrefix="select-token"
467+
placeholder="Select API token"
468+
isClearable={false}
469+
options={getWebhookTokenListOptions(tokenList)}
470+
value={selectedToken}
471+
onChange={handleSelectedTokenChange}
472+
isSearchable={false}
473+
/>
474+
</div>
475+
{selectedToken?.name && renderSelectedToken(selectedToken.token)}
476+
</>
477+
)
478+
}
479+
480+
const renderGeneratedTokenDetails = () => {
481+
if (hideApiToken) {
482+
return (
483+
<div className="cn-9 fs-13 mb-8">
484+
<span className="fs-13 lh-1-5 fw-6">{GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS}</span>
485+
{renderGenerateTokenSection()}
486+
</div>
487+
)
488+
}
489+
return (
490+
<div>
491+
{generateTabHeader(TOKEN_TAB_LIST, selectedTokenTab, setSelectedTokenTab)}
492+
{selectedTokenTab === TOKEN_TAB_LIST[0].key && renderSelectTokenSection()}
493+
{selectedTokenTab === TOKEN_TAB_LIST[1].key && renderGenerateTokenSection()}
494+
</div>
495+
)
496+
}
497+
427498
const renderTokenSection = (): JSX.Element | null => {
428499
if (!isSuperAdmin) {
429500
return (
@@ -440,13 +511,10 @@ export const WebhookDetailsModal = ({ close, isTemplateView }: WebhookDetailType
440511
dataTestId="select-or-generate-token"
441512
variant={ButtonVariantType.text}
442513
onClick={toggleTokenSection}
443-
text={GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS}
514+
text={hideApiToken ? GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS : SELECT_AUTO_GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS }
444515
/>
445516
) : (
446-
<div className="cn-9 fs-13 mb-8">
447-
<span className="fs-13 lh-1-5 fw-6">{GENERATE_TOKEN_WITH_REQUIRED_PERMISSIONS}</span>
448-
{renderGenerateTokenSection()}
449-
</div>
517+
renderGeneratedTokenDetails()
450518
)
451519
}
452520

0 commit comments

Comments
 (0)