mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-25 07:04:43 +00:00
* fix contract old schema column not visible on schem form while edit * fix the unit test failing * show column status, represent which column is being failed and passed * fix the dropdown scrolling with screen and fix sonar issue as well
This commit is contained in:
parent
3fb800cabc
commit
c903f3b485
@ -1418,6 +1418,144 @@ test.describe('Data Contracts', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('Operation on Old Schema Columns Contract', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
const { apiContext } = await getApiContext(page);
|
||||
const table = new TableClass();
|
||||
await table.create(apiContext);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await table.visitEntityPage(page);
|
||||
|
||||
const entityFQN = table.entityResponseData.fullyQualifiedName;
|
||||
|
||||
await page.click('[data-testid="contract"]');
|
||||
await page.waitForSelector('[data-testid="loader"]', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
await page.getByTestId('add-contract-button').click();
|
||||
|
||||
await expect(page.getByTestId('add-contract-card')).toBeVisible();
|
||||
|
||||
await page.getByTestId('contract-name').fill(DATA_CONTRACT_DETAILS.name);
|
||||
|
||||
await page.getByRole('tab', { name: 'Schema' }).click();
|
||||
|
||||
await page
|
||||
.locator('input[type="checkbox"][aria-label="Select all"]')
|
||||
.check();
|
||||
|
||||
await expect(
|
||||
page.getByRole('checkbox', { name: 'Select all' })
|
||||
).toBeChecked();
|
||||
|
||||
// save and trigger contract validation
|
||||
await saveAndTriggerDataContractValidation(page, true);
|
||||
|
||||
await expect(
|
||||
page.getByTestId('contract-status-card-item-schema-status')
|
||||
).toContainText('Passed');
|
||||
|
||||
// Modify the first 2 columns with PATCH API
|
||||
await table.patch({
|
||||
apiContext,
|
||||
patchData: [
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/columns/0/name',
|
||||
value: 'new_column_0',
|
||||
},
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/columns/0/fullyQualifiedName',
|
||||
value: `${table.entityResponseData.fullyQualifiedName}.new_column_0`,
|
||||
},
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/columns/1/name',
|
||||
value: 'new_column_1',
|
||||
},
|
||||
{
|
||||
op: 'replace',
|
||||
path: '/columns/1/fullyQualifiedName',
|
||||
value: `${table.entityResponseData.fullyQualifiedName}.new_column_1`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Run Contract After Schema Change should Fail
|
||||
await page.getByTestId('manage-contract-actions').click();
|
||||
|
||||
await page.waitForSelector('.contract-action-dropdown', {
|
||||
state: 'visible',
|
||||
});
|
||||
|
||||
await page.getByTestId('contract-run-now-button').click();
|
||||
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('[data-testid="loader"]', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.getByTestId('contract-status-card-item-schema-status')
|
||||
).toContainText('Failed');
|
||||
await expect(
|
||||
page.getByTestId('data-contract-latest-result-btn')
|
||||
).toContainText('Contract Failed');
|
||||
|
||||
await expect(
|
||||
page.getByTestId(`schema-column-${table.entityLinkColumnsName[0]}-failed`)
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.getByTestId(`schema-column-${table.entityLinkColumnsName[1]}-failed`)
|
||||
).toBeVisible();
|
||||
|
||||
// Check the Columns Present in Contract Schema Form Component
|
||||
|
||||
await page.getByTestId('manage-contract-actions').click();
|
||||
|
||||
await page.waitForSelector('.contract-action-dropdown', {
|
||||
state: 'visible',
|
||||
});
|
||||
await page.getByTestId('contract-edit-button').click();
|
||||
|
||||
await page.getByRole('tab', { name: 'Schema' }).click();
|
||||
|
||||
// Old column should be visible and we should un-check them
|
||||
await page
|
||||
.locator(
|
||||
`[data-row-key="${entityFQN}.${table.entityLinkColumnsName[0]}"] .ant-checkbox-input`
|
||||
)
|
||||
.click();
|
||||
|
||||
await page
|
||||
.locator(
|
||||
`[data-row-key="${entityFQN}.${table.entityLinkColumnsName[1]}"] .ant-checkbox-input`
|
||||
)
|
||||
.click();
|
||||
|
||||
// Select newly added column
|
||||
await page
|
||||
.locator(`[data-row-key="${entityFQN}.new_column_0"] .ant-checkbox-input`)
|
||||
.click();
|
||||
await page
|
||||
.locator(`[data-row-key="${entityFQN}.new_column_1"] .ant-checkbox-input`)
|
||||
.click();
|
||||
|
||||
// save and trigger contract validation
|
||||
await saveAndTriggerDataContractValidation(page, true);
|
||||
|
||||
await expect(
|
||||
page.getByTestId('contract-status-card-item-schema-status')
|
||||
).toContainText('Passed');
|
||||
});
|
||||
|
||||
test('should allow adding a semantic with multiple rules', async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@ -55,6 +55,7 @@ import {
|
||||
} from '../../../utils/DataContract/DataContractUtils';
|
||||
import { customFormatDateTime } from '../../../utils/date-time/DateTimeUtils';
|
||||
import { getEntityName } from '../../../utils/EntityUtils';
|
||||
import { getPopupContainer } from '../../../utils/formUtils';
|
||||
import { pruneEmptyChildren } from '../../../utils/TableUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
|
||||
import AlertBar from '../../AlertBar/AlertBar';
|
||||
@ -254,6 +255,7 @@ const ContractDetail: React.FC<{
|
||||
|
||||
<Dropdown
|
||||
destroyPopupOnHide
|
||||
getPopupContainer={getPopupContainer}
|
||||
menu={{
|
||||
items: contractActionsItems,
|
||||
onClick: handleContractAction,
|
||||
@ -481,6 +483,9 @@ const ContractDetail: React.FC<{
|
||||
|
||||
<ContractSchemaTable
|
||||
contractStatus={constraintStatus['schema']}
|
||||
latestSchemaValidationResult={
|
||||
latestContractResults?.schemaValidation
|
||||
}
|
||||
schemaDetail={schemaDetail}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
.ant-card-head {
|
||||
border-bottom: none;
|
||||
|
||||
.ant-card-head-title {
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.contract-header-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@ -26,7 +26,7 @@ import {
|
||||
import { TABLE_COLUMNS_KEYS } from '../../../constants/TableKeys.constants';
|
||||
import { EntityType, FqnPart } from '../../../enums/entity.enum';
|
||||
import { DataContract } from '../../../generated/entity/data/dataContract';
|
||||
import { Column } from '../../../generated/entity/data/table';
|
||||
import { Column, Table } from '../../../generated/entity/data/table';
|
||||
import { TagSource } from '../../../generated/tests/testCase';
|
||||
import { TagLabel } from '../../../generated/type/tagLabel';
|
||||
import { usePaging } from '../../../hooks/paging/usePaging';
|
||||
@ -40,7 +40,8 @@ import {
|
||||
import Fqn from '../../../utils/Fqn';
|
||||
import { pruneEmptyChildren } from '../../../utils/TableUtils';
|
||||
import { PagingHandlerParams } from '../../common/NextPrevious/NextPrevious.interface';
|
||||
import Table from '../../common/Table/Table';
|
||||
import AntTable from '../../common/Table/Table';
|
||||
import { useGenericContext } from '../../Customization/GenericProvider/GenericProvider';
|
||||
import { TableCellRendered } from '../../Database/SchemaTable/SchemaTable.interface';
|
||||
import TableTags from '../../Database/TableTags/TableTags.component';
|
||||
|
||||
@ -54,7 +55,8 @@ export const ContractSchemaFormTab: React.FC<{
|
||||
}> = ({ selectedSchema, onNext, onChange, onPrev, nextLabel, prevLabel }) => {
|
||||
const { t } = useTranslation();
|
||||
const { fqn } = useFqn();
|
||||
const [allColumnsData, setAllColumnData] = useState<Column[]>([]);
|
||||
const { data: tableData } = useGenericContext();
|
||||
const [allColumnsData, setAllColumnsData] = useState<Column[]>([]);
|
||||
const [columnsData, setColumnsData] = useState<Column[]>([]);
|
||||
const [selectedKeys, setSelectedKeys] = useState<string[]>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -95,6 +97,17 @@ export const ContractSchemaFormTab: React.FC<{
|
||||
[allColumnsData, onChange]
|
||||
);
|
||||
|
||||
// Old Columns which are available in Contract but being Modified/Removed at Table Schema Level
|
||||
const oldRemovedColumns = useMemo(() => {
|
||||
const columnsDataFQN = new Set(
|
||||
(tableData as Table).columns.map((col) => col.fullyQualifiedName)
|
||||
);
|
||||
|
||||
return selectedSchema.filter(
|
||||
(col) => !columnsDataFQN.has(col.fullyQualifiedName)
|
||||
);
|
||||
}, [selectedSchema, tableData]);
|
||||
|
||||
const fetchTableColumns = useCallback(
|
||||
async (page = 1) => {
|
||||
if (!tableFqn) {
|
||||
@ -112,9 +125,18 @@ export const ContractSchemaFormTab: React.FC<{
|
||||
});
|
||||
|
||||
const prunedColumns = pruneEmptyChildren(response.data);
|
||||
setColumnsData(prunedColumns);
|
||||
setAllColumnData((prev) => {
|
||||
const combined = [...prev, ...selectedSchema, ...prunedColumns];
|
||||
const oldPrunedColumns = pruneEmptyChildren(oldRemovedColumns);
|
||||
// should render the oldPrunedColumns only on the first page, if there is pagination
|
||||
setColumnsData(
|
||||
offset === 0 ? [...oldPrunedColumns, ...prunedColumns] : prunedColumns
|
||||
);
|
||||
setAllColumnsData((prev) => {
|
||||
const combined = [
|
||||
...prev,
|
||||
...selectedSchema,
|
||||
...oldPrunedColumns,
|
||||
...prunedColumns,
|
||||
];
|
||||
|
||||
return uniqBy(combined, 'fullyQualifiedName');
|
||||
});
|
||||
@ -131,7 +153,7 @@ export const ContractSchemaFormTab: React.FC<{
|
||||
}
|
||||
setIsLoading(false);
|
||||
},
|
||||
[tableFqn, pageSize, selectedSchema, setAllColumnData]
|
||||
[tableFqn, pageSize, selectedSchema, oldRemovedColumns, setAllColumnsData]
|
||||
);
|
||||
|
||||
const handleColumnsPageChange = useCallback(
|
||||
@ -295,7 +317,7 @@ export const ContractSchemaFormTab: React.FC<{
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedKeys(
|
||||
selectedSchema.map((item) => (item as Column).fullyQualifiedName ?? '')
|
||||
selectedSchema.map((item) => item.fullyQualifiedName ?? '')
|
||||
);
|
||||
}, [selectedSchema]);
|
||||
|
||||
@ -314,7 +336,7 @@ export const ContractSchemaFormTab: React.FC<{
|
||||
{t('message.data-contract-schema-description')}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
<Table
|
||||
<AntTable
|
||||
columns={columns}
|
||||
customPaginationProps={paginationProps}
|
||||
dataSource={columnsData}
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
} from '@testing-library/react';
|
||||
import { Column } from '../../../generated/entity/data/table';
|
||||
import { useFqn } from '../../../hooks/useFqn';
|
||||
import { mockTableData } from '../../../mocks/TableVersion.mock';
|
||||
import { getTableColumnsByFQN } from '../../../rest/tableAPI';
|
||||
import { ContractSchemaFormTab } from './ContractScehmaFormTab';
|
||||
|
||||
@ -50,6 +51,12 @@ jest.mock('../../../utils/TableUtils', () => ({
|
||||
pruneEmptyChildren: jest.fn().mockImplementation((columns) => columns),
|
||||
}));
|
||||
|
||||
jest.mock('../../Customization/GenericProvider/GenericProvider', () => ({
|
||||
useGenericContext: jest.fn().mockImplementation(() => ({
|
||||
data: mockTableData,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../common/Table/Table', () => {
|
||||
return function MockTable({
|
||||
columns,
|
||||
|
||||
@ -12,11 +12,16 @@
|
||||
*/
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Col, Row, Tag, Typography } from 'antd';
|
||||
import { ColumnsType, ColumnType, TablePaginationConfig } from 'antd/lib/table';
|
||||
import classNames from 'classnames';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as ArrowIcon } from '../../../assets/svg/arrow-right-full.svg';
|
||||
import { ReactComponent as FailedIcon } from '../../../assets/svg/fail-badge.svg';
|
||||
import { ReactComponent as CompletedIcon } from '../../../assets/svg/ic-check-circle-colored.svg';
|
||||
import { LIST_SIZE, NO_DATA_PLACEHOLDER } from '../../../constants/constants';
|
||||
import { Column } from '../../../generated/entity/data/table';
|
||||
import { SchemaValidation } from '../../../generated/entity/datacontract/dataContractResult';
|
||||
import { getContractStatusType } from '../../../utils/DataContract/DataContractUtils';
|
||||
import StatusBadgeV2 from '../../common/StatusBadge/StatusBadgeV2.component';
|
||||
import Table from '../../common/Table/Table';
|
||||
@ -25,10 +30,23 @@ import './contract-schema.less';
|
||||
const ContractSchemaTable: React.FC<{
|
||||
schemaDetail: Column[];
|
||||
contractStatus?: string;
|
||||
}> = ({ schemaDetail, contractStatus }) => {
|
||||
latestSchemaValidationResult?: SchemaValidation;
|
||||
}> = ({ schemaDetail, contractStatus, latestSchemaValidationResult }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const schemaColumns = useMemo(
|
||||
const tablePaginationProps: TablePaginationConfig = useMemo(
|
||||
() => ({
|
||||
size: 'default',
|
||||
hideOnSinglePage: true,
|
||||
pageSize: LIST_SIZE,
|
||||
prevIcon: <Icon component={ArrowIcon} />,
|
||||
nextIcon: <Icon component={ArrowIcon} />,
|
||||
className: 'schema-custom-pagination',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const schemaColumns: ColumnsType<Column> = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('label.name'),
|
||||
@ -66,8 +84,31 @@ const ContractSchemaTable: React.FC<{
|
||||
</div>
|
||||
),
|
||||
},
|
||||
...(latestSchemaValidationResult
|
||||
? [
|
||||
{
|
||||
title: t('label.column-status'),
|
||||
dataIndex: 'name',
|
||||
key: 'columnStatus',
|
||||
align: 'center',
|
||||
render: (name: string) => {
|
||||
const isColumnFailed =
|
||||
latestSchemaValidationResult?.failedFields?.includes(name);
|
||||
const iconClass = isColumnFailed ? 'failed' : 'success';
|
||||
|
||||
return (
|
||||
<Icon
|
||||
className={classNames('column-status-icon', iconClass)}
|
||||
component={isColumnFailed ? FailedIcon : CompletedIcon}
|
||||
data-testid={`schema-column-${name}-${iconClass}`}
|
||||
/>
|
||||
);
|
||||
},
|
||||
} as ColumnType<Column>,
|
||||
]
|
||||
: []),
|
||||
],
|
||||
[]
|
||||
[latestSchemaValidationResult]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -76,14 +117,7 @@ const ContractSchemaTable: React.FC<{
|
||||
<Table
|
||||
columns={schemaColumns}
|
||||
dataSource={schemaDetail}
|
||||
pagination={{
|
||||
size: 'default',
|
||||
hideOnSinglePage: true,
|
||||
pageSize: LIST_SIZE,
|
||||
prevIcon: <Icon component={ArrowIcon} />,
|
||||
nextIcon: <Icon component={ArrowIcon} />,
|
||||
className: 'schema-custom-pagination',
|
||||
}}
|
||||
pagination={tablePaginationProps}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
@ -55,10 +55,42 @@ describe('ContractSchemaTable', () => {
|
||||
expect(screen.getByText('label.name')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.type')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.constraint-plural')).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText('label.column-status')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('StatusBadgeV2')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render ColumnStatus column when latestSchemaValidationResult present', () => {
|
||||
render(
|
||||
<ContractSchemaTable
|
||||
latestSchemaValidationResult={{
|
||||
failed: 1,
|
||||
failedFields: ['name'],
|
||||
passed: 5,
|
||||
total: 6,
|
||||
}}
|
||||
schemaDetail={mockSchemaDetail}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('label.column-status')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('schema-column-name-failed')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('schema-column-id-success')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('schema-column-email-success')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('schema-column-contract-success')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('schema-column-property-success')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Since being on second page
|
||||
expect(
|
||||
screen.queryByTestId('schema-column-business-success')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render schema table with pagination', () => {
|
||||
render(<ContractSchemaTable schemaDetail={mockSchemaDetail} />);
|
||||
|
||||
|
||||
@ -13,6 +13,15 @@
|
||||
|
||||
@import (reference) '../../../styles/variables.less';
|
||||
|
||||
.contract-schema-component-container {
|
||||
.column-status-icon {
|
||||
font-size: 16px;
|
||||
&.success {
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pagination.schema-custom-pagination {
|
||||
align-items: center;
|
||||
&.ant-table-pagination-right {
|
||||
|
||||
@ -54,7 +54,7 @@ export const ContractTab = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
const handleDelete = () => {
|
||||
if (contract?.id) {
|
||||
setIsDeleteModalVisible(true);
|
||||
}
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Spaltenname",
|
||||
"column-plural": "Spalten",
|
||||
"column-profile": "Spaltenprofil",
|
||||
"column-status": "Spaltenstatus",
|
||||
"comment": "Kommentar",
|
||||
"comment-lowercase": "kommentar",
|
||||
"comment-plural": "Kommentare",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Column Name",
|
||||
"column-plural": "Columns",
|
||||
"column-profile": "Column Profile",
|
||||
"column-status": "Column Status",
|
||||
"comment": "Comment",
|
||||
"comment-lowercase": "comment",
|
||||
"comment-plural": "Comments",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Nombre de columna",
|
||||
"column-plural": "Columnas",
|
||||
"column-profile": "Perfilado de columnas",
|
||||
"column-status": "Estado de la columna",
|
||||
"comment": "Comentar",
|
||||
"comment-lowercase": "comentario",
|
||||
"comment-plural": "Comentarios",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Nom de colonne",
|
||||
"column-plural": "Colonnes",
|
||||
"column-profile": "Profil de Colonne",
|
||||
"column-status": "Statut de la colonne",
|
||||
"comment": "Commentaire",
|
||||
"comment-lowercase": "commentaire",
|
||||
"comment-plural": "Commentaires",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Nome da columna",
|
||||
"column-plural": "Columnas",
|
||||
"column-profile": "Perfil da columna",
|
||||
"column-status": "Estado da columna",
|
||||
"comment": "Comentario",
|
||||
"comment-lowercase": "comentario",
|
||||
"comment-plural": "Comentarios",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "שם עמודה",
|
||||
"column-plural": "עמודות",
|
||||
"column-profile": "פרופיל עמודה",
|
||||
"column-status": "סטטוס עמודה",
|
||||
"comment": "תגובה",
|
||||
"comment-lowercase": "תגובה",
|
||||
"comment-plural": "תגובות",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "カラム名",
|
||||
"column-plural": "カラム",
|
||||
"column-profile": "カラムプロファイル",
|
||||
"column-status": "カラムステータス",
|
||||
"comment": "コメント",
|
||||
"comment-lowercase": "コメント",
|
||||
"comment-plural": "コメント",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "열 이름",
|
||||
"column-plural": "열들",
|
||||
"column-profile": "열 프로필",
|
||||
"column-status": "열 상태",
|
||||
"comment": "댓글",
|
||||
"comment-lowercase": "댓글",
|
||||
"comment-plural": "댓글들",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "स्तंभ नाव",
|
||||
"column-plural": "स्तंभ",
|
||||
"column-profile": "स्तंभ प्रोफाइल",
|
||||
"column-status": "स्तंभ स्थिती",
|
||||
"comment": "टिप्पणी",
|
||||
"comment-lowercase": "टिप्पणी",
|
||||
"comment-plural": "टिप्पण्या",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Kolomnaam",
|
||||
"column-plural": "Kolommen",
|
||||
"column-profile": "Kolomprofiel",
|
||||
"column-status": "Kolomstatus",
|
||||
"comment": "Opmerking",
|
||||
"comment-lowercase": "opmerking",
|
||||
"comment-plural": "Opmerkingen",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "نام ستون",
|
||||
"column-plural": "ستونها",
|
||||
"column-profile": "پروفایل ستون",
|
||||
"column-status": "وضعیت ستون",
|
||||
"comment": "نظر",
|
||||
"comment-lowercase": "نظر",
|
||||
"comment-plural": "Comentarios",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Nome da coluna",
|
||||
"column-plural": "Colunas",
|
||||
"column-profile": "Perfil da Coluna",
|
||||
"column-status": "Status da coluna",
|
||||
"comment": "Comentário",
|
||||
"comment-lowercase": "comentário",
|
||||
"comment-plural": "Comentários",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Nome da coluna",
|
||||
"column-plural": "Colunas",
|
||||
"column-profile": "Perfil da Coluna",
|
||||
"column-status": "Estado da coluna",
|
||||
"comment": "Comentário",
|
||||
"comment-lowercase": "comentário",
|
||||
"comment-plural": "Comentários",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Имя столбца",
|
||||
"column-plural": "Столбцы",
|
||||
"column-profile": "Профиль столбца",
|
||||
"column-status": "Статус столбца",
|
||||
"comment": "Комментарий",
|
||||
"comment-lowercase": "комментарий",
|
||||
"comment-plural": "Комментарии",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "ชื่อคอลัมน์",
|
||||
"column-plural": "คอลัมน์หลายรายการ",
|
||||
"column-profile": "โปรไฟล์คอลัมน์",
|
||||
"column-status": "สถานะคอลัมน์",
|
||||
"comment": "ความคิดเห็น",
|
||||
"comment-lowercase": "ความคิดเห็น",
|
||||
"comment-plural": "ความคิดเห็น",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "Sütun adı",
|
||||
"column-plural": "Sütunlar",
|
||||
"column-profile": "Sütun Profili",
|
||||
"column-status": "Sütun Durumu",
|
||||
"comment": "Yorum",
|
||||
"comment-lowercase": "yorum",
|
||||
"comment-plural": "Yorumlar",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "列名",
|
||||
"column-plural": "列",
|
||||
"column-profile": "列分析",
|
||||
"column-status": "列状态",
|
||||
"comment": "评论",
|
||||
"comment-lowercase": "评论",
|
||||
"comment-plural": "评论",
|
||||
|
||||
@ -262,6 +262,7 @@
|
||||
"column-name": "欄位名稱",
|
||||
"column-plural": "欄位",
|
||||
"column-profile": "欄位分析",
|
||||
"column-status": "欄位狀態",
|
||||
"comment": "留言",
|
||||
"comment-lowercase": "留言",
|
||||
"comment-plural": "留言",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user