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

Fork Sync: Update from parent repository #49

Merged
merged 12 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .github/workflows/backport.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
ref: `heads/${targetBranch}`,
});

const backportBranch = `backport-${originalPullRequest.head.ref}`
const backportBranch = `backport-${originalPullRequest.head.ref}-${targetBranch}`

try {
await github.rest.git.getRef({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ If you have Java installed:
1. [Download the JAR file for Metabase OSS](https://metabase.com/start/oss/jar).
2. Create a new directory and move the Metabase JAR into it.
3. Change into your new Metabase directory and run the JAR.
```
java -jar metabase.jar
```

```
java -jar metabase.jar
```

Metabase will log its progress in the terminal as it starts up. Wait until you see "Metabase Initialization Complete" and visit `http://localhost:3000/setup`.

Expand Down
140 changes: 139 additions & 1 deletion e2e/test/scenarios/admin-2/whitelabel.cy.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { describeEE, restore, setTokenFeatures } from "e2e/support/helpers";
import {
appBar,
describeEE,
main,
popover,
restore,
setTokenFeatures,
} from "e2e/support/helpers";
import { ORDERS_QUESTION_ID } from "e2e/support/cypress_sample_instance_data";

function checkFavicon() {
Expand Down Expand Up @@ -151,6 +158,130 @@ describeEE("formatting > whitelabel", () => {
cy.get("body").should("have.css", "font-family", `"${font}", sans-serif`);
});
});

describe("Help link", () => {
beforeEach(() => {
cy.intercept("PUT", "/api/setting/help-link").as("putHelpLink");
cy.intercept("PUT", "/api/setting/help-link-custom-destination").as(
"putHelpLinkUrl",
);
});

it("should allow customising the help link", () => {
cy.log("Hide Help link");

cy.signInAsAdmin();
cy.visit("/admin/settings/whitelabel");

cy.findByLabelText("Link to Metabase help").should("be.checked");

cy.findByTestId("help-link-setting").findByText("Hide it").click();
cy.wait("@putHelpLink");

cy.signInAsNormalUser();

cy.visit("/");
openSettingsMenu();
helpLink().should("not.exist");

cy.log("Set custom Help link");

cy.signInAsAdmin();
cy.visit("/admin/settings/whitelabel");

cy.findByTestId("help-link-setting")
.findByText("Go to a custom destination...")
.click();

getHelpLinkCustomDestinationInput()
.should("have.focus")
.type("https://example.org/custom-destination")
.blur();

cy.wait("@putHelpLinkUrl");

cy.wait("@putHelpLink");

cy.log("Check that on page load the text field is not focused");
cy.reload();

getHelpLinkCustomDestinationInput().should("not.have.focus");

cy.signInAsNormalUser();
cy.visit("/");
openSettingsMenu();
helpLink().should(
"have.attr",
"href",
"https://example.org/custom-destination",
);

cy.log("Set default Help link");

cy.signInAsAdmin();
cy.visit("/admin/settings/whitelabel");

cy.findByTestId("help-link-setting")
.findByText("Link to Metabase help")
.click();

cy.wait("@putHelpLink");

cy.visit("/");
openSettingsMenu();

helpLink()
.should("have.attr", "href")
.and("include", "https://www.metabase.com/help-premium?");

cy.signInAsNormalUser();
cy.visit("/");
openSettingsMenu();

helpLink()
.should("have.attr", "href")
.and("include", "https://www.metabase.com/help?");
});

it("should link to metabase help when the whitelabel feature is disabled (eg OSS)", () => {
setTokenFeatures("none");

cy.signInAsNormalUser();
cy.visit("/");
openSettingsMenu();

helpLink()
.should("have.attr", "href")
.and("include", "https://www.metabase.com/help?");
});

it("it should validate the url", () => {
cy.signInAsAdmin();
cy.visit("/admin/settings/whitelabel");

cy.findByTestId("help-link-setting")
.findByText("Go to a custom destination...")
.click();

getHelpLinkCustomDestinationInput()
.clear()
.type("ftp://something")
.blur();
main()
.findByText(/This needs to be/i)
.should("exist");

getHelpLinkCustomDestinationInput().clear().type("https://").blur();

main().findByText("Please make sure this is a valid URL").should("exist");

getHelpLinkCustomDestinationInput().type("examp");

main()
.findByText("Please make sure this is a valid URL")
.should("not.exist");
});
});
});

function changeLoadingMessage(message) {
Expand All @@ -164,3 +295,10 @@ function setApplicationFontTo(font) {
value: font,
});
}

const openSettingsMenu = () => appBar().icon("gear").click();

const helpLink = () => popover().findByRole("link", { name: "Help" });

const getHelpLinkCustomDestinationInput = () =>
cy.findByPlaceholderText("Enter a URL it should go to");
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe("issue 10803", () => {

// Excel and CSV will have different formats
if (fileType === "csv") {
expect(sheet["A2"].v).to.eq("2026-06-03");
expect(sheet["A2"].v).to.eq("2026-06-03T00:00:00");
expect(sheet["B2"].v).to.eq("2026-06-03T23:41:23");
} else if (fileType === "xlsx") {
// We tell the xlsx library to read raw and not parse dates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,19 @@ import { Flex, Stack } from "metabase/ui";
import { FormSection } from "metabase/containers/FormikForm";
import GroupMappingsWidget from "metabase/admin/settings/containers/GroupMappingsWidget";
import type { SettingValue } from "metabase-types/api";
import type { SettingElement } from "metabase/admin/settings/types";

type SettingValues = { [key: string]: SettingValue };

type SettingElement = {
// Similar to SettingElement from "metabase/admin/settings/types" but with required key
key: string;
display_name?: string;
description?: string;
type JWTFormSettingElement = Omit<SettingElement, "key"> & {
key: string; // ensuring key is required
is_env_setting?: boolean;
env_name?: string;
placeholder?: string;
default?: any;
required?: boolean;
autoFocus?: boolean;
default?: string;
};

type Props = {
elements: SettingElement[];
elements: JWTFormSettingElement[];
settingValues: SettingValues;
onSubmit: (values: SettingValues) => void;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ const GROUPS = [

const elements = [
{
// placeholder: false,
key: "jwt-enabled",
value: null,
is_env_setting: false,
env_name: "MB_JWT_ENABLED",
description: "Is JWT authentication configured and enabled?",
default: false,
originalValue: null,
display_name: "JWT Authentication",
type: "boolean",
Expand All @@ -34,22 +32,19 @@ const elements = [
is_env_setting: false,
env_name: "MB_JWT_IDENTITY_PROVIDER_URI",
description: "URL of JWT based login page",
default: null,
originalValue: null,
display_name: "JWT Identity Provider URI",
type: "string",
required: true,
autoFocus: true,
},
{
// placeholder: null,
key: "jwt-shared-secret",
value: null,
is_env_setting: false,
env_name: "MB_JWT_SHARED_SECRET",
description:
"String used to seed the private key used to validate JWT messages. A hexadecimal-encoded 256-bit key (i.e., a 64-character string) is strongly recommended.",
default: null,
originalValue: null,
display_name: "String used by the JWT signing key",
type: "text",
Expand Down Expand Up @@ -92,24 +87,19 @@ const elements = [
type: "string",
},
{
// placeholder: false,
key: "jwt-group-sync",
value: null,
is_env_setting: false,
env_name: "MB_JWT_GROUP_SYNC",
// description: null,
default: false,
originalValue: null,
display_name: "Synchronize group memberships",
},
{
// placeholder: {},
key: "jwt-group-mappings",
value: null,
is_env_setting: false,
env_name: "MB_JWT_GROUP_MAPPINGS",
description: "JSON containing JWT to Metabase group mappings.",
default: {},
originalValue: null,
},
];
Expand Down Expand Up @@ -174,9 +164,9 @@ describe("SettingsJWTForm", () => {
await screen.findByRole("textbox", { name: /Last name attribute/ }),
ATTRS["jwt-attribute-lastname"],
);
userEvent.click(await screen.findByRole("button", { name: /Save/ }));
userEvent.click(screen.getByRole("checkbox")); // checkbox for "jwt-group-sync"

userEvent.click(screen.getByRole("checkbox")); // checkbox for "jwt-enabled"
userEvent.click(await screen.findByRole("button", { name: /Save/ }));

await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith(ATTRS);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { t } from "ttag";
import { useState } from "react";
import { Radio, Stack, Text } from "metabase/ui";
import type { HelpLinkSetting, SettingKey, Settings } from "metabase-types/api";
import { SettingInputBlurChange } from "metabase/admin/settings/components/widgets/SettingInput.styled";

interface Props {
setting: {
value?: HelpLinkSetting;
originalValue?: HelpLinkSetting;
default: HelpLinkSetting;
};
onChange: (value: string) => void;
onChangeSetting: <TKey extends SettingKey>(
key: TKey,
value: Settings[TKey],
) => Promise<void>;
settingValues: Settings;
}

const supportedPrefixes = ["http://", "https://", "mailto:"];

export const HelpLinkSettings = ({
setting,
onChangeSetting,
settingValues,
}: Props) => {
const [helpLinkSetting, setHelpLinkSetting] = useState(
settingValues["help-link"] || "metabase",
);

const [error, setError] = useState<string | null>(null);

const handleRadioChange = (value: HelpLinkSetting) => {
setHelpLinkSetting(value);
onChangeSetting("help-link", value);
};
const customUrl = settingValues["help-link-custom-destination"];

const isTextInputVisible = helpLinkSetting === "custom";

const handleChange = async (value: string) => {
if (value === "") {
setError(t`This field can't be left empty.`);
} else if (!supportedPrefixes.some(prefix => value.startsWith(prefix))) {
setError(t`This needs to be an "http://", "https://" or "mailto:" URL.`);
} else {
setError("");
try {
await onChangeSetting("help-link-custom-destination", value);
} catch (e: any) {
setError(e?.data?.message || t`Something went wrong`);
}
}
};

return (
<Stack>
<Radio.Group value={helpLinkSetting} onChange={handleRadioChange}>
<Stack>
<Radio label={t`Link to Metabase help`} value="metabase" />
<Radio label={t`Hide it`} value="hidden" />
<Radio label={t`Go to a custom destination...`} value="custom" />
</Stack>
</Radio.Group>
{isTextInputVisible && (
<Stack ml={28} spacing={0}>
{error && (
<Text size="md" color="error.0">
{error}
</Text>
)}
<SettingInputBlurChange
size="large"
error={Boolean(error)}
style={{ marginTop: 4 }}
value={customUrl}
// this makes it autofocus only when the value wasn't originally a custom destination
// this prevents it to be focused on page load
autoFocus={setting.originalValue !== "custom"}
onChange={() => setError(null)}
aria-label={t`Help link custom destination`}
placeholder={t`Enter a URL it should go to`}
onBlurChange={e => handleChange(e.target.value)}
/>
</Stack>
)}
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HelpLinkSettings } from "./HelpLinkSettings";
Loading
Loading