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

fix: improve in context errors #3344

Merged
merged 7 commits into from
Jun 13, 2024
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
2 changes: 1 addition & 1 deletion e2e/cypress/common/simulateReqAndResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const simulateReqAndResponse = ({
cy.intercept({ path: '/v2/projects/*/keys/**', method: 'put' }, (req) => {
checkRequest?.(req.body);
// response is not checked by the client
req.reply({});
req.reply({ response: 'success' });
}).as(randomId);

getDevUi().contains('Update').click();
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"prettier": "^3.2.5",
"prettier-plugin-svelte": "3.2.2",
"semantic-release": "^21.0.6",
"terminate": "^2.6.1",
"ts-node": "^10.9.1",
"turbo": "1.13.2",
"typescript": "^4.9.5"
Expand Down
3 changes: 2 additions & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"test": "jest --collect-coverage",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"schema": "openapi-typescript http://localhost:8202/v3/api-docs/Accessible%20with%20API%20key --output ./src/ui/client/apiSchema.generated.ts",
"schema": "openapi-typescript 'http://localhost:8080/v3/api-docs/Accessible%20with%20Project%20API%20key%20(V2)' --output ./src/package/ui/client/apiSchema.generated.ts",
"tsc": "tsc --noEmit",
"clean": "rm -rf dist lib coverage"
},
Expand Down Expand Up @@ -69,6 +69,7 @@
"fast-text-encoding": "^1.0.6",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"openapi-typescript": "^6.7.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-query": "^3.39.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/package/ui/InContextUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Root, createRoot } from 'react-dom/client';
import { KeyContextMenu } from './KeyContextMenu/KeyContextMenu';

export const InContextUi = (props: UiProps) => {
let rootElement: ShadowRoot | undefined;
let rootElement: Element | undefined;
let tolgeeModalRoot: Root;
let contextMenuRoot: Root;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class KeyContextMenu extends React.Component<
this.setState({ opened: false });
this.state.onSelect?.(undefined);
}}
container={getRootElement().host}
container={getRootElement()}
style={{ zIndex: DEVTOOLS_Z_INDEX }}
>
{Array.from(this.state.keys || []).map(
Expand Down
97 changes: 97 additions & 0 deletions packages/web/src/package/ui/KeyDialog/ErrorAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Alert, AlertTitle } from '@mui/material';
import { HttpError } from '../client/HttpError';
import { useDialogContext } from './dialogContext';
import { NewTabLink } from './Link';

type Props = {
error: HttpError | Error;
severity?: 'error' | 'info';
};

export const ErrorAlert = ({ error, severity = 'error' }: Props) => {
const apiUrl = useDialogContext((c) => c.uiProps.apiUrl);

return (
<Alert sx={{ mt: 2 }} severity={severity}>
{error instanceof HttpError
? getErrorContent(error, apiUrl)
: error.message}
</Alert>
);
};

function DocsInContext() {
return (
<NewTabLink href="https://tolgee.io/js-sdk/in-context">
Learn more in Docs
</NewTabLink>
);
}

function DocsAPIKeys() {
return (
<NewTabLink href="https://tolgee.io/platform/account_settings/api_keys_and_pat_tokens">
Learn more in Docs
</NewTabLink>
);
}

function getErrorContent({ code, params, message }: HttpError, apiUrl: string) {
switch (code) {
case 'operation_not_permitted':
return (
<>
<AlertTitle>Operation not permitted</AlertTitle>
{Boolean(params?.length) && 'Missing scopes: ' + params?.join(', ')}
</>
);

case 'invalid_project_api_key':
return (
<>
<AlertTitle>Invalid API key</AlertTitle>
Check it in the code or in the chrome plugin. <DocsInContext />
</>
);

case 'api_url_not_specified':
return (
<>
<AlertTitle>Oops... I miss the API url</AlertTitle>
Add it in the code or via the chrome plugin. <DocsInContext />
</>
);

case 'api_url_not_valid':
return (
<>
<AlertTitle>API url is not correct ({apiUrl})</AlertTitle>
Check it in the code or in the chrome plugin. <DocsInContext />
</>
);

case 'api_key_not_specified':
return (
<>
<AlertTitle>Oops... I miss the API key</AlertTitle>
Add it in the code or via the chrome plugin. <DocsInContext />
</>
);

case 'permissions_not_sufficient_to_edit':
return (
<>
<AlertTitle>
Sorry, you don't have permissions to make changes
</AlertTitle>
Update your API key or ask admin for more permissions <DocsAPIKeys />
</>
);

case 'fetch_error':
return `Failed to fetch (${apiUrl})`;

default:
return message;
}
}
71 changes: 42 additions & 29 deletions packages/web/src/package/ui/KeyDialog/KeyForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconButton, Button, styled, useTheme } from '@mui/material';
import { IconButton, Button, styled, useTheme, Link } from '@mui/material';
import { OpenInNew } from '@mui/icons-material';

import { TranslationFields } from './TranslationFields';
Expand All @@ -11,6 +11,9 @@ import { NsSelect } from './NsSelect';
import { TOLGEE_RESTRICT_ATTRIBUTE } from '../../constants';
import { Tags } from './Tags/Tags';
import { PluralFormCheckbox } from './PluralFormCheckbox';
import { ErrorAlert } from './ErrorAlert';
import { HttpError } from '../client/HttpError';
import { Tooltip } from '../common/Tooltip';

const ScContainer = styled('div')`
font-family: Rubik, Roboto, Arial;
Expand Down Expand Up @@ -68,14 +71,16 @@ const ScControls = styled('div')`
min-height: 36px;
`;

const ScRestriction = styled('div')`
margin-top: 8px;
color: ${({ theme }) => theme.palette.text.secondary};
const ScKeyTitle = styled(ScFieldTitle)`
justify-content: start;
gap: 4px;
align-items: center;
`;

const ScError = styled('div')`
padding-top: 16px;
color: red;
const ScLinkIcon = styled(Link)`
display: grid;
font-size: 16px;
margin: 0px 0px;
`;

export const KeyForm = () => {
Expand Down Expand Up @@ -130,64 +135,72 @@ export const KeyForm = () => {
<path d="M97.16,7.27a16.94,16.94,0,0,0-1.9,24.47,16.36,16.36,0,0,0,5,3.83,3.23,3.23,0,0,1-2.9,5.77,23.14,23.14,0,0,1-11.41-13C73.83,31.1,63.46,37.09,52.82,46.51c-27.44,24.3-34.35,61.74-16.38,85.26-4.57,5.79-8,12.22-8.9,18.69a20.88,20.88,0,0,0,5.62,18c9.18,9.61,21.42,7.13,31.26,5.14,6.58-1.34,12.8-2.6,16.5-.23,3.22,2.07,3.47,3.87,3.61,4.45,2.1,9.32-5.79,13.89-7.67,16.27a1.48,1.48,0,0,0,1.13,2.4c3.48,0,9-1.18,12.34-4.08s7.16-7.9,5.89-16.32c-.08-.5-.18-1-.32-1.58-.86-3.35-3.1-7.57-8.61-11.09-7.72-4.95-17-3.07-25.22-1.41-9.76,2-16,2.85-20.37-1.71a9.13,9.13,0,0,1-2.46-8.19c.54-3.77,2.65-7.89,5.62-11.86,21.71,16.89,56.87,13.47,82.67-9.39a75.34,75.34,0,0,0,20.81-28.09A23.14,23.14,0,0,1,134.8,89a3.23,3.23,0,0,1,6.08-2.19,16.37,16.37,0,0,0,3.2,5.39,16.85,16.85,0,1,0,11.48-28,3.23,3.23,0,0,1-.51-6.44,23.41,23.41,0,0,1,12.88,2.69c2.6-14.08,3.34-31.41-2.06-37.51-4.08-4.61-20.62-8-35.18-7.76A23.48,23.48,0,0,1,130.8,25a3.23,3.23,0,0,1-6.33-1.28A16.94,16.94,0,0,0,97.16,7.27Zm63.25,21a5.29,5.29,0,0,1-.57,6.19c-1.29,1.14-2.72-.51-4.1-2.06s-3.1-3.42-1.81-4.56A5.74,5.74,0,0,1,160.41,28.27Z"></path>
</svg>
</a>

<ScHeadingTitle>Quick translation</ScHeadingTitle>

{!useBrowserWindow && (
<IconButton
title="Open in new window"
onClick={() => setUseBrowserWindow(true)}
color="inherit"
size="small"
>
<OpenInNew fontSize="small" />
</IconButton>
<Tooltip title="Open in separate window">
<IconButton
onClick={() => setUseBrowserWindow(true)}
color="inherit"
size="small"
>
<OpenInNew fontSize="small" />
</IconButton>
</Tooltip>
)}
<ScHeadingRight>{!loading && <LanguageSelect />}</ScHeadingRight>
</ScHeading>

<ScFieldTitle>Key</ScFieldTitle>
<ScKeyTitle>
Key
{linkToPlatform && (
<Tooltip title="Open key in Tolgee platform">
<ScLinkIcon
role="button"
href={linkToPlatform}
target="_blank"
rel="noreferrer noopener"
sx={{ color: 'inherit' }}
>
<OpenInNew />
</ScLinkIcon>
</Tooltip>
)}
</ScKeyTitle>
<ScKey>
{input}
<ScKeyHint>
{!keyExists && ready && " (key doesn't exist yet)"}
</ScKeyHint>
</ScKey>

<NsSelect
options={fallbackNamespaces}
value={selectedNs}
onChange={setSelectedNs}
/>

{ready && (
<ScTagsWrapper>
<ScFieldTitle>Tags</ScFieldTitle>
<Tags />
</ScTagsWrapper>
)}

{ready && viewPluralCheckbox && <PluralFormCheckbox />}

{!error && (
<ScFieldsWrapper>
<TranslationFields />
</ScFieldsWrapper>
)}

{screenshotsView && ready && (
<ScGalleryWrapper>
<ScreenshotGallery />
</ScGalleryWrapper>
)}

{formDisabled && ready && (
<ScRestriction>
Modification is restricted due to missing permissions.
</ScRestriction>
<ErrorAlert
error={new HttpError('permissions_not_sufficient_to_edit')}
severity="info"
/>
)}

{generalError && <ScError>{generalError}</ScError>}
{generalError && <ErrorAlert error={generalError} />}
<ScControls>
<Button onClick={onClose} color="secondary">
{useBrowserWindow ? 'Close' : 'Cancel'}
Expand Down
26 changes: 26 additions & 0 deletions packages/web/src/package/ui/KeyDialog/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { OpenInNew } from '@mui/icons-material';
import { styled } from '@mui/material';

type Props = {
href: string;
children: React.ReactNode;
};

const StyledLink = styled('a')``;

const StyledIcon = styled(OpenInNew)`
width: 17px;
height: 17px;
position: relative;
top: 3px;
margin-left: 1px;
`;

export const NewTabLink = ({ href, children }: Props) => {
return (
<StyledLink href={href} target="_blank" rel="noreferrer noopener">
{children}
<StyledIcon />
</StyledLink>
);
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { useRef, useState } from 'react';
import { styled, CircularProgress, IconButton, Tooltip } from '@mui/material';
import { styled, CircularProgress, IconButton } from '@mui/material';
import { CameraAlt, AddCircleOutline } from '@mui/icons-material';

import { ScreenshotDropzone } from './ScreenshotDropzone';
import { ScreenshotThumbnail } from './ScreenshotThumbnail';
import { MAX_FILE_COUNT } from './utils';
import { DEVTOOLS_Z_INDEX } from '../../../constants';
import { useDialogContext, useDialogActions } from '../dialogContext';
import { ScreenshotDetail } from './ScreenshotDetail';
import { ScFieldTitle } from '../../common/FieldTitle';
import { ExtensionPrompt } from './ExtensionPrompt';
import { Tooltip } from '../../common/Tooltip';

const ScPlaceholder = styled('div')`
display: flex;
Expand Down Expand Up @@ -134,13 +134,7 @@ export const ScreenshotGallery: React.FC = () => {
{uploadEnabled && (
<>
{(isChrome || ableToTakeScreenshot) && (
<Tooltip
title="Take screenshot"
PopperProps={{
disablePortal: true,
style: { zIndex: DEVTOOLS_Z_INDEX },
}}
>
<Tooltip title="Take screenshot">
<IconButton
sx={{ fontSize: 24 }}
onClick={
Expand All @@ -154,13 +148,7 @@ export const ScreenshotGallery: React.FC = () => {
</Tooltip>
)}

<Tooltip
title="Add image"
PopperProps={{
disablePortal: true,
style: { zIndex: DEVTOOLS_Z_INDEX },
}}
>
<Tooltip title="Add image">
<IconButton sx={{ fontSize: 24 }} onClick={onFileSelect}>
<AddCircleOutline />
</IconButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import clsx from 'clsx';
import React, { useState } from 'react';
import { styled, Tooltip, IconButton } from '@mui/material';
import { styled, IconButton } from '@mui/material';
import { Clear } from '@mui/icons-material';

import { ScreenshotInterface } from '../dialogContext/useGallery';
import { DEVTOOLS_Z_INDEX } from '../../../constants';
import { ScreenshotWithLabels } from './ScreenshotWithLabels';
import { Tooltip } from '../../common/Tooltip';

const ScreenshotBox = styled('div')`
border: 1px solid ${({ theme }) => theme.palette.grey[300]};
Expand Down Expand Up @@ -85,13 +85,7 @@ export const ScreenshotThumbnail: React.FC<Props> = (props) => {
return (
<ScreenshotBox onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
{props.onDelete && (
<Tooltip
title="Delete"
PopperProps={{
disablePortal: true,
style: { zIndex: DEVTOOLS_Z_INDEX },
}}
>
<Tooltip title="Delete">
<DeleteIconButton onClick={onDeleteClick} className={clsx({ hover })}>
<DeleteIcon />
</DeleteIconButton>
Expand Down
Loading