Use & enforce snake_case naming convention on config.json settings (#8062)

* Document and support the established naming convention for config opts

This change:
* Rename `ConfigOptions` to `IConfigOptions` to match code convention/style, plus move it to a dedicated file
* Update comments and surrounding documentation
* Define every single documented option (from element-web's config.md)
* Enable a linter to enforce the convention
* Invent a translation layer for a different change to use
* No attempt to fix build errors from doing this (at this stage)

* Add demo of lint rule in action

* Fix all obvious instances of SdkConfig case conflicts

* Fix tests to use SdkConfig directly

* Add docs to make unset() calling safer

* Appease the linter

* Update documentation to match snake_case_config

* Fix more instances of square brackets off SdkConfig
This commit is contained in:
Travis Ralston 2022-03-18 10:12:36 -06:00 committed by GitHub
parent 09c57b228e
commit d8a939df5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 605 additions and 259 deletions

View file

@ -103,11 +103,8 @@ const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
if (justRegistered) {
introSection = <UserWelcomeTop />;
} else {
const brandingConfig = config.branding;
let logoUrl = "themes/element/img/logos/element-logo.svg";
if (brandingConfig && brandingConfig.authHeaderLogoUrl) {
logoUrl = brandingConfig.authHeaderLogoUrl;
}
const brandingConfig = SdkConfig.getObject("branding");
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
introSection = <React.Fragment>
<img src={logoUrl} alt={config.brand} />

View file

@ -39,8 +39,8 @@ export default class HostSignupAction extends React.PureComponent<IProps, IState
};
public render(): React.ReactNode {
const hostSignupConfig = SdkConfig.get().hostSignup;
if (!hostSignupConfig?.brand) {
const hostSignupConfig = SdkConfig.getObject("host_signup");
if (!hostSignupConfig?.get("brand")) {
return null;
}
@ -51,7 +51,7 @@ export default class HostSignupAction extends React.PureComponent<IProps, IState
label={_t(
"Upgrade to %(hostSignupBrand)s",
{
hostSignupBrand: hostSignupConfig.brand,
hostSignupBrand: hostSignupConfig.get("brand"),
},
)}
onClick={this.openDialog}

View file

@ -75,6 +75,7 @@ import RightPanelStore from '../../stores/right-panel/RightPanelStore';
import { TimelineRenderingType } from "../../contexts/RoomContext";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
import { IConfigOptions } from "../../IConfigOptions";
import LeftPanelLiveShareWarning from '../views/beacon/LeftPanelLiveShareWarning';
// We need to fetch each pinned message individually (if we don't already have it)
@ -103,12 +104,7 @@ interface IProps {
roomOobData?: IOOBData;
currentRoomId: string;
collapseLhs: boolean;
config: {
piwik: {
policyUrl: string;
};
[key: string]: any;
};
config: IConfigOptions;
currentUserId?: string;
currentGroupId?: string;
currentGroupIsNew?: boolean;

View file

@ -131,6 +131,8 @@ import { ViewHomePagePayload } from '../../dispatcher/payloads/ViewHomePagePaylo
import { AfterLeaveRoomPayload } from '../../dispatcher/payloads/AfterLeaveRoomPayload';
import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyncPreparedPayload';
import { ViewStartChatOrReusePayload } from '../../dispatcher/payloads/ViewStartChatOrReusePayload';
import { IConfigOptions } from "../../IConfigOptions";
import { SnakedObject } from "../../utils/SnakedObject";
import InfoDialog from '../views/dialogs/InfoDialog';
// legacy export
@ -154,12 +156,7 @@ interface IScreen {
}
interface IProps { // TODO type things better
config: {
piwik: {
policyUrl: string;
};
[key: string]: any;
};
config: IConfigOptions;
serverConfig?: ValidatedServerConfig;
onNewScreen: (screen: string, replaceLast: boolean) => void;
enableGuest?: boolean;
@ -355,7 +352,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
Analytics.enable();
}
initSentry(SdkConfig.get()["sentry"]);
initSentry(SdkConfig.get("sentry"));
}
private async postLoginSetup() {
@ -474,7 +471,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private getServerProperties() {
let props = this.state.serverConfig;
if (!props) props = this.props.serverConfig; // for unit tests
if (!props) props = SdkConfig.get()["validated_server_config"];
if (!props) props = SdkConfig.get("validated_server_config");
return { serverConfig: props };
}
@ -865,7 +862,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
);
// If the hs url matches then take the hs name we know locally as it is likely prettier
const defaultConfig = SdkConfig.get()["validated_server_config"] as ValidatedServerConfig;
const defaultConfig = SdkConfig.get("validated_server_config");
if (defaultConfig && defaultConfig.hsUrl === newState.serverConfig.hsUrl) {
newState.serverConfig.hsName = defaultConfig.hsName;
newState.serverConfig.hsNameIsDifferent = defaultConfig.hsNameIsDifferent;
@ -1062,11 +1059,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
private chatCreateOrReuse(userId: string) {
const snakedConfig = new SnakedObject<IConfigOptions>(this.props.config);
// Use a deferred action to reshow the dialog once the user has registered
if (MatrixClientPeg.get().isGuest()) {
// No point in making 2 DMs with welcome bot. This assumes view_set_mxid will
// result in a new DM with the welcome user.
if (userId !== this.props.config.welcomeUserId) {
if (userId !== snakedConfig.get("welcome_user_id")) {
dis.dispatch<DoAfterSyncPreparedPayload<ViewStartChatOrReusePayload>>({
action: Action.DoAfterSyncPrepared,
deferred_action: {
@ -1083,7 +1081,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// `_chatCreateOrReuse` again)
go_welcome_on_cancel: true,
screen_after: {
screen: `user/${this.props.config.welcomeUserId}`,
screen: `user/${snakedConfig.get("welcome_user_id")}`,
params: { action: 'chat' },
},
});
@ -1231,12 +1229,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
await waitFor;
const snakedConfig = new SnakedObject<IConfigOptions>(this.props.config);
const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId(
this.props.config.welcomeUserId,
snakedConfig.get("welcome_user_id"),
);
if (welcomeUserRooms.length === 0) {
const roomId = await createRoom({
dmUserId: this.props.config.welcomeUserId,
dmUserId: snakedConfig.get("welcome_user_id"),
// Only view the welcome user if we're NOT looking at a room
andView: !this.state.currentRoomId,
spinner: false, // we're already showing one: we don't need another one
@ -1250,7 +1249,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
// user room (it doesn't wait for new data from the server, just
// the saved sync to be loaded).
const saveWelcomeUser = (ev: MatrixEvent) => {
if (ev.getType() === EventType.Direct && ev.getContent()[this.props.config.welcomeUserId]) {
if (ev.getType() === EventType.Direct && ev.getContent()[snakedConfig.get("welcome_user_id")]) {
MatrixClientPeg.get().store.save(true);
MatrixClientPeg.get().removeListener(ClientEvent.AccountData, saveWelcomeUser);
}
@ -1280,7 +1279,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else if (MatrixClientPeg.currentUserIsJustRegistered()) {
MatrixClientPeg.setJustRegisteredUserId(null);
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
const snakedConfig = new SnakedObject<IConfigOptions>(this.props.config);
if (snakedConfig.get("welcome_user_id") && getCurrentLanguage().startsWith("en")) {
const welcomeUserRoom = await this.startWelcomeUserChat();
if (welcomeUserRoom === null) {
// We didn't redirect to the welcome user room, so show
@ -1312,7 +1312,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
showAnonymousAnalyticsOptInToast();
}
if (SdkConfig.get().mobileGuideToast) {
if (SdkConfig.get("mobile_guide_toast")) {
// The toast contains further logic to detect mobile platforms,
// check if it has been dismissed before, etc.
showMobileGuideToast();
@ -1463,7 +1463,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (!localStorage.getItem("mx_seen_feature_thread_experimental")) {
setTimeout(() => {
if (SettingsStore.getValue("feature_thread") && SdkConfig.get()['showLabsSettings']) {
if (SettingsStore.getValue("feature_thread") && SdkConfig.get("show_labs_settings")) {
Modal.createDialog(InfoDialog, {
title: _t("Threads are no longer experimental! 🎉"),
description: <>

View file

@ -103,7 +103,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
let roomServer = myHomeserver;
if (
SdkConfig.get().roomDirectory?.servers?.includes(lsRoomServer) ||
SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) ||
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
) {
roomServer = lsRoomServer;

View file

@ -53,7 +53,6 @@ import IconizedContextMenu, {
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
import { UIFeature } from "../../settings/UIFeature";
import HostSignupAction from "./HostSignupAction";
import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes";
import SpaceStore from "../../stores/spaces/SpaceStore";
import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
import { replaceableComponent } from "../../utils/replaceableComponent";
@ -375,7 +374,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
if (!this.state.contextMenuPosition) return null;
let topSection;
const hostSignupConfig: IHostSignupConfig = SdkConfig.get().hostSignup;
const hostSignupConfig = SdkConfig.getObject("host_signup");
if (MatrixClientPeg.get().isGuest()) {
topSection = (
<div className="mx_UserMenu_contextMenu_header mx_UserMenu_contextMenu_guestPrompts">
@ -395,16 +394,14 @@ export default class UserMenu extends React.Component<IProps, IState> {
}) }
</div>
);
} else if (hostSignupConfig) {
if (hostSignupConfig && hostSignupConfig.url) {
// If hostSignup.domains is set to a non-empty array, only show
// dialog if the user is on the domain or a subdomain.
const hostSignupDomains = hostSignupConfig.domains || [];
const mxDomain = MatrixClientPeg.get().getDomain();
const validDomains = hostSignupDomains.filter(d => (d === mxDomain || mxDomain.endsWith(`.${d}`)));
if (!hostSignupConfig.domains || validDomains.length > 0) {
topSection = <HostSignupAction onClick={this.onCloseMenu} />;
}
} else if (hostSignupConfig?.get("url")) {
// If hostSignup.domains is set to a non-empty array, only show
// dialog if the user is on the domain or a subdomain.
const hostSignupDomains = hostSignupConfig.get("domains") || [];
const mxDomain = MatrixClientPeg.get().getDomain();
const validDomains = hostSignupDomains.filter(d => (d === mxDomain || mxDomain.endsWith(`.${d}`)));
if (!hostSignupConfig.get("domains") || validDomains.length > 0) {
topSection = <HostSignupAction onClick={this.onCloseMenu} />;
}
}

View file

@ -245,7 +245,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
if (error.errcode === 'M_USER_DEACTIVATED') {
errorText = _t('This account has been deactivated.');
} else if (SdkConfig.get()['disable_custom_urls']) {
} else if (SdkConfig.get("disable_custom_urls")) {
errorText = (
<div>
<div>{ _t('Incorrect username and/or password.') }</div>