Skip to content

Commit

Permalink
[PLAT-15062][PLAT-15071] Support DB scoped on UI and display schema c…
Browse files Browse the repository at this point in the history
…hange mode information

Summary:
**Context**
As we now support DB scoped replication on the YBDB and DB scoped replication for DR on YBA,
we need to make some changes to the YBA UI to help users take advantage of this new DDL replication mode.

**Changes**
- Add support for creating DB scoped DR configs on the YBA UI.
- Add schema change DDL mode labels, modals, popovers, and doc links
   - These UI additions are made to help users understand which "version/variant" of DDL mode they
      currently using and what steps they need to take when making DDL changes.

Test Plan:
Case: Both universes support DB scoped DR
- Create xCluster DR and verify the DR config is DB scoped.
- Verify the schema change mode is 'semi-automatic' and the corresponding modals, popovers and
   doc links are as expected.
{F284695}

Case: Both universes do not support DB scoped DR
- Create xCluster DR and verify the DR config is not DB scoped (it should just be a plain txn xCluster config).
     - Verify the schema change mode is 'manual' and the corresponding modals, popovers and
   doc links are as expected.
- Upgrade one of the universes to a db software version which supports DB scoped DR
     - Verify no 'upgrade available' message is present.
- Upgrade the other universe.
     - Verify the user is shown an 'upgrade available' link which brings them to the expected upgrade available modal.

{F283397}

Reviewers: rmadhavan, cwang, hzare, vbansal, mkazerooni, kkannan, lsangappa

Reviewed By: rmadhavan

Subscribers: mkazerooni, yugaware

Differential Revision: https://phorge.dev.yugabyte.com/D37876
  • Loading branch information
Jethro-M committed Sep 11, 2024
1 parent e013578 commit 2b6a2d3
Show file tree
Hide file tree
Showing 37 changed files with 1,189 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.yugabyte.yw.models.DrConfig;
import com.yugabyte.yw.models.PitrConfig;
import com.yugabyte.yw.models.XClusterConfig;
import com.yugabyte.yw.models.XClusterConfig.ConfigType;
import com.yugabyte.yw.models.XClusterConfig.TableType;
import com.yugabyte.yw.models.XClusterConfig.XClusterConfigStatusType;
import com.yugabyte.yw.models.XClusterNamespaceConfig;
Expand Down Expand Up @@ -98,6 +99,11 @@ public TableType getTableType() {
return xClusterConfig.getTableType();
}

@ApiModelProperty(value = "Whether the config is basic, txn, or db scoped xCluster")
public ConfigType getType() {
return xClusterConfig.getType();
}

@ApiModelProperty(value = "Whether the underlying xCluster config is paused")
public boolean isPaused() {
return xClusterConfig.isPaused();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public static ConfigType getFromString(@Nullable String value) {
}
}

@ApiModelProperty(value = "Whether the config is txn xCluster")
@ApiModelProperty(value = "Whether the config is basic, txn, or db scoped xCluster")
private ConfigType type;

@ApiModelProperty(value = "Whether the source is active in txn xCluster")
Expand Down
110 changes: 86 additions & 24 deletions managed/ui/src/components/tables/ListTables/ListTables.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Component, Fragment } from 'react';
import { Link } from 'react-router';
import { Image, ProgressBar, ButtonGroup, DropdownButton } from 'react-bootstrap';
import { toast } from 'react-toastify';
import { Typography } from '@material-ui/core';

import tableIcon from '../images/table.png';
import { isNonEmptyArray } from '../../../utils/ObjectUtils';
import { TableAction } from '../../tables';
Expand All @@ -12,16 +14,19 @@ import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
import 'react-bootstrap-table/css/react-bootstrap-table.css';
import _ from 'lodash';
import { getPromiseState } from '../../../utils/PromiseUtils';
import { YBResourceCount } from '../../common/descriptors';
import { YBBanner, YBBannerVariant, YBResourceCount } from '../../common/descriptors';
import { isDisabled, isNotHidden } from '../../../utils/LayoutUtils';
import { formatSchemaName } from '../../../utils/Formatters';
import { YBButtonLink } from '../../common/forms/fields';
import { YBButton } from '../../../redesign/components';
import {
getTableName,
getTableUuid,
isColocatedChildTable,
isColocatedParentTable
} from '../../../utils/tableUtils';
import { SchemaChangeModeInfoModal } from '../../xcluster/sharedComponents/SchemaChangeInfoModal';
import { getPrimaryCluster } from '../../../utils/universeUtilsTyped';

import './ListTables.scss';

Expand Down Expand Up @@ -79,13 +84,20 @@ class TableTitle extends Component {
export default class ListTables extends Component {
constructor(props) {
super(props);
this.state = { currentView: 'listTables' };
this.state = { currentView: 'listTables', isSchemaChangeInfoModalOpen: false };
}

showListTables = () => {
this.setState({ currentView: 'listTables' });
};

openSchemaChangeInfoModal = () => {
this.setState({ isSchemaChangeInfoModalOpen: true });
};
closeSchemaChangeInfoModal = () => {
this.setState({ isSchemaChangeInfoModalOpen: false });
};

render() {
const self = this;
const { tables } = this.props;
Expand All @@ -104,19 +116,67 @@ export default class ListTables extends Component {
});
}

const currentUniverse = this.props.universe.currentUniverse.data;
const hasTablesInXClusterReplication = !!(
currentUniverse.drConfigUuidsAsSource.length ||
currentUniverse.drConfigUuidsAsTarget.length ||
currentUniverse.universeDetails.xclusterInfo.sourceXClusterConfigs.length ||
currentUniverse.universeDetails.xclusterInfo.sourceXClusterConfigs.length
);

const clusters = currentUniverse?.universeDetails.clusters;
const currentUniverseVersion = clusters
? getPrimaryCluster(clusters)?.userIntent.ybSoftwareVersion ?? ''
: '';

if (tables.currentTableView === 'list') {
return (
<YBPanelItem
header={
<TableTitle
numRedisTables={numRedisTables}
numCassandraTables={numCassandraTables}
numPostgresTables={numPostgresTables}
{...this.props}
/>
}
body={<ListTableGrid {...this.props} />}
/>
<>
{hasTablesInXClusterReplication && (
<>
<YBBanner variant={YBBannerVariant.INFO} isFeatureBanner={true}>
<div className="universe-tables-bannerContainer">
<Typography variant="body2">
<b>One or more tables in this universe is involved with xCluster.</b> Before
making schema changes to tables in replication, review the steps for making
manual schema changes.
</Typography>
<div className="universe-tables-bannerActionButtonContainer">
<YBButton
variant="secondary"
size="large"
onClick={this.openSchemaChangeInfoModal}
>
Learn More
</YBButton>
</div>
</div>
</YBBanner>
{this.state.isSchemaChangeInfoModalOpen && (
<SchemaChangeModeInfoModal
currentUniverseVersion={currentUniverseVersion}
isDrInterface={true}
isConfigInterface={false}
modalProps={{
open: this.state.isSchemaChangeInfoModalOpen,
onClose: this.closeSchemaChangeInfoModal
}}
/>
)}
</>
)}
<YBPanelItem
header={
<TableTitle
numRedisTables={numRedisTables}
numCassandraTables={numCassandraTables}
numPostgresTables={numPostgresTables}
{...this.props}
/>
}
body={<ListTableGrid {...this.props} />}
/>
</>
);
} else {
return <span />;
Expand Down Expand Up @@ -180,7 +240,7 @@ class ListTableGrid extends Component {
/>
]);
}
if(actions.length === 0) return null;
if (actions.length === 0) return null;
return (
<ButtonGroup>
<DropdownButton
Expand Down Expand Up @@ -334,16 +394,18 @@ class ListTableGrid extends Component {
>
WAL Size
</TableHeaderColumn>
{!universePaused && isNotHidden(currentCustomer.data.features, 'universes.backup') && sortedListItems.filter(t => t.tableType === 'YQL_TABLE_TYPE').length > 0 && (
<TableHeaderColumn
dataField={'actions'}
columnClassName={'yb-actions-cell'}
width="10%"
dataFormat={formatActionButtons}
>
Actions
</TableHeaderColumn>
)}
{!universePaused &&
isNotHidden(currentCustomer.data.features, 'universes.backup') &&
sortedListItems.filter((t) => t.tableType === 'YQL_TABLE_TYPE').length > 0 && (
<TableHeaderColumn
dataField={'actions'}
columnClassName={'yb-actions-cell'}
width="10%"
dataFormat={formatActionButtons}
>
Actions
</TableHeaderColumn>
)}
</BootstrapTable>
);
return <Fragment>{tableListDisplay}</Fragment>;
Expand Down
12 changes: 12 additions & 0 deletions managed/ui/src/components/tables/ListTables/ListTables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
}
}

.universe-tables-bannerContainer {
display: flex;
align-items: center;
}

.universe-tables-bannerActionButtonContainer {
display: flex;
gap: 8px;

margin-left: auto;
}

.table-search-bar {
position: relative;
width: 200px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,11 @@ class UniverseDetail extends Component {
(config) => config.key === RuntimeConfigKey.ENABLE_NODE_AGENT
)?.value === 'true';
runtimeConfigs?.data?.configEntries?.find(
(config) => config.key === RuntimeConfigKey.PERFOMANCE_ADVISOR_UI_FEATURE_FLAG
(config) => config.key === RuntimeConfigKey.PERFORMANCE_ADVISOR_UI_FEATURE_FLAG
)?.value === 'true';
const isPerfAdvisorEnabled =
runtimeConfigs?.data?.configEntries?.find(
(config) => config.key === RuntimeConfigKey.PERFOMANCE_ADVISOR_UI_FEATURE_FLAG
(config) => config.key === RuntimeConfigKey.PERFORMANCE_ADVISOR_UI_FEATURE_FLAG
)?.value === 'true';
const isDrEnabled =
runtimeConfigs?.data?.configEntries?.find(
Expand Down
42 changes: 40 additions & 2 deletions managed/ui/src/components/xcluster/ReplicationUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import {
I18N_KEY_PREFIX_XCLUSTER_TABLE_STATUS,
UNCONFIGURED_XCLUSTER_TABLE_STATUSES,
DROPPED_XCLUSTER_TABLE_STATUSES,
BootstrapCategory
BootstrapCategory,
XClusterSchemaChangeMode,
DB_SCOPED_XCLUSTER_VERSION_THRESHOLD_STABLE,
DB_SCOPED_XCLUSTER_VERSION_THRESHOLD_PREVIEW
} from './constants';
import {
alertConfigQueryKey,
Expand All @@ -31,7 +34,11 @@ import { getUniverseStatus } from '../universes/helpers/universeHelpers';
import { UnavailableUniverseStates } from '../../redesign/helpers/constants';
import { assertUnreachableCase } from '../../utils/errorHandlingUtils';
import { SortOrder } from '../../redesign/helpers/constants';
import { compareYBSoftwareVersions, getPrimaryCluster } from '../../utils/universeUtilsTyped';
import {
compareYBSoftwareVersions,
compareYBSoftwareVersionsWithReleaseTrack,
getPrimaryCluster
} from '../../utils/universeUtilsTyped';

import {
Metrics,
Expand Down Expand Up @@ -750,6 +757,37 @@ export const getIsXClusterConfigAllBidirectional = (
);
};

export const getSchemaChangeMode = (xClusterConfig: XClusterConfig) => {
switch (xClusterConfig.type) {
case XClusterConfigType.BASIC:
case XClusterConfigType.TXN:
return XClusterSchemaChangeMode.TABLE_LEVEL;
case XClusterConfigType.DB_SCOPED:
return XClusterSchemaChangeMode.DB_SCOPED;
}
};

export const checkIsDbScopedXClusterSupported = (ybSoftwareVersion: string) =>
compareYBSoftwareVersionsWithReleaseTrack({
version: ybSoftwareVersion,
stableVersion: DB_SCOPED_XCLUSTER_VERSION_THRESHOLD_STABLE,
previewVersion: DB_SCOPED_XCLUSTER_VERSION_THRESHOLD_PREVIEW,
options: { suppressFormatError: true }
});

export const getLatestSchemaChangeModeSupported = (
sourceUniverseVersion: string,
targetUniverseVersion: string
): XClusterSchemaChangeMode => {
if (
checkIsDbScopedXClusterSupported(sourceUniverseVersion) &&
checkIsDbScopedXClusterSupported(targetUniverseVersion)
) {
return XClusterSchemaChangeMode.DB_SCOPED;
}
return XClusterSchemaChangeMode.TABLE_LEVEL;
};

const updateTableStatusWithReplicationLag = (
tableStatus: XClusterTableStatus,
maxAcceptableLag: number | undefined,
Expand Down
Loading

0 comments on commit 2b6a2d3

Please sign in to comment.