mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Get rid of this.disposers while registering stuff in extensions (#1148)
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
b765ee11bb
commit
67754ffbb9
@ -1,26 +1,16 @@
|
|||||||
import { LensRendererExtension, Registry } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { ExamplePage, ExampleIcon } from "./page"
|
import { ExampleIcon, ExamplePage } from "./page"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
export default class ExampleExtension extends LensRendererExtension {
|
export default class ExampleExtension extends LensRendererExtension {
|
||||||
onActivate() {
|
clusterPages = [
|
||||||
console.log('EXAMPLE EXTENSION RENDERER: ACTIVATED', this.getMeta());
|
{
|
||||||
}
|
path: "/extension-example",
|
||||||
|
title: "Example Extension",
|
||||||
registerClusterPage(registry: Registry.ClusterPageRegistry) {
|
components: {
|
||||||
this.disposers.push(
|
Page: () => <ExamplePage extension={this}/>,
|
||||||
registry.add({
|
MenuIcon: ExampleIcon,
|
||||||
path: "/extension-example",
|
}
|
||||||
title: "Example Extension",
|
}
|
||||||
components: {
|
]
|
||||||
Page: () => <ExamplePage extension={this} />,
|
|
||||||
MenuIcon: ExampleIcon,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeactivate() {
|
|
||||||
console.log('EXAMPLE EXTENSION RENDERER: DEACTIVATED', this.getMeta());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,23 @@
|
|||||||
import { Registry, LensRendererExtension } from "@k8slens/extensions"
|
import { LensRendererExtension } from "@k8slens/extensions"
|
||||||
import { MetricsFeature } from "./src/metrics-feature"
|
import { MetricsFeature } from "./src/metrics-feature"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
|
||||||
registerClusterFeatures(registry: Registry.ClusterFeatureRegistry) {
|
clusterFeatures = [
|
||||||
this.disposers.push(
|
{
|
||||||
registry.add({
|
title: "Metrics Stack",
|
||||||
title: "Metrics Stack",
|
components: {
|
||||||
components: {
|
Description: () => {
|
||||||
Description: () => {
|
return (
|
||||||
return (
|
<span>
|
||||||
<span>
|
|
||||||
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
Enable timeseries data visualization (Prometheus stack) for your cluster.
|
||||||
Install this only if you don't have existing Prometheus stack installed.
|
Install this only if you don't have existing Prometheus stack installed.
|
||||||
You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/lens-metrics/resources" target="_blank">here</a>.
|
You can see preview of manifests <a href="https://github.com/lensapp/lens/tree/master/extensions/lens-metrics/resources" target="_blank">here</a>.
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
feature: new MetricsFeature()
|
feature: new MetricsFeature()
|
||||||
})
|
}
|
||||||
)
|
]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,15 @@
|
|||||||
import { Registry, LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { NodeMenu } from "./src/node-menu"
|
import { NodeMenu, NodeMenuProps } from "./src/node-menu"
|
||||||
|
|
||||||
export default class NodeMenuRendererExtension extends LensRendererExtension {
|
export default class NodeMenuRendererExtension extends LensRendererExtension {
|
||||||
async onActivate() {
|
kubeObjectMenuItems = [
|
||||||
console.log("node-menu extension activated")
|
{
|
||||||
}
|
kind: "Node",
|
||||||
|
apiVersions: ["v1"],
|
||||||
registerKubeObjectMenus(registry: Registry.KubeObjectMenuRegistry) {
|
components: {
|
||||||
this.disposers.push(
|
MenuItem: (props: NodeMenuProps) => <NodeMenu {...props} />
|
||||||
registry.add({
|
}
|
||||||
kind: "Node",
|
}
|
||||||
apiVersions: ["v1"],
|
]
|
||||||
components: {
|
|
||||||
MenuItem: (props) => <NodeMenu {...props} />
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Component, K8sApi, Navigation} from "@k8slens/extensions"
|
import { Component, K8sApi, Navigation} from "@k8slens/extensions"
|
||||||
|
|
||||||
export function NodeMenu(props: Component.KubeObjectMenuProps<K8sApi.Node>) {
|
export interface NodeMenuProps extends Component.KubeObjectMenuProps<K8sApi.Node> {
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NodeMenu(props: NodeMenuProps) {
|
||||||
const { object: node, toolbar } = props;
|
const { object: node, toolbar } = props;
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
const nodeName = node.getName();
|
const nodeName = node.getName();
|
||||||
|
|||||||
@ -1,31 +1,23 @@
|
|||||||
import { Registry, LensRendererExtension } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { PodShellMenu } from "./src/shell-menu"
|
import { PodShellMenu, PodShellMenuProps } from "./src/shell-menu"
|
||||||
import { PodLogsMenu } from "./src/logs-menu"
|
import { PodLogsMenu, PodLogsMenuProps } from "./src/logs-menu"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
export default class PodMenuRendererExtension extends LensRendererExtension {
|
export default class PodMenuRendererExtension extends LensRendererExtension {
|
||||||
async onActivate() {
|
kubeObjectMenuItems = [
|
||||||
console.log("pod-menu extension activated")
|
{
|
||||||
}
|
kind: "Pod",
|
||||||
|
apiVersions: ["v1"],
|
||||||
registerKubeObjectMenus(registry: Registry.KubeObjectMenuRegistry) {
|
components: {
|
||||||
this.disposers.push(
|
MenuItem: (props: PodShellMenuProps) => <PodShellMenu {...props} />
|
||||||
registry.add({
|
}
|
||||||
kind: "Pod",
|
},
|
||||||
apiVersions: ["v1"],
|
{
|
||||||
components: {
|
kind: "Pod",
|
||||||
MenuItem: (props) => <PodShellMenu {...props} />
|
apiVersions: ["v1"],
|
||||||
}
|
components: {
|
||||||
})
|
MenuItem: (props: PodLogsMenuProps) => <PodLogsMenu {...props} />
|
||||||
)
|
}
|
||||||
this.disposers.push(
|
}
|
||||||
registry.add({
|
]
|
||||||
kind: "Pod",
|
|
||||||
apiVersions: ["v1"],
|
|
||||||
components: {
|
|
||||||
MenuItem: (props) => <PodLogsMenu {...props} />
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
||||||
|
|
||||||
interface Props extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
export interface PodLogsMenuProps extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PodLogsMenu extends React.Component<Props> {
|
export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
|
||||||
showLogs(container: K8sApi.IPodContainer) {
|
showLogs(container: K8sApi.IPodContainer) {
|
||||||
Navigation.hideDetails();
|
Navigation.hideDetails();
|
||||||
const pod = this.props.object;
|
const pod = this.props.object;
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
import { Component, K8sApi, Util, Navigation } from "@k8slens/extensions";
|
||||||
|
|
||||||
interface Props extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
export interface PodShellMenuProps extends Component.KubeObjectMenuProps<K8sApi.Pod> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PodShellMenu extends React.Component<Props> {
|
export class PodShellMenu extends React.Component<PodShellMenuProps> {
|
||||||
async execShell(container?: string) {
|
async execShell(container?: string) {
|
||||||
Navigation.hideDetails();
|
Navigation.hideDetails();
|
||||||
const { object: pod } = this.props
|
const { object: pod } = this.props
|
||||||
|
|||||||
@ -1,20 +1,14 @@
|
|||||||
import { LensMainExtension, Registry, windowManager } from "@k8slens/extensions";
|
import { LensMainExtension, windowManager } from "@k8slens/extensions";
|
||||||
import { supportPageURL } from "./src/support.route";
|
import { supportPageURL } from "./src/support.route";
|
||||||
|
|
||||||
export default class SupportPageMainExtension extends LensMainExtension {
|
export default class SupportPageMainExtension extends LensMainExtension {
|
||||||
async onActivate() {
|
appMenus = [
|
||||||
console.log("support page extension activated")
|
{
|
||||||
}
|
parentId: "help",
|
||||||
|
label: "Support",
|
||||||
async registerAppMenus(registry: Registry.MenuRegistry) {
|
click() {
|
||||||
this.disposers.push(
|
windowManager.navigate(supportPageURL());
|
||||||
registry.add({
|
}
|
||||||
parentId: "help",
|
}
|
||||||
label: "Support",
|
]
|
||||||
click() {
|
|
||||||
windowManager.navigate(supportPageURL());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +1,31 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Component, LensRendererExtension, Navigation, Registry } from "@k8slens/extensions";
|
import { Component, LensRendererExtension, Navigation } from "@k8slens/extensions";
|
||||||
import { supportPageRoute, supportPageURL } from "./src/support.route";
|
import { supportPageRoute, supportPageURL } from "./src/support.route";
|
||||||
import { Support } from "./src/support";
|
import { Support } from "./src/support";
|
||||||
|
|
||||||
export default class SupportPageRendererExtension extends LensRendererExtension {
|
export default class SupportPageRendererExtension extends LensRendererExtension {
|
||||||
async onActivate() {
|
globalPages = [
|
||||||
console.log("support page extension activated")
|
{
|
||||||
}
|
...supportPageRoute,
|
||||||
|
url: supportPageURL(),
|
||||||
|
hideInMenu: true,
|
||||||
|
components: {
|
||||||
|
Page: Support,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
registerGlobalPage(registry: Registry.GlobalPageRegistry) {
|
statusBarItems = [
|
||||||
this.disposers.push(
|
{
|
||||||
registry.add({
|
item: (
|
||||||
...supportPageRoute,
|
<div
|
||||||
url: supportPageURL(),
|
className="flex align-center gaps hover-highlight"
|
||||||
hideInMenu: true,
|
onClick={() => Navigation.navigate(supportPageURL())}
|
||||||
components: {
|
>
|
||||||
Page: Support,
|
<Component.Icon material="help_outline" small/>
|
||||||
}
|
<span>Support</span>
|
||||||
})
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
]
|
||||||
registerStatusBarItem(registry: Registry.StatusBarRegistry) {
|
|
||||||
this.disposers.push(
|
|
||||||
registry.add({
|
|
||||||
item: (
|
|
||||||
<div
|
|
||||||
className="flex align-center gaps hover-highlight"
|
|
||||||
onClick={() => Navigation.navigate(supportPageURL())}
|
|
||||||
>
|
|
||||||
<Component.Icon material="help_outline" small />
|
|
||||||
<span>Support</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,23 @@
|
|||||||
import { LensRendererExtension, Registry } from "@k8slens/extensions";
|
import { LensRendererExtension } from "@k8slens/extensions";
|
||||||
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store"
|
import { telemetryPreferencesStore } from "./src/telemetry-preferences-store"
|
||||||
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference"
|
import { TelemetryPreferenceHint, TelemetryPreferenceInput } from "./src/telemetry-preference"
|
||||||
import { tracker } from "./src/tracker"
|
import { tracker } from "./src/tracker"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
||||||
export default class TelemetryRendererExtension extends LensRendererExtension {
|
export default class TelemetryRendererExtension extends LensRendererExtension {
|
||||||
|
appPreferences = [
|
||||||
|
{
|
||||||
|
title: "Telemetry & Usage Tracking",
|
||||||
|
components: {
|
||||||
|
Hint: () => <TelemetryPreferenceHint/>,
|
||||||
|
Input: () => <TelemetryPreferenceInput telemetry={telemetryPreferencesStore}/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
async onActivate() {
|
async onActivate() {
|
||||||
console.log("telemetry extension activated")
|
console.log("telemetry extension activated")
|
||||||
tracker.start()
|
tracker.start()
|
||||||
await telemetryPreferencesStore.loadExtension(this)
|
await telemetryPreferencesStore.loadExtension(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
registerAppPreferences(registry: Registry.AppPreferenceRegistry) {
|
|
||||||
this.disposers.push(
|
|
||||||
registry.add({
|
|
||||||
title: "Telemetry & Usage Tracking",
|
|
||||||
components: {
|
|
||||||
Hint: () => <TelemetryPreferenceHint />,
|
|
||||||
Input: () => <TelemetryPreferenceInput telemetry={telemetryPreferencesStore} />
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeactivate() {
|
|
||||||
console.log("telemetry extension deactivated")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,26 +36,26 @@ export class ExtensionLoader {
|
|||||||
|
|
||||||
loadOnMain() {
|
loadOnMain() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main')
|
logger.info('[EXTENSIONS-LOADER]: load on main')
|
||||||
this.autoloadExtensions((instance: LensMainExtension) => {
|
this.autoloadExtensions((extension: LensMainExtension) => {
|
||||||
instance.registerAppMenus(menuRegistry);
|
extension.registerTo(menuRegistry, extension.appMenus)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterManagerRenderer() {
|
loadOnClusterManagerRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
logger.info('[EXTENSIONS-LOADER]: load on main renderer (cluster manager)')
|
||||||
this.autoloadExtensions((instance: LensRendererExtension) => {
|
this.autoloadExtensions((extension: LensRendererExtension) => {
|
||||||
instance.registerGlobalPage(globalPageRegistry)
|
extension.registerTo(globalPageRegistry, extension.globalPages)
|
||||||
instance.registerAppPreferences(appPreferenceRegistry)
|
extension.registerTo(appPreferenceRegistry, extension.appPreferences)
|
||||||
instance.registerClusterFeatures(clusterFeatureRegistry)
|
extension.registerTo(clusterFeatureRegistry, extension.clusterFeatures)
|
||||||
instance.registerStatusBarItem(statusBarRegistry)
|
extension.registerTo(statusBarRegistry, extension.statusBarItems)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOnClusterRenderer() {
|
loadOnClusterRenderer() {
|
||||||
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
logger.info('[EXTENSIONS-LOADER]: load on cluster renderer (dashboard)')
|
||||||
this.autoloadExtensions((instance: LensRendererExtension) => {
|
this.autoloadExtensions((extension: LensRendererExtension) => {
|
||||||
instance.registerClusterPage(clusterPageRegistry)
|
extension.registerTo(clusterPageRegistry, extension.clusterPages)
|
||||||
instance.registerKubeObjectMenus(kubeObjectMenuRegistry)
|
extension.registerTo(kubeObjectMenuRegistry, extension.kubeObjectMenuItems)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { readJsonSync } from "fs-extra";
|
import { readJsonSync } from "fs-extra";
|
||||||
import { action, observable, toJS } from "mobx";
|
import { action, observable, toJS } from "mobx";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
|
import { BaseRegistry } from "./registries/base-registry";
|
||||||
|
|
||||||
export type ExtensionId = string | ExtensionPackageJsonPath;
|
export type ExtensionId = string | ExtensionPackageJsonPath;
|
||||||
export type ExtensionPackageJsonPath = string;
|
export type ExtensionPackageJsonPath = string;
|
||||||
@ -25,7 +26,7 @@ export interface ExtensionManifest extends ExtensionModel {
|
|||||||
export class LensExtension implements ExtensionModel {
|
export class LensExtension implements ExtensionModel {
|
||||||
public id: ExtensionId;
|
public id: ExtensionId;
|
||||||
public updateUrl: string;
|
public updateUrl: string;
|
||||||
protected disposers: Function[] = [];
|
protected disposers: (() => void)[] = [];
|
||||||
|
|
||||||
@observable name = "";
|
@observable name = "";
|
||||||
@observable description = "";
|
@observable description = "";
|
||||||
@ -77,6 +78,14 @@ export class LensExtension implements ExtensionModel {
|
|||||||
// mock
|
// mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerTo<T = any>(registry: BaseRegistry<T>, items: T[] = []) {
|
||||||
|
const disposers = items.map(item => registry.add(item));
|
||||||
|
this.disposers.push(...disposers);
|
||||||
|
return () => {
|
||||||
|
this.disposers = this.disposers.filter(disposer => !disposers.includes(disposer))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
getMeta() {
|
getMeta() {
|
||||||
return toJS({
|
return toJS({
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
|
import type { MenuRegistration } from "./registries/menu-registry";
|
||||||
|
import { observable } from "mobx";
|
||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension"
|
||||||
import type { MenuRegistry } from "./registries/menu-registry";
|
|
||||||
|
|
||||||
export class LensMainExtension extends LensExtension {
|
export class LensMainExtension extends LensExtension {
|
||||||
registerAppMenus(registry: MenuRegistry) {
|
@observable.shallow appMenus: MenuRegistration[] = []
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
registerPrometheusProviders(registry: any) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,12 @@
|
|||||||
|
import type { AppPreferenceRegistration, ClusterFeatureRegistration, KubeObjectMenuRegistration, PageRegistration, StatusBarRegistration } from "./registries"
|
||||||
|
import { observable } from "mobx";
|
||||||
import { LensExtension } from "./lens-extension"
|
import { LensExtension } from "./lens-extension"
|
||||||
import type { GlobalPageRegistry, ClusterPageRegistry, AppPreferenceRegistry, StatusBarRegistry, KubeObjectMenuRegistry, ClusterFeatureRegistry } from "./registries"
|
|
||||||
|
|
||||||
export class LensRendererExtension extends LensExtension {
|
export class LensRendererExtension extends LensExtension {
|
||||||
registerGlobalPage(registry: GlobalPageRegistry) {
|
@observable.shallow globalPages: PageRegistration[] = []
|
||||||
return
|
@observable.shallow clusterPages: PageRegistration[] = []
|
||||||
}
|
@observable.shallow appPreferences: AppPreferenceRegistration[] = []
|
||||||
|
@observable.shallow clusterFeatures: ClusterFeatureRegistration[] = []
|
||||||
registerClusterPage(registry: ClusterPageRegistry) {
|
@observable.shallow statusBarItems: StatusBarRegistration[] = []
|
||||||
return
|
@observable.shallow kubeObjectMenuItems: KubeObjectMenuRegistration[] = []
|
||||||
}
|
|
||||||
|
|
||||||
registerAppPreferences(registry: AppPreferenceRegistry) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
registerClusterFeatures(registry: ClusterFeatureRegistry) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
registerStatusBarItem(registry: StatusBarRegistry) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
registerKubeObjectMenus(registry: KubeObjectMenuRegistry) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
// Extensions API -> Global menu customizations
|
// Extensions API -> Global menu customizations
|
||||||
|
|
||||||
import type { MenuTopId } from "../../main/menu";
|
|
||||||
import type { MenuItemConstructorOptions } from "electron";
|
import type { MenuItemConstructorOptions } from "electron";
|
||||||
import { BaseRegistry } from "./base-registry";
|
import { BaseRegistry } from "./base-registry";
|
||||||
|
|
||||||
export interface MenuRegistration extends MenuItemConstructorOptions {
|
export interface MenuRegistration extends MenuItemConstructorOptions {
|
||||||
parentId?: MenuTopId;
|
parentId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MenuRegistry extends BaseRegistry<MenuRegistration> {
|
export class MenuRegistry extends BaseRegistry<MenuRegistration> {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user