Skip to content
Snippets Groups Projects
Unverified Commit a028ddcd authored by Anton Kulyk's avatar Anton Kulyk Committed by GitHub
Browse files

Consistent "Verify" button for models (#19800)

* Accept `renderActions` prop on ModerationActions

* Pass verified icon name to render props fn

* Add `reviewBannerClassName` prop

* Render custom verify button for models

* Avoid render prop
parent 25b2366c
No related branches found
No related tags found
No related merge requests found
Showing
with 62 additions and 93 deletions
import React from "react";
import PropTypes from "prop-types";
import { t } from "ttag";
import { isItemVerified } from "metabase-enterprise/moderation/service";
import { Container, VerifyButton } from "./ModerationActions.styled";
export default ModerationActions;
ModerationActions.propTypes = {
className: PropTypes.string,
onVerify: PropTypes.func,
moderationReview: PropTypes.object,
isDataset: PropTypes.bool,
};
function ModerationActions({
moderationReview,
className,
onVerify,
isDataset,
}) {
const isVerified = isItemVerified(moderationReview);
const hasActions = !!onVerify;
const buttonTitle = isDataset
? t`Verify this model`
: t`Verify this question`;
return hasActions ? (
<Container className={className}>
{!isVerified && (
<VerifyButton data-testid="moderation-verify-action" onClick={onVerify}>
{buttonTitle}
</VerifyButton>
)}
</Container>
) : null;
}
import React from "react";
import ModerationActions from "./ModerationActions";
import { render, screen } from "@testing-library/react";
describe("ModerationActions", () => {
describe("when the user is not a moderator", () => {
it("should not render", () => {
const { queryByTestId } = render(
<ModerationActions isModerator={false} />,
);
expect(queryByTestId("moderation-verify-action")).toBeNull();
expect(screen.queryByText("Moderation")).toBeNull();
});
});
describe("when a moderator clicks on the verify button", () => {
it("should call the onVerify prop", () => {
const onVerify = jest.fn();
const { getByTestId } = render(
<ModerationActions isModerator onVerify={onVerify} />,
);
getByTestId("moderation-verify-action").click();
expect(onVerify).toHaveBeenCalled();
});
});
});
......@@ -40,6 +40,7 @@ ModerationReviewBanner.propTypes = {
user: PropTypes.object,
currentUser: PropTypes.object.isRequired,
onRemove: PropTypes.func,
className: PropTypes.func,
};
export function ModerationReviewBanner({
......@@ -47,6 +48,7 @@ export function ModerationReviewBanner({
user: moderator,
currentUser,
onRemove,
className,
}) {
const [isHovering, setIsHovering] = React.useState(false);
const [isActive, setIsActive] = React.useState(false);
......@@ -69,6 +71,7 @@ export function ModerationReviewBanner({
backgroundColor={alpha(iconColor, 0.2)}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
className={className}
>
<Tooltip tooltip={onRemove && tooltipText}>
{onRemove ? (
......
import React from "react";
import PropTypes from "prop-types";
import { t } from "ttag";
import { connect } from "react-redux";
import { getLatestModerationReview } from "metabase-enterprise/moderation/service";
import { color } from "metabase/lib/colors";
import {
MODERATION_STATUS,
getLatestModerationReview,
getStatusIcon,
isItemVerified,
} from "metabase-enterprise/moderation/service";
import { getIsModerator } from "metabase-enterprise/moderation/selectors";
import {
verifyCard,
removeCardReview,
} from "metabase-enterprise/moderation/actions";
import ModerationActions from "../ModerationActions/ModerationActions";
import ModerationReviewBanner from "../ModerationReviewBanner/ModerationReviewBanner";
import { VerifyButton as DefaultVerifyButton } from "./QuestionModerationSection.styled";
const mapStateToProps = (state, props) => ({
isModerator: getIsModerator(state, props),
......@@ -25,22 +33,33 @@ export default connect(
mapDispatchToProps,
)(QuestionModerationSection);
QuestionModerationSection.VerifyButton = DefaultVerifyButton;
QuestionModerationSection.propTypes = {
question: PropTypes.object.isRequired,
verifyCard: PropTypes.func.isRequired,
removeCardReview: PropTypes.func.isRequired,
isModerator: PropTypes.bool.isRequired,
reviewBannerClassName: PropTypes.string,
VerifyButton: PropTypes.func,
};
const { name: verifiedIconName, color: verifiedIconColor } = getStatusIcon(
MODERATION_STATUS.verified,
);
function QuestionModerationSection({
question,
verifyCard,
removeCardReview,
isModerator,
reviewBannerClassName,
VerifyButton = DefaultVerifyButton,
}) {
const latestModerationReview = getLatestModerationReview(
question.getModerationReviews(),
);
const isVerified = isItemVerified(latestModerationReview);
const onVerify = () => {
const id = question.id();
......@@ -54,13 +73,21 @@ function QuestionModerationSection({
return (
<React.Fragment>
<ModerationActions
moderationReview={latestModerationReview}
onVerify={isModerator && onVerify}
isDataset={question.isDataset()}
/>
{isModerator && !isVerified && (
<VerifyButton
icon={verifiedIconName}
iconColor={color(verifiedIconColor)}
onClick={onVerify}
data-testid="moderation-verify-action"
>
{question.isDataset()
? t`Verify this model`
: t`Verify this question`}
</VerifyButton>
)}
{latestModerationReview && (
<ModerationReviewBanner
className={reviewBannerClassName}
moderationReview={latestModerationReview}
onRemove={isModerator && onRemoveModerationReview}
/>
......
......@@ -6,30 +6,15 @@ import {
getStatusIcon,
} from "metabase-enterprise/moderation/service";
const { name: verifiedIconName, color: verifiedIconColor } = getStatusIcon(
MODERATION_STATUS.verified,
);
import Button from "metabase/core/components/Button";
export const Container = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`;
export const Label = styled.h5`
font-size: 14px;
color: ${color("text-medium")};
flex: 1;
`;
const { color: verifiedIconColor } = getStatusIcon(MODERATION_STATUS.verified);
export const VerifyButton = styled(Button).attrs({
icon: verifiedIconName,
iconSize: 20,
})`
border: none;
color: ${color(verifiedIconColor)};
border: none;
padding: 8px;
&:disabled {
......
......@@ -10,6 +10,8 @@ import {
turnDatasetIntoQuestion,
} from "metabase/query_builder/actions";
import { PLUGIN_MODERATION } from "metabase/plugins";
import DatasetMetadataStrengthIndicator from "./DatasetMetadataStrengthIndicator";
import {
Button,
......@@ -68,6 +70,11 @@ function DatasetManagementSection({
icon="model_framed"
onClick={turnDatasetIntoQuestion}
>{t`Turn back into a saved question`}</Button>
<PLUGIN_MODERATION.QuestionModerationSection
question={dataset}
VerifyButton={Button}
reviewBannerClassName="mt1"
/>
</SectionContent>
</div>
);
......
......@@ -11,6 +11,7 @@ import {
Container,
BorderedSectionContainer,
SidebarPaddedContent,
ModerationSectionContainer,
} from "./QuestionDetailsSidebarPanel.styled";
import DatasetManagementSection from "./DatasetManagementSection";
......@@ -47,7 +48,13 @@ function QuestionDetailsSidebarPanel({ question, onOpenModal }) {
{isDataset && canWrite && (
<DatasetManagementSection dataset={question} />
)}
<PLUGIN_MODERATION.QuestionModerationSection question={question} />
{!isDataset && (
<ModerationSectionContainer>
<PLUGIN_MODERATION.QuestionModerationSection
question={question}
/>
</ModerationSectionContainer>
)}
</BorderedSectionContainer>
</SidebarPaddedContent>
<QuestionActivityTimeline question={question} />
......
......@@ -27,3 +27,11 @@ export const BorderedSectionContainer = styled.div`
border-top: 1px solid ${color("border")};
}
`;
export const ModerationSectionContainer = styled.div`
.Button {
display: flex;
align-items: center;
justify-content: space-between;
}
`;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment