Skip to content

Commit

Permalink
feat: filtering keys by tag (#3404)
Browse files Browse the repository at this point in the history
  • Loading branch information
stepan662 authored Nov 21, 2024
1 parent 950285e commit a61f250
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 17 deletions.
14 changes: 11 additions & 3 deletions packages/core/src/Controller/Plugins/Plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,14 @@ export function Plugins(
addPlugin,
findPositions: findPositions,
run() {
const { apiKey, apiUrl, projectId, observerOptions, tagNewKeys } =
getInitialOptions();
const {
apiKey,
apiUrl,
projectId,
observerOptions,
tagNewKeys,
filterTag,
} = getInitialOptions();
instances.ui = plugins.ui?.({
apiKey: apiKey!,
apiUrl: apiUrl!,
Expand All @@ -198,6 +204,7 @@ export function Plugins(
findPositions,
onPermanentChange: (data) => events.onPermanentChange.emit(data),
tagNewKeys,
filterTag,
});

instances.observer?.run({
Expand Down Expand Up @@ -259,13 +266,14 @@ export function Plugins(
}) as BackendGetRecordInternal,

getBackendDevRecord: (async ({ language, namespace }) => {
const { apiKey, apiUrl, projectId } = getInitialOptions();
const { apiKey, apiUrl, projectId, filterTag } = getInitialOptions();
return instances.devBackend?.getRecord({
apiKey,
apiUrl,
projectId,
language,
namespace,
filterTag,
...getCommonProps(),
});
}) as BackendGetRecordInternal,
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/Controller/State/initState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ export type TolgeeOptionsInternal = {
* Specify tags that will be preselected for non-existant keys.
*/
tagNewKeys?: string[];

/**
* Use only keys tagged with one of the listed tags
*/
filterTag?: string[];
};

export type TolgeeOptions = Partial<
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type BackendDevProps = {
apiUrl?: string;
apiKey?: string;
projectId?: number | string;
filterTag?: string[];
};

export type CommonProps = {
Expand Down Expand Up @@ -160,6 +161,7 @@ export type UiProps = {
changeTranslation: ChangeTranslationInterface;
onPermanentChange: (props: TranslationDescriptor) => void;
tagNewKeys?: string[];
filterTag?: string[];
};

export type UiKeyOption = {
Expand Down
2 changes: 1 addition & 1 deletion packages/i18next/src/__integration/tolgeeUpdating.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import i18n from 'i18next';
import { DevTools, Tolgee } from '@tolgee/web';
import { withTolgee, I18nextPlugin } from '..';

const API_URL = 'http://localhost';
const API_URL = 'http://localhost/';
const API_KEY = 'dummyApiKey';

const fetch = mockCoreFetch();
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/app/basicTolgee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const tolgee = Tolgee()
availableLanguages: ['en', 'cs', 'fr', 'de'],
defaultLanguage: 'en',
tagNewKeys: ['draft'],
filterTag: ['test'],
});

export const useTolgee = (events?: TolgeeEvent[]): TolgeeInstance => {
Expand Down
29 changes: 22 additions & 7 deletions packages/web/src/package/DevBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,36 @@ import { getApiKeyType, getProjectIdFromApiKey } from './tools/decodeApiKey';

function createDevBackend(): BackendDevMiddleware {
return {
getRecord({ apiUrl, apiKey, language, namespace, projectId, fetch }) {
getRecord({
apiUrl,
apiKey,
language,
namespace,
projectId,
filterTag,
fetch,
}) {
const pId = getProjectIdFromApiKey(apiKey) ?? projectId;
let url =
pId !== undefined
? `${apiUrl}/v2/projects/${pId}/translations/${language}`
: `${apiUrl}/v2/projects/translations/${language}`;
const url = new URL(apiUrl);

if (pId !== undefined) {
url.pathname = `/v2/projects/${pId}/translations/${language}`;
} else {
url.pathname = `/v2/projects/translations/${language}`;
}

if (namespace) {
url += `?ns=${namespace}`;
url.searchParams.append('ns', namespace);
}
filterTag?.forEach((tag) => {
url.searchParams.append('filterTag', tag);
});

if (getApiKeyType(apiKey) === 'tgpat' && projectId === undefined) {
throw new Error("You need to specify 'projectId' when using PAT key");
}
return fetch(url, {

return fetch(url.toString(), {
headers: {
'X-API-Key': apiKey || '',
'Content-Type': 'application/json',
Expand Down
7 changes: 5 additions & 2 deletions packages/web/src/package/ui/KeyDialog/KeyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PluralFormCheckbox } from './PluralFormCheckbox';
import { ErrorAlert } from './ErrorAlert';
import { HttpError } from '../client/HttpError';
import { Tooltip } from '../common/Tooltip';
import { FilterTagMissingInfo } from './Tags/FilterTagMissingInfo';

const ScContainer = styled('div')`
font-family: Rubik, Roboto, Arial;
Expand Down Expand Up @@ -53,7 +54,7 @@ const ScKeyHint = styled('span')`
`;

const ScFieldsWrapper = styled('div')`
margin-top: 20px;
margin-top: 10px;
`;

const ScTagsWrapper = styled('div')`
Expand Down Expand Up @@ -103,6 +104,7 @@ export const KeyForm = () => {
const fallbackNamespaces = useDialogContext((c) => c.fallbackNamespaces);
const selectedNs = useDialogContext((c) => c.selectedNs);
const permissions = useDialogContext((c) => c.permissions);
const filterTagMissing = useDialogContext((c) => c.filterTagMissing);

const screenshotsView = permissions.canViewScreenshots;
const viewPluralCheckbox = permissions.canEditPlural && pluralsSupported;
Expand Down Expand Up @@ -181,6 +183,7 @@ export const KeyForm = () => {
<ScTagsWrapper>
<ScFieldTitle>Tags</ScFieldTitle>
<Tags />
{filterTagMissing && <FilterTagMissingInfo />}
</ScTagsWrapper>
)}
{ready && viewPluralCheckbox && <PluralFormCheckbox />}
Expand All @@ -207,7 +210,7 @@ export const KeyForm = () => {
</Button>
<LoadingButton
loading={saving}
disabled={saving || formDisabled}
disabled={saving || formDisabled || filterTagMissing}
onClick={onSave}
color="primary"
variant="contained"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const PluralFormCheckbox = () => {
const expanded = _expanded && isPlural;

return (
<Box display="grid" mt={2}>
<Box display="grid">
<Box justifyContent="start" display="flex" alignItems="center">
<FormControlLabel
data-cy="key-plural-checkbox"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Alert, AlertTitle, Box } from '@mui/material';
import { useDialogContext } from '../dialogContext';
import { MissingTagsList } from './MissingTagsList';

export const FilterTagMissingInfo = () => {
const filterTag = useDialogContext((c) => c.uiProps.filterTag);
const filterTagMissing = useDialogContext((c) => c.filterTagMissing);

if (!filterTagMissing) {
return null;
}

if (filterTag)
return (
<Alert severity="error" sx={{ mt: 1, mb: 1, fontSize: 15 }}>
<AlertTitle>Missing Required Tag</AlertTitle>
{filterTag.length > 1 ? (
<Box>
This app is configured to use only keys tagged with the{' '}
<MissingTagsList tags={filterTag} />. Add one of them to continue.
</Box>
) : (
<Box>
This app is configured to use only keys tagged with the{' '}
<MissingTagsList tags={filterTag} />. Add this tag to continue.
</Box>
)}
<Box mt={1}>
<a
href="https://docs.tolgee.io/js-sdk/filter_by_tags"
target="_blank"
rel="noopener noreferrer"
>
Read more in the docs
</a>
</Box>
</Alert>
);
};
37 changes: 37 additions & 0 deletions packages/web/src/package/ui/KeyDialog/Tags/MissingTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { styled } from '@mui/material';
import { useDialogActions } from '../dialogContext';

const StyledTag = styled('div')`
display: inline-flex;
outline: 0;
cursor: default;
padding: 1px 8px;
border-radius: 12px;
align-items: center;
font-size: 14px;
background: ${({ theme }) => theme.palette.grey[200]};
border: 1px solid transparent;
max-width: 100%;
box-sizing: border-box;
border: 1px solid ${({ theme }) => theme.palette.text.secondary};
margin: -2px 0px;
cursor: pointer;
&:focus-within,
&:hover {
border: 1px solid ${({ theme }) => theme.palette.primary.main};
color: ${({ theme }) => theme.palette.primary.main};
}
`;

type Props = {
name: string;
};

export const MissingTag = ({ name }: Props) => {
const { setTags } = useDialogActions();
function addTag(name: string) {
setTags((values) => [...values.filter((t) => t !== name), name]);
}

return <StyledTag onClick={() => addTag(name)}>{name}</StyledTag>;
};
35 changes: 35 additions & 0 deletions packages/web/src/package/ui/KeyDialog/Tags/MissingTagsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { MissingTag } from './MissingTag';

type Props = {
tags: string[];
};

export const MissingTagsList = ({ tags }: Props) => {
return (
<>
{tags.map((tag, index) => {
if (index === 0) {
return (
<React.Fragment key={tag}>
<MissingTag name={tag} />
</React.Fragment>
);
} else if (index < tags.length - 1) {
return (
<React.Fragment key={tag}>
, <MissingTag name={tag} />
</React.Fragment>
);
} else {
return (
<React.Fragment key={tag}>
{' '}
or <MissingTag name={tag} />
</React.Fragment>
);
}
})}
</>
);
};
14 changes: 11 additions & 3 deletions packages/web/src/package/ui/KeyDialog/dialogContext/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
const [saving, setSaving] = useState(false);

const [selectedNs, setSelectedNs] = useState<string>(props.namespace);
const [tags, setTags] = useState<string[]>([]);
const [tags, setTags] = useState<string[] | undefined>(undefined);
const [_isPlural, setIsPlural] = useState<boolean>();
const [_pluralArgName, setPluralArgName] = useState<string>();
const [submitError, setSubmitError] = useState<HttpError>();

const filterTagMissing =
Boolean(props.uiProps.filterTag?.length) &&
tags &&
!props.uiProps.filterTag.find((t) => tags.includes(t));
useEffect(() => {
// reset when key changes
setIsPlural(undefined);
Expand Down Expand Up @@ -182,7 +186,10 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
if (firstKey) {
setTags(firstKey?.keyTags?.map((t) => t.name) || []);
} else {
setTags(props.uiProps.tagNewKeys ?? []);
setTags([
...(props.uiProps.filterTag ?? []),
...(props.uiProps.tagNewKeys ?? []),
]);
}
setScreenshots(
firstKey?.screenshots?.map((sc) => ({
Expand Down Expand Up @@ -486,7 +493,7 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
screenshotDetail,
linkToPlatform,
keyExists,
tags,
tags: tags || [],
permissions,
canTakeScreenshots,
isPlural,
Expand All @@ -495,6 +502,7 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
pluralsSupported,
icuPlaceholders,
submitError,
filterTagMissing,
} as const;

const actions = {
Expand Down

0 comments on commit a61f250

Please sign in to comment.