Skip to content
Snippets Groups Projects
Unverified Commit aa68beea authored by Gustavo Saiani's avatar Gustavo Saiani Committed by GitHub
Browse files

Show server timezone info in `now` custom expression helper text (#26736)

parent 2d6a7799
No related branches found
No related tags found
No related merge requests found
Showing
with 244 additions and 71 deletions
import type { Database } from "metabase-types/api/database";
export interface HelpText {
name: string;
args: HelpTextArg[];
......@@ -7,6 +9,15 @@ export interface HelpText {
docsPage?: string;
}
export interface HelpTextConfig {
name: string;
args: HelpTextArg[];
description: (database: Database, reportTimezone: string) => string;
example: string;
structure: string;
docsPage?: string;
}
export interface HelpTextArg {
name: string;
description: string;
......
import { NativePermissions } from "./permissions";
import { ScheduleSettings } from "./settings";
import { Table } from "./table";
import { ISO8601Time } from ".";
export type DatabaseId = number;
......@@ -22,20 +24,23 @@ export type DatabaseFeature =
| "nested-queries"
| "standard-deviation-aggregations"
| "persist-models"
| "persist-models-enabled";
| "persist-models-enabled"
| "set-timezone";
export interface Database extends DatabaseData {
id: DatabaseId;
is_saved_questions: boolean;
features: DatabaseFeature[];
creator_id?: number;
created_at: string;
timezone?: string;
native_permissions: NativePermissions;
initial_sync_status: InitialSyncStatus;
settings?: DatabaseSettings | null;
created_at: ISO8601Time;
updated_at: ISO8601Time;
// Only appears in GET /api/database/:id
"can-manage"?: boolean;
}
......
......@@ -24,3 +24,6 @@ export * from "./table";
export * from "./timeline";
export * from "./user";
export * from "./parameters";
// ISO8601 timestamp
export type ISO8601Time = string;
......@@ -21,6 +21,7 @@ export const createMockDatabase = (opts?: Partial<Database>): Database => ({
is_sample: false,
is_saved_questions: false,
created_at: "2015-01-01T20:10:30.200",
updated_at: "2015-01-01T20:10:30.200",
timezone: "UTC",
native_permissions: "write",
initial_sync_status: "complete",
......
......@@ -330,10 +330,11 @@ class ExpressionEditorTextfield extends React.Component {
handleCursorChange(selection) {
const cursor = selection.getCursor();
const { query, startRule } = this.props;
const { query, reportTimezone, startRule } = this.props;
const { source } = this.state;
const { suggestions, helpText } = suggest({
query,
reportTimezone,
startRule,
source,
targetOffset: cursor.column,
......
import { createMockDatabase } from "metabase-types/api/mocks/database";
import { getHelpText } from "./helper-text-strings";
describe("getHelpText", () => {
const database = createMockDatabase();
const reportTimezone = "US/Hawaii";
it("should be undefined if an unsupported function name is passed", () => {
const helpText = getHelpText(
"this function name should not ever exist",
database,
reportTimezone,
);
expect(helpText).toBeUndefined();
});
it("should return help text if a supported name is passed", () => {
const helpText = getHelpText("count", database, reportTimezone);
expect(helpText?.structure).toBe("Count");
expect(helpText?.example).toBe("Count");
expect(helpText?.description).toMatch(/returns the count of rows/i);
expect(helpText?.args).toHaveLength(0);
});
describe("help texts customized per database engine", () => {
it("should include timestamps in description for default database engines", () => {
const helpText = getHelpText("now", database, reportTimezone);
expect(helpText?.description).toMatch(/currently/i);
});
it("should not include timestamps in description for h2 database engines", () => {
const h2Database = { ...database, engine: "h2" };
const helpText = getHelpText("now", h2Database, reportTimezone);
expect(helpText?.description).not.toMatch(/currently/i);
});
it("should use custom reportTimezone in description for supporting database engines ", () => {
const helpText = getHelpText(
"now",
{ ...database, features: ["set-timezone"] },
reportTimezone,
);
expect(helpText?.description).toMatch(new RegExp(reportTimezone));
});
it("should default to UTC in description for database engines not supporting set-timezone", () => {
const helpText = getHelpText("now", database, reportTimezone);
expect(helpText?.description).toMatch(/UTC/);
});
});
});
......@@ -28,6 +28,7 @@ const suggestionText = func => {
export function suggest({
source,
query,
reportTimezone,
startRule,
targetOffset = source.length,
} = {}) {
......@@ -40,7 +41,11 @@ export function suggest({
// no keystroke to match? show help text for the enclosing function
const functionDisplayName = enclosingFunction(partialSource);
if (functionDisplayName) {
const helpText = getHelpText(getMBQLName(functionDisplayName));
const helpText = getHelpText(
getMBQLName(functionDisplayName),
query.database(),
reportTimezone,
);
if (helpText) {
return { suggestions, helpText };
}
......@@ -188,7 +193,11 @@ export function suggest({
if (suggestions.length === 1 && matchPrefix) {
const { icon } = suggestions[0];
if (icon === "function") {
const helpText = getHelpText(getMBQLName(matchPrefix));
const helpText = getHelpText(
getMBQLName(matchPrefix),
query.database(),
reportTimezone,
);
if (helpText) {
return { helpText };
}
......
......@@ -20,6 +20,7 @@ export default class ExpressionWidget extends Component {
]),
name: PropTypes.string,
query: PropTypes.object.isRequired,
reportTimezone: PropTypes.string,
onChangeExpression: PropTypes.func.isRequired,
onRemoveExpression: PropTypes.func,
onClose: PropTypes.func,
......@@ -56,7 +57,7 @@ export default class ExpressionWidget extends Component {
};
render() {
const { query } = this.props;
const { query, reportTimezone } = this.props;
const { expression, name } = this.state;
return (
......@@ -69,6 +70,7 @@ export default class ExpressionWidget extends Component {
expression={expression}
name={name}
query={query}
reportTimezone={reportTimezone}
onChange={parsedExpression =>
this.setState({ expression: parsedExpression, error: null })
}
......
......@@ -111,8 +111,14 @@ export default class NotebookStep extends React.Component {
};
render() {
const { step, openStep, isLastStep, isLastOpened, updateQuery } =
this.props;
const {
step,
openStep,
isLastStep,
isLastOpened,
updateQuery,
reportTimezone,
} = this.props;
const { showPreview } = this.state;
const {
......@@ -180,6 +186,7 @@ export default class NotebookStep extends React.Component {
query={step.query}
updateQuery={updateQuery}
isLastOpened={isLastOpened}
reportTimezone={reportTimezone}
/>
</StepContent>
<StepButtonContainer>
......
......@@ -38,7 +38,7 @@ export default class NotebookSteps extends React.Component {
};
render() {
const { question, className, updateQuestion } = this.props;
const { question, className, reportTimezone, updateQuestion } = this.props;
const { openSteps, lastOpenedStep } = this.state;
if (!question) {
......@@ -67,6 +67,7 @@ export default class NotebookSteps extends React.Component {
closeStep={this.closeStep}
isLastStep={index === steps.length - 1}
isLastOpened={lastOpenedStep === step.id}
reportTimezone={reportTimezone}
/>
);
})}
......
......@@ -9,6 +9,7 @@ export default function ExpressionStep({
query,
updateQuery,
isLastOpened,
reportTimezone,
...props
}) {
return (
......@@ -28,6 +29,7 @@ export default function ExpressionStep({
)
: updateQuery(query.addExpression(newName, newExpression))
}
reportTimezone={reportTimezone}
/>
)}
isLastOpened={isLastOpened}
......
......@@ -15,6 +15,7 @@ import { PLUGIN_SELECTORS } from "metabase/plugins";
import Bookmark from "metabase/entities/bookmarks";
import Collections from "metabase/entities/collections";
import Timelines from "metabase/entities/timelines";
import { getSetting } from "metabase/selectors/settings";
import { closeNavbar, getIsNavbarOpen } from "metabase/redux/app";
import { getMetadata } from "metabase/selectors/metadata";
......@@ -181,6 +182,8 @@ const mapStateToProps = (state, props) => {
pageFavicon: getPageFavicon(state),
isLoadingComplete: getIsLoadingComplete(state),
loadingMessage: PLUGIN_SELECTORS.getLoadingMessage(state),
reportTimezone: getSetting(state, "report-timezone-long"),
};
};
......
......@@ -42,6 +42,11 @@
java.time.format.TextStyle/SHORT
(java.util.Locale/getDefault))))
(defn- long-timezone-name [timezone-id]
(if (seq timezone-id)
timezone-id
(str (t/zone-id))))
(defsetting report-timezone
(deferred-tru "Connection timezone to use when executing queries. Defaults to system timezone.")
:visibility :settings-manager
......@@ -57,6 +62,13 @@
:getter (fn [] (short-timezone-name (report-timezone)))
:doc false)
(defsetting report-timezone-long
"Current report timezone string"
:visibility :public
:setter :none
:getter (fn [] (long-timezone-name (report-timezone)))
:doc false)
;;; +----------------------------------------------------------------------------------------------------------------+
;;; | Current Driver |
......
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