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

feat: filtering keys by tag #3404

Merged
merged 8 commits into from
Nov 21, 2024
Merged
Changes from 1 commit
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
Next Next commit
feat: filterTag option
stepan662 committed Nov 15, 2024
commit 54c4de08a30691c43d0589bbba284965926d7419
14 changes: 11 additions & 3 deletions packages/core/src/Controller/Plugins/Plugins.ts
Original file line number Diff line number Diff line change
@@ -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!,
@@ -198,6 +204,7 @@ export function Plugins(
findPositions,
onPermanentChange: (data) => events.onPermanentChange.emit(data),
tagNewKeys,
filterTag,
});

instances.observer?.run({
@@ -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,
5 changes: 5 additions & 0 deletions packages/core/src/Controller/State/initState.ts
Original file line number Diff line number Diff line change
@@ -119,6 +119,11 @@ export type TolgeeOptionsInternal = {
* Specify tags that will be preselected for non-existant keys.
*/
tagNewKeys?: string[];

/**
* Filter only keys with specific tags
*/
filterTag?: string[];
};

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

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

export type UiKeyOption = {
1 change: 1 addition & 0 deletions packages/web/src/app/basicTolgee.ts
Original file line number Diff line number Diff line change
@@ -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 => {
21 changes: 17 additions & 4 deletions packages/web/src/package/DevBackend.ts
Original file line number Diff line number Diff line change
@@ -3,20 +3,33 @@ 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 =
const url = new URL(
pId !== undefined
? `${apiUrl}/v2/projects/${pId}/translations/${language}`
: `${apiUrl}/v2/projects/translations/${language}`;
: `${apiUrl}/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, {
headers: {
'X-API-Key': apiKey || '',
33 changes: 30 additions & 3 deletions packages/web/src/package/ui/KeyDialog/KeyForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { IconButton, Button, styled, useTheme, Link } from '@mui/material';
import {
IconButton,
Button,
styled,
useTheme,
Link,
Typography,
} from '@mui/material';
import { OpenInNew } from '@mui/icons-material';

import { TranslationFields } from './TranslationFields';
@@ -53,13 +60,20 @@ const ScKeyHint = styled('span')`
`;

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

const ScTagsWrapper = styled('div')`
margin-top: 5px;
`;

const ScTagsError = styled(Typography)`
color: ${({ theme }) => theme.palette.error.main};
font-size: 12px;
font-weight: 400;
min-height: 18px;
`;

const ScGalleryWrapper = styled('div')`
margin-top: 10px;
`;
@@ -103,6 +117,8 @@ 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 filterTag = useDialogContext((c) => c.uiProps.filterTag);

const screenshotsView = permissions.canViewScreenshots;
const viewPluralCheckbox = permissions.canEditPlural && pluralsSupported;
@@ -181,6 +197,17 @@ export const KeyForm = () => {
<ScTagsWrapper>
<ScFieldTitle>Tags</ScFieldTitle>
<Tags />
<ScTagsError>
{filterTagMissing ? (
<Tooltip title="You need to include at least one of filtered tags, otherwise the translation won't be used in current application.">
<span>
Missing one of filtered tags ({filterTag.join(', ')})
</span>
</Tooltip>
) : (
''
)}
</ScTagsError>
</ScTagsWrapper>
)}
{ready && viewPluralCheckbox && <PluralFormCheckbox />}
@@ -207,7 +234,7 @@ export const KeyForm = () => {
</Button>
<LoadingButton
loading={saving}
disabled={saving || formDisabled}
disabled={saving || formDisabled || filterTagMissing}
onClick={onSave}
color="primary"
variant="contained"
Original file line number Diff line number Diff line change
@@ -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"
23 changes: 16 additions & 7 deletions packages/web/src/package/ui/KeyDialog/dialogContext/index.ts
Original file line number Diff line number Diff line change
@@ -86,6 +86,9 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
const [_pluralArgName, setPluralArgName] = useState<string>();
const [submitError, setSubmitError] = useState<HttpError>();

const filterTagMissing =
Boolean(props.uiProps.filterTag.length) &&
!props.uiProps.filterTag.find((t) => tags.includes(t));
useEffect(() => {
// reset when key changes
setIsPlural(undefined);
@@ -182,7 +185,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) => ({
@@ -351,12 +357,14 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
path: { id: keyData.keyId! },
}));

changeInTolgeeCache(
props.keyName,
selectedNs,
Object.entries(newTranslations),
props.uiProps.changeTranslation
);
if (!filterTagMissing) {
changeInTolgeeCache(
props.keyName,
selectedNs,
Object.entries(newTranslations),
props.uiProps.changeTranslation
);
}

props.uiProps.onPermanentChange({
key: props.keyName,
@@ -495,6 +503,7 @@ export const [DialogProvider, useDialogActions, useDialogContext] =
pluralsSupported,
icuPlaceholders,
submitError,
filterTagMissing,
} as const;

const actions = {