minor fix

This commit is contained in:
Chirag Madlani 2025-09-30 15:14:32 +05:30
parent e51fc7afe6
commit f16e5f2bf2
22 changed files with 336 additions and 167 deletions

View File

@ -262,10 +262,10 @@ const CustomControls: FC<{
); );
const handleExportClick = useCallback(() => { const handleExportClick = useCallback(() => {
if (activeTab === 'lineage') { if (activeTab === 'impact_analysis') {
onExportClick([ExportTypes.CSV, ExportTypes.PNG]);
} else {
onExportClick([ExportTypes.CSV], handleImpactAnalysisExport); onExportClick([ExportTypes.CSV], handleImpactAnalysisExport);
} else {
onExportClick([ExportTypes.CSV, ExportTypes.PNG]);
} }
}, [activeTab, onExportClick]); }, [activeTab, onExportClick]);
@ -354,7 +354,7 @@ const CustomControls: FC<{
arrow arrow
placement="top" placement="top"
title={t('label.export-as-type', { type: t('label.csv') })}> title={t('label.export-as-type', { type: t('label.csv') })}>
<StyledIconButton size="large" onClick={() => handleExportClick}> <StyledIconButton size="large" onClick={handleExportClick}>
<DownloadIcon /> <DownloadIcon />
</StyledIconButton> </StyledIconButton>
</Tooltip> </Tooltip>

View File

@ -10,66 +10,328 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { render } from '@testing-library/react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { LINEAGE_TAB_VIEW } from '../../../constants/Lineage.constants'; import { EntityType } from '../../../enums/entity.enum';
import { LineageLayer } from '../../../generated/settings/settings';
import CustomControlsComponent from './CustomControls.component'; import CustomControlsComponent from './CustomControls.component';
const mockOnEditLineageClick = jest.fn();
const mockOnExportClick = jest.fn(); const mockOnExportClick = jest.fn();
const mockHandleActiveViewTabChange = jest.fn(); const mockOnLineageConfigUpdate = jest.fn();
const mockSetSelectedQuickFilters = jest.fn();
const mockOnSearchValueChange = jest.fn();
const mockLineageConfig = {
upstreamDepth: 3,
downstreamDepth: 3,
nodesPerLayer: 50,
};
const mockNavigate = jest.fn();
const mockLocation = {
search: '?mode=lineage&depth=3&dir=downstream',
};
const defaultProps = {
nodeDepthOptions: [1, 2, 3, 4, 5],
onSearchValueChange: mockOnSearchValueChange,
searchValue: '',
};
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockNavigate,
useLocation: () => mockLocation,
}));
jest.mock('./LineageSearchSelect/LineageSearchSelect', () => jest.mock('./LineageSearchSelect/LineageSearchSelect', () =>
jest.fn().mockReturnValue(<p>LineageSearchSelect</p>) jest
.fn()
.mockReturnValue(
<div data-testid="lineage-search-select">LineageSearchSelect</div>
)
); );
jest.mock('../../Explore/ExploreQuickFilters', () => jest.mock('../../Explore/ExploreQuickFilters', () =>
jest.fn().mockReturnValue(<p>ExploreQuickFilters</p>) jest
.fn()
.mockReturnValue(
<div data-testid="explore-quick-filters">ExploreQuickFilters</div>
)
); );
jest.mock('reactflow', () => ({ jest.mock('./LineageConfigModal', () =>
Position: () => ({ jest.fn(({ visible, onCancel, onSave }) =>
Left: 'left', visible ? (
Top: 'top', <div data-testid="lineage-config-modal">
Right: 'right', <button onClick={onCancel}>Cancel</button>
Bottom: 'bottom', <button onClick={() => onSave(mockLineageConfig)}>Save</button>
}), </div>
MarkerType: () => ({ ) : null
Arrow: 'arrow', )
ArrowClosed: 'arrowclosed', );
}),
})); jest.mock('../../common/SearchBarComponent/SearchBar.component', () =>
jest.fn(({ onSearch, searchValue, placeholder }) => (
<input
data-testid="search-bar"
placeholder={placeholder}
value={searchValue}
onChange={(e) => onSearch(e.target.value)}
/>
))
);
jest.mock('../../../context/LineageProvider/LineageProvider', () => ({ jest.mock('../../../context/LineageProvider/LineageProvider', () => ({
useLineageProvider: jest.fn().mockImplementation(() => ({ useLineageProvider: jest.fn().mockImplementation(() => ({
onLineageEditClick: mockOnEditLineageClick,
onExportClick: mockOnExportClick, onExportClick: mockOnExportClick,
activeLayer: [LineageLayer.ColumnLevelLineage], onLineageConfigUpdate: mockOnLineageConfigUpdate,
selectedQuickFilters: [], selectedQuickFilters: [],
setSelectedQuickFilters: jest.fn(), setSelectedQuickFilters: mockSetSelectedQuickFilters,
lineageConfig: mockLineageConfig,
nodes: [],
})), })),
})); }));
jest.mock('../../../hooks/useFqn', () => ({
useFqn: jest.fn().mockReturnValue({ fqn: 'test.table' }),
}));
jest.mock('../../../utils/useRequiredParams', () => ({
useRequiredParams: jest
.fn()
.mockReturnValue({ entityType: EntityType.TABLE }),
}));
jest.mock('../../../rest/lineageAPI', () => ({
exportLineageByEntityCountAsync: jest.fn(),
}));
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
search: '?mode=lineage&depth=3&dir=downstream',
},
writable: true,
});
describe('CustomControls', () => { describe('CustomControls', () => {
afterEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
it('calls onEditLinageClick on Edit Lineage button click', () => { it('renders all main control buttons', () => {
const { getByText } = render( render(<CustomControlsComponent {...defaultProps} />, {
<CustomControlsComponent wrapper: MemoryRouter,
activeViewTab={LINEAGE_TAB_VIEW.DIAGRAM_VIEW} });
handleActiveViewTabChange={mockHandleActiveViewTabChange}
onlyShowTabSwitch={false} expect(screen.getByLabelText('label.filter-plural')).toBeInTheDocument();
/>, expect(screen.getByText('label.lineage')).toBeInTheDocument();
{ wrapper: MemoryRouter } expect(screen.getByText('label.impact-analysis')).toBeInTheDocument();
expect(screen.getByLabelText('label.export-as-type')).toBeInTheDocument();
expect(
screen.getByLabelText('label.lineage-configuration')
).toBeInTheDocument();
expect(screen.getByLabelText('label.full-screen-view')).toBeInTheDocument();
});
it('shows LineageSearchSelect by default in lineage mode', () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
expect(screen.getByTestId('lineage-search-select')).toBeInTheDocument();
});
it('shows SearchBar when in impact analysis mode', () => {
window.location.search = '?mode=impact_analysis&depth=3&dir=downstream';
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
expect(screen.getByTestId('search-bar')).toBeInTheDocument();
});
it('toggles filter selection when filter button is clicked', () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const filterButton = screen.getByLabelText('label.filter-plural');
fireEvent.click(filterButton);
expect(screen.getByTestId('explore-quick-filters')).toBeInTheDocument();
});
it('navigates to lineage mode when lineage button is clicked', () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const lineageButton = screen.getByText('label.lineage');
fireEvent.click(lineageButton);
expect(mockNavigate).toHaveBeenCalled();
});
it('navigates to impact analysis mode when impact analysis button is clicked', () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const impactAnalysisButton = screen.getByText('label.impact-analysis');
fireEvent.click(impactAnalysisButton);
expect(mockNavigate).toHaveBeenCalled();
});
it('calls onExportClick when export button is clicked', () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const exportButton = screen.getByLabelText('label.export-as-type');
fireEvent.click(exportButton);
expect(mockOnExportClick).toHaveBeenCalled();
});
it('opens lineage config modal when settings button is clicked', () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const settingsButton = screen.getByLabelText('label.lineage-configuration');
fireEvent.click(settingsButton);
expect(screen.getByTestId('lineage-config-modal')).toBeInTheDocument();
});
it('calls onLineageConfigUpdate when modal is saved', async () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const settingsButton = screen.getByLabelText('label.lineage-configuration');
fireEvent.click(settingsButton);
const saveButton = screen.getByText('Save');
fireEvent.click(saveButton);
await waitFor(() => {
expect(mockOnLineageConfigUpdate).toHaveBeenCalledWith(mockLineageConfig);
});
});
it('toggles fullscreen mode when fullscreen button is clicked', () => {
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const fullscreenButton = screen.getByLabelText('label.full-screen-view');
fireEvent.click(fullscreenButton);
expect(mockNavigate).toHaveBeenCalled();
});
it('shows node depth selector in impact analysis mode with filters active', () => {
window.location.search = '?mode=impact_analysis&depth=3&dir=downstream';
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const filterButton = screen.getByLabelText('label.filter-plural');
fireEvent.click(filterButton);
// Node depth should be visible in the filter section
expect(
screen.getByText(
(content) =>
content?.includes('label.node-depth') && content?.includes(':')
)
).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
});
it('opens node depth menu and selects new depth', () => {
window.location.search = '?mode=impact_analysis&depth=3&dir=downstream';
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const filterButton = screen.getByLabelText('label.filter-plural');
fireEvent.click(filterButton);
const nodeDepthButton = screen.getByText(
(content) =>
content?.includes('label.node-depth') && content?.includes(':')
); );
fireEvent.click(nodeDepthButton);
// check LineageSearchSelect is visible // The menu items would be rendered by Material-UI Menu component
expect(getByText('LineageSearchSelect')).toBeInTheDocument(); // This test verifies the click handler is set up correctly
expect(nodeDepthButton).toBeInTheDocument();
});
// check ExploreQuickFilters is visible it('calls onSearchValueChange when search value changes in impact analysis mode', () => {
expect(getByText('ExploreQuickFilters')).toBeInTheDocument(); window.location.search = '?mode=impact_analysis&depth=3&dir=downstream';
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const searchInput = screen.getByTestId('search-bar');
fireEvent.change(searchInput, { target: { value: 'test search' } });
expect(mockOnSearchValueChange).toHaveBeenCalledWith('test search');
});
it('shows clear filters button when filters are applied', () => {
const mockSetSelectedQuickFilters = jest.fn();
jest.doMock('../../../context/LineageProvider/LineageProvider', () => ({
useLineageProvider: jest.fn().mockImplementation(() => ({
onExportClick: mockOnExportClick,
onLineageConfigUpdate: mockOnLineageConfigUpdate,
selectedQuickFilters: [{ key: 'service', value: ['test-service'] }],
setSelectedQuickFilters: mockSetSelectedQuickFilters,
lineageConfig: mockLineageConfig,
nodes: [],
})),
}));
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
const filterButton = screen.getByLabelText('label.filter-plural');
fireEvent.click(filterButton);
expect(screen.getByText('label.clear-entity')).toBeInTheDocument();
});
it('parses query parameters correctly for upstream direction', () => {
window.location.search = '?mode=lineage&depth=2&dir=upstream';
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
// Component should render correctly with upstream direction
expect(screen.getByText('label.lineage')).toBeInTheDocument();
});
it('handles missing query parameters with defaults', () => {
window.location.search = '';
render(<CustomControlsComponent {...defaultProps} />, {
wrapper: MemoryRouter,
});
// Component should render with default values
expect(screen.getByText('label.lineage')).toBeInTheDocument();
expect(screen.getByText('label.impact-analysis')).toBeInTheDocument();
}); });
}); });

View File

@ -14,7 +14,6 @@
import { IconButton, Menu, ToggleButtonGroup } from '@mui/material'; import { IconButton, Menu, ToggleButtonGroup } from '@mui/material';
import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { ExportTypes } from '../../constants/Export.constants';
import { useLineageProvider } from '../../context/LineageProvider/LineageProvider'; import { useLineageProvider } from '../../context/LineageProvider/LineageProvider';
import { LineageContextType } from '../../context/LineageProvider/LineageProvider.interface'; import { LineageContextType } from '../../context/LineageProvider/LineageProvider.interface';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
@ -26,6 +25,7 @@ import {
getLineagePagingData, getLineagePagingData,
} from '../../rest/lineageAPI'; } from '../../rest/lineageAPI';
import { useRequiredParams } from '../../utils/useRequiredParams'; import { useRequiredParams } from '../../utils/useRequiredParams';
import CustomControlsComponent from '../Entity/EntityLineage/CustomControls.component';
import { LineageConfig } from '../Entity/EntityLineage/EntityLineage.interface'; import { LineageConfig } from '../Entity/EntityLineage/EntityLineage.interface';
import LineageTable from './LineageTable'; import LineageTable from './LineageTable';
import { EImpactLevel } from './LineageTable.interface'; import { EImpactLevel } from './LineageTable.interface';
@ -66,6 +66,9 @@ jest.mock('lodash', () => {
return module; return module;
}); });
jest.mock('../Entity/EntityLineage/CustomControls.component', () => {
return jest.fn().mockReturnValue(<div>CustomControls</div>);
});
const mockUseLineageProvider = useLineageProvider as jest.MockedFunction< const mockUseLineageProvider = useLineageProvider as jest.MockedFunction<
typeof useLineageProvider typeof useLineageProvider
@ -163,8 +166,6 @@ const mockEntity = {
domain: null, domain: null,
}; };
// Remove the custom renderWithRouter function since our test utils handle this
describe('LineageTable', () => { describe('LineageTable', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -176,6 +177,7 @@ describe('LineageTable', () => {
downstreamDepth: 2, downstreamDepth: 2,
upstreamDepth: 2, upstreamDepth: 2,
} as LineageConfig, } as LineageConfig,
updateEntityData: jest.fn(),
onExportClick: jest.fn(), onExportClick: jest.fn(),
onLineageConfigUpdate: jest.fn(), onLineageConfigUpdate: jest.fn(),
} as unknown as LineageContextType); } as unknown as LineageContextType);
@ -230,20 +232,10 @@ describe('LineageTable', () => {
}); });
}); });
it('should render the component', () => { it('should render the CustomControls component', () => {
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter }); render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
// expect(screen.getByRole('searchbox')).toBeInTheDocument(); expect(screen.getByText('CustomControls')).toBeInTheDocument();
expect(screen.getByText('label.lineage')).toBeInTheDocument();
expect(screen.getByText('label.impact-analysis')).toBeInTheDocument();
});
it('should display search bar with correct placeholder', () => {
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
const searchInput = screen.getByPlaceholderText('label.search-for-type');
expect(searchInput).toHaveAttribute('placeholder', 'label.search-for-type');
}); });
it('should render toggle buttons for upstream and downstream', () => { it('should render toggle buttons for upstream and downstream', () => {
@ -263,36 +255,6 @@ describe('LineageTable', () => {
expect(impactButton).toBeInTheDocument(); expect(impactButton).toBeInTheDocument();
}); });
it('should handle search input changes', async () => {
const setSearchValue = jest.fn();
mockUseLineageTableState.mockReturnValue({
...defaultMockState,
setSearchValue,
});
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
const searchInput = screen.getByPlaceholderText('label.search-for-type');
fireEvent.change(searchInput, { target: { value: 'test search' } });
expect(setSearchValue).toHaveBeenCalledWith('test search');
});
it('should toggle filter selection when filter button is clicked', async () => {
const toggleFilterSelection = jest.fn();
mockUseLineageTableState.mockReturnValue({
...defaultMockState,
toggleFilterSelection,
});
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
const filterButton = screen.getByTitle('label.filter-plural');
fireEvent.click(filterButton);
expect(toggleFilterSelection).toHaveBeenCalled();
});
it('should open impact level menu when clicked', async () => { it('should open impact level menu when clicked', async () => {
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter }); render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
@ -337,44 +299,6 @@ describe('LineageTable', () => {
expect(screen.getByText(/label.node-depth/)).toBeInTheDocument(); expect(screen.getByText(/label.node-depth/)).toBeInTheDocument();
}); });
it('should call export function when export button is clicked', async () => {
const onExportClick = jest.fn();
mockUseLineageProvider.mockReturnValue({
selectedQuickFilters: [],
setSelectedQuickFilters: jest.fn(),
lineageConfig: {},
onExportClick,
onLineageConfigUpdate: jest.fn(),
} as unknown as LineageContextType);
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
const exportButton = screen.getByRole('button', { name: 'Export as CSV' });
fireEvent.click(exportButton);
expect(onExportClick).toHaveBeenCalledWith(
[ExportTypes.CSV],
expect.any(Function)
);
});
it('should open lineage config dialog when settings button is clicked', async () => {
const setDialogVisible = jest.fn();
mockUseLineageTableState.mockReturnValue({
...defaultMockState,
setDialogVisible,
});
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
const settingsButton = screen.getByRole('button', {
name: 'label.lineage-configuration',
});
fireEvent.click(settingsButton);
expect(setDialogVisible).toHaveBeenCalledWith(true);
});
it('should render table with correct data source for table level', () => { it('should render table with correct data source for table level', () => {
mockUseLineageTableState.mockReturnValue({ mockUseLineageTableState.mockReturnValue({
...defaultMockState, ...defaultMockState,
@ -405,8 +329,8 @@ describe('LineageTable', () => {
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter }); render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
expect(screen.getByText('Source Table')).toBeInTheDocument(); expect(screen.getByText('label.source')).toBeInTheDocument();
expect(screen.getByText('Impacted Table')).toBeInTheDocument(); expect(screen.getByText('label.impacted')).toBeInTheDocument();
}); });
it('should fetch nodes on component mount', async () => { it('should fetch nodes on component mount', async () => {
@ -503,24 +427,6 @@ describe('LineageTable', () => {
expect(table).toBeInTheDocument(); expect(table).toBeInTheDocument();
}); });
it('should handle node depth selection', async () => {
mockUseLineageTableState.mockReturnValue({
...defaultMockState,
filterSelectionActive: true,
});
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
const nodeDepthButton = screen.getByRole('button', {
name: /label.node-depth/,
});
fireEvent.click(nodeDepthButton);
const depthOption = await screen.findByRole('menuitem', { name: '1' });
expect(depthOption).toBeInTheDocument();
});
describe('LineageTable Hooks Integration', () => { describe('LineageTable Hooks Integration', () => {
it('should integrate with useLineageTableState correctly', () => { it('should integrate with useLineageTableState correctly', () => {
const mockState = { const mockState = {
@ -532,7 +438,12 @@ describe('LineageTable', () => {
render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter }); render(<LineageTable entity={mockEntity} />, { wrapper: MemoryRouter });
expect(screen.getByDisplayValue('test search')).toBeInTheDocument(); expect(CustomControlsComponent).toHaveBeenCalledWith(
expect.objectContaining({
searchValue: 'test search',
}),
{}
);
}); });
it('should integrate with usePaging correctly', () => { it('should integrate with usePaging correctly', () => {

View File

@ -48,7 +48,6 @@ import { useFqn } from '../../hooks/useFqn';
import { SearchSourceAlias } from '../../interface/search.interface'; import { SearchSourceAlias } from '../../interface/search.interface';
import { QueryFieldInterface } from '../../pages/ExplorePage/ExplorePage.interface'; import { QueryFieldInterface } from '../../pages/ExplorePage/ExplorePage.interface';
import { import {
exportLineageByEntityCountAsync,
getLineageByEntityCount, getLineageByEntityCount,
getLineagePagingData, getLineagePagingData,
} from '../../rest/lineageAPI'; } from '../../rest/lineageAPI';
@ -306,27 +305,6 @@ const LineageTable: FC<{ entity: SourceType }> = ({ entity }) => {
return JSON.stringify(query); return JSON.stringify(query);
}, [selectedQuickFilters, searchValue]); }, [selectedQuickFilters, searchValue]);
// Function to handle export click
const handleExportClick = useCallback(
() =>
exportLineageByEntityCountAsync({
fqn: fqn ?? '',
type: entityType ?? '',
direction: lineageDirection,
nodeDepth: nodeDepth,
query_filter: queryFilter,
}),
[
fqn,
entityType,
lineageDirection,
nodeDepth,
currentPage,
pageSize,
queryFilter,
]
);
// Define table columns // Define table columns
const extraTableFilters = useMemo(() => { const extraTableFilters = useMemo(() => {
return ( return (
@ -375,7 +353,7 @@ const LineageTable: FC<{ entity: SourceType }> = ({ entity }) => {
</StyledMenu> </StyledMenu>
</div> </div>
); );
}, [navigate, streamButtonGroup, impactOnEl, impactLevel, handleExportClick]); }, [navigate, streamButtonGroup, impactOnEl, impactLevel]);
// Function to fetch nodes based on current filters and pagination // Function to fetch nodes based on current filters and pagination
const fetchNodes = useCallback(async () => { const fetchNodes = useCallback(async () => {
@ -662,7 +640,7 @@ const LineageTable: FC<{ entity: SourceType }> = ({ entity }) => {
), ),
}, },
{ {
title: t('label.source-column'), title: t('label.source-column-plural'),
dataIndex: dataIndex:
lineageDirection === LineageDirection.Downstream lineageDirection === LineageDirection.Downstream
? ['column', 'fromColumns'] ? ['column', 'fromColumns']

View File

@ -1554,6 +1554,7 @@
"source": "Quelle", "source": "Quelle",
"source-aligned": "Source-aligned", "source-aligned": "Source-aligned",
"source-column": "Quellenspalte", "source-column": "Quellenspalte",
"source-column-plural": "Source Columns",
"source-label": "Quelle:", "source-label": "Quelle:",
"source-match": "Quellenübereinstimmung", "source-match": "Quellenübereinstimmung",
"source-plural": "Quellen", "source-plural": "Quellen",

View File

@ -1554,6 +1554,7 @@
"source": "Source", "source": "Source",
"source-aligned": "Source-aligned", "source-aligned": "Source-aligned",
"source-column": "Source Column", "source-column": "Source Column",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Match By Event Source", "source-match": "Match By Event Source",
"source-plural": "Sources", "source-plural": "Sources",

View File

@ -1554,6 +1554,7 @@
"source": "Fuente", "source": "Fuente",
"source-aligned": "Alineado con el Origen", "source-aligned": "Alineado con el Origen",
"source-column": "Columna de Origen", "source-column": "Columna de Origen",
"source-column-plural": "Source Columns",
"source-label": "Fuente:", "source-label": "Fuente:",
"source-match": "Coincidir por Fuente de Eventos", "source-match": "Coincidir por Fuente de Eventos",
"source-plural": "Fuentes", "source-plural": "Fuentes",

View File

@ -1554,6 +1554,7 @@
"source": "Source", "source": "Source",
"source-aligned": "Source-aligned", "source-aligned": "Source-aligned",
"source-column": "Colonne Source", "source-column": "Colonne Source",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Correspondance par Source d'Événement", "source-match": "Correspondance par Source d'Événement",
"source-plural": "Sources", "source-plural": "Sources",

View File

@ -1554,6 +1554,7 @@
"source": "Fonte", "source": "Fonte",
"source-aligned": "Aliñado coa fonte", "source-aligned": "Aliñado coa fonte",
"source-column": "Columna de orixe", "source-column": "Columna de orixe",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Coincidencia por fonte de evento", "source-match": "Coincidencia por fonte de evento",
"source-plural": "Fontes", "source-plural": "Fontes",

View File

@ -1554,6 +1554,7 @@
"source": "מקור", "source": "מקור",
"source-aligned": "מקור מתואם", "source-aligned": "מקור מתואם",
"source-column": "עמודת מקור", "source-column": "עמודת מקור",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "התאמה לפי מקור אירוע", "source-match": "התאמה לפי מקור אירוע",
"source-plural": "מקורות", "source-plural": "מקורות",

View File

@ -1554,6 +1554,7 @@
"source": "ソース", "source": "ソース",
"source-aligned": "ソース整列", "source-aligned": "ソース整列",
"source-column": "ソースカラム", "source-column": "ソースカラム",
"source-column-plural": "Source Columns",
"source-label": "ソース:", "source-label": "ソース:",
"source-match": "イベントソースで一致", "source-match": "イベントソースで一致",
"source-plural": "ソース", "source-plural": "ソース",

View File

@ -1554,6 +1554,7 @@
"source": "소스", "source": "소스",
"source-aligned": "소스 정렬", "source-aligned": "소스 정렬",
"source-column": "소스 열", "source-column": "소스 열",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "이벤트 소스로 매칭", "source-match": "이벤트 소스로 매칭",
"source-plural": "소스들", "source-plural": "소스들",

View File

@ -1554,6 +1554,7 @@
"source": "स्रोत", "source": "स्रोत",
"source-aligned": "स्रोत-संरेखित", "source-aligned": "स्रोत-संरेखित",
"source-column": "स्रोत स्तंभ", "source-column": "स्रोत स्तंभ",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "इव्हेंट स्रोताद्वारे जुळवा", "source-match": "इव्हेंट स्रोताद्वारे जुळवा",
"source-plural": "स्रोत", "source-plural": "स्रोत",

View File

@ -1554,6 +1554,7 @@
"source": "Bron", "source": "Bron",
"source-aligned": "Bron-georiënteerd", "source-aligned": "Bron-georiënteerd",
"source-column": "Bronkolom", "source-column": "Bronkolom",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Overeenkomst op eventbron", "source-match": "Overeenkomst op eventbron",
"source-plural": "Bronnen", "source-plural": "Bronnen",

View File

@ -1554,6 +1554,7 @@
"source": "منبع", "source": "منبع",
"source-aligned": "هم‌راستا با منبع", "source-aligned": "هم‌راستا با منبع",
"source-column": "ستون منبع", "source-column": "ستون منبع",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "مطابقت بر اساس منبع رویداد", "source-match": "مطابقت بر اساس منبع رویداد",
"source-plural": "منابع", "source-plural": "منابع",

View File

@ -1554,6 +1554,7 @@
"source": "Fonte", "source": "Fonte",
"source-aligned": "Alinhado à Fonte", "source-aligned": "Alinhado à Fonte",
"source-column": "Coluna de Fonte", "source-column": "Coluna de Fonte",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Correspondência por Fonte de Evento", "source-match": "Correspondência por Fonte de Evento",
"source-plural": "Fontes", "source-plural": "Fontes",

View File

@ -1554,6 +1554,7 @@
"source": "Fonte", "source": "Fonte",
"source-aligned": "Alinhado à Fonte", "source-aligned": "Alinhado à Fonte",
"source-column": "Coluna de Fonte", "source-column": "Coluna de Fonte",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Correspondência por Fonte de Evento", "source-match": "Correspondência por Fonte de Evento",
"source-plural": "Fontes", "source-plural": "Fontes",

View File

@ -1554,6 +1554,7 @@
"source": "Источник", "source": "Источник",
"source-aligned": "Выровненный по источнику", "source-aligned": "Выровненный по источнику",
"source-column": "Источник столбца", "source-column": "Источник столбца",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Сопоставление по источнику события", "source-match": "Сопоставление по источнику события",
"source-plural": "Источники", "source-plural": "Источники",

View File

@ -1554,6 +1554,7 @@
"source": "แหล่งที่มา", "source": "แหล่งที่มา",
"source-aligned": "ตรงตามแหล่งที่มา", "source-aligned": "ตรงตามแหล่งที่มา",
"source-column": "คอลัมน์แหล่งที่มา", "source-column": "คอลัมน์แหล่งที่มา",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "จับคู่ตามแหล่งที่มาของเหตุการณ์", "source-match": "จับคู่ตามแหล่งที่มาของเหตุการณ์",
"source-plural": "แหล่งที่มาหลายรายการ", "source-plural": "แหล่งที่มาหลายรายการ",

View File

@ -1554,6 +1554,7 @@
"source": "Kaynak", "source": "Kaynak",
"source-aligned": "Kaynak Odaklı", "source-aligned": "Kaynak Odaklı",
"source-column": "Kaynak Sütun", "source-column": "Kaynak Sütun",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "Olay Kaynağına Göre Eşleştir", "source-match": "Olay Kaynağına Göre Eşleştir",
"source-plural": "Kaynaklar", "source-plural": "Kaynaklar",

View File

@ -1554,6 +1554,7 @@
"source": "源", "source": "源",
"source-aligned": "Source-aligned", "source-aligned": "Source-aligned",
"source-column": "源列", "source-column": "源列",
"source-column-plural": "Source Columns",
"source-label": "Source:", "source-label": "Source:",
"source-match": "根据事件源匹配", "source-match": "根据事件源匹配",
"source-plural": "来源", "source-plural": "来源",

View File

@ -1554,6 +1554,7 @@
"source": "來源", "source": "來源",
"source-aligned": "來源導向", "source-aligned": "來源導向",
"source-column": "來源欄位", "source-column": "來源欄位",
"source-column-plural": "Source Columns",
"source-label": "來源:", "source-label": "來源:",
"source-match": "依事件來源比對", "source-match": "依事件來源比對",
"source-plural": "來源", "source-plural": "來源",