Rename plugin to GitHub Release Manager and remove references to RMaaS
Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: Release Manager as a Service
|
||||
title: GitHub Release Manager
|
||||
author: '@erikengervall'
|
||||
authorUrl: https://github.com/erikengervall
|
||||
category: Release management
|
||||
description: Manage releases without having to juggle git commands
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/release-manager-as-a-service
|
||||
iconUrl: img/release-manager-as-a-service.svg
|
||||
npmPackageName: '@backstage/plugin-release-manager-as-a-service'
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/github-release-manager
|
||||
iconUrl: img/github-release-manager-logo.svg
|
||||
npmPackageName: '@backstage/plugin-github-release-manager'
|
||||
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -30,7 +30,7 @@
|
||||
"@backstage/plugin-org": "^0.3.10",
|
||||
"@backstage/plugin-pagerduty": "0.3.2",
|
||||
"@backstage/plugin-register-component": "^0.2.12",
|
||||
"@backstage/plugin-release-manager-as-a-service": "^0.1.1",
|
||||
"@backstage/plugin-github-release-manager": "^0.1.1",
|
||||
"@backstage/plugin-rollbar": "^0.3.3",
|
||||
"@backstage/plugin-scaffolder": "^0.8.0",
|
||||
"@backstage/plugin-search": "^0.3.4",
|
||||
|
||||
@@ -46,4 +46,4 @@ export { plugin as Kafka } from '@backstage/plugin-kafka';
|
||||
export { todoPlugin } from '@backstage/plugin-todo';
|
||||
export { badgesPlugin } from '@backstage/plugin-badges';
|
||||
export { githubDeploymentsPlugin } from '@backstage/plugin-github-deployments';
|
||||
export { releaseManagerAsAServicePlugin } from '@backstage/plugin-release-manager-as-a-service';
|
||||
export { releaseManagerAsAServicePlugin } from '@backstage/plugin-github-release-manager';
|
||||
|
||||
+7
-7
@@ -1,14 +1,14 @@
|
||||
# Release Manager as a Service (RMaaS)
|
||||
# GitHub Release Manager (GRM)
|
||||
|
||||
## Overview
|
||||
|
||||
`RMaaS` enables developers to manage their releases without having to juggle git commands.
|
||||
`GRM` enables developers to manage their releases without having to juggle git commands.
|
||||
|
||||
Does it build and ship your code? **No**.
|
||||
|
||||
What `RMaaS` does is manage your **[releases](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository)** on GitHub, building and shipping is entirely up to you as a developer to handle in your CI.
|
||||
What `GRM` does is manage your **[releases](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository)** on GitHub, building and shipping is entirely up to you as a developer to handle in your CI.
|
||||
|
||||
`RMaaS` is built with industry standards in mind and the flow is as follows:
|
||||
`GRM` is built with industry standards in mind and the flow is as follows:
|
||||
|
||||

|
||||
|
||||
@@ -21,7 +21,7 @@ What `RMaaS` does is manage your **[releases](https://docs.github.com/en/free-pr
|
||||
Looking at the flow above, a common release lifecycle could be:
|
||||
|
||||
- User presses **Create Release Candidate**
|
||||
- `RMaaS`
|
||||
- `GRM`
|
||||
1. Creates a release branch `rc/<version>`
|
||||
1. Creates Release Candidate tag `rc-<version>`
|
||||
1. Creates a GitHub prerelease with Release Candidate tag
|
||||
@@ -29,7 +29,7 @@ Looking at the flow above, a common release lifecycle could be:
|
||||
1. Detects the new tag by matching the git reference `refs/tags/rc-.*`
|
||||
1. Builds and deploys to staging environment for testing
|
||||
- User presses **Patch**
|
||||
- `RMaaS`
|
||||
- `GRM`
|
||||
1. The selected commit is cherry-picked to the release branch
|
||||
1. The release tag is bumped
|
||||
1. Updates GitHub release's tag and description with the patch's details
|
||||
@@ -37,7 +37,7 @@ Looking at the flow above, a common release lifecycle could be:
|
||||
1. Detects the new tag by matching the git reference `refs/tags/(rc|version)-.*` (Release Versions are patchable as well)
|
||||
1. Builds and deploys to staging (or production if Release Version) for testing
|
||||
- User presses **Promote Release Candidate to Release Version**
|
||||
- `RMaaS`
|
||||
- `GRM`
|
||||
1. Creates Release Version tag `version-<version>`
|
||||
1. Promotes the GitHub release by removing the prerelease flag
|
||||
- Your CI
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
# release-manager-as-a-service
|
||||
# github-release-manager
|
||||
|
||||
Welcome to the release-manager-as-a-service plugin!
|
||||
Welcome to the github-release-manager plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
|
||||
## Getting started
|
||||
|
||||
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/release-manager-as-a-service](http://localhost:3000/release-manager-as-a-service).
|
||||
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/github-release-manager](http://localhost:3000/github-release-manager).
|
||||
|
||||
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
||||
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
||||
+2
-2
@@ -16,12 +16,12 @@
|
||||
import React from 'react';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import {
|
||||
releaseManagerAsAServicePlugin,
|
||||
gitHubReleaseManagerPlugin,
|
||||
ReleaseManagerAsAServicePage,
|
||||
} from '../src/plugin';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(releaseManagerAsAServicePlugin)
|
||||
.registerPlugin(gitHubReleaseManagerPlugin)
|
||||
.addPage({
|
||||
element: (
|
||||
<ReleaseManagerAsAServicePage
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@backstage/plugin-release-manager-as-a-service",
|
||||
"name": "@backstage/plugin-github-release-manager",
|
||||
"version": "0.1.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
+7
-7
@@ -34,12 +34,12 @@ import {
|
||||
SetRefetch,
|
||||
} from './types/types';
|
||||
import { PromoteRc } from './cards/promoteRc/PromoteRc';
|
||||
import { releaseManagerAsAServiceApiRef } from './api/serviceApiRef';
|
||||
import { githubReleaseManagerApiRef } from './api/serviceApiRef';
|
||||
import {
|
||||
ApiClientContext,
|
||||
useApiClientContext,
|
||||
} from './components/ProjectContext';
|
||||
import { RMaaSApiClient } from './api/RMaaSApiClient';
|
||||
import { ApiClient } from './api/ApiClient';
|
||||
|
||||
interface ReleaseManagerAsAServiceProps {
|
||||
project: Project;
|
||||
@@ -75,17 +75,17 @@ export function ReleaseManagerAsAService({
|
||||
project,
|
||||
components,
|
||||
}: ReleaseManagerAsAServiceProps) {
|
||||
const pluginApiClient = useApi(releaseManagerAsAServiceApiRef);
|
||||
const RMaaSApi = new RMaaSApiClient({
|
||||
const pluginApiClient = useApi(githubReleaseManagerApiRef);
|
||||
const apiClient = new ApiClient({
|
||||
pluginApiClient,
|
||||
repoPath: `${project.github.org}/${project.github.repo}`,
|
||||
});
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<ApiClientContext.Provider value={RMaaSApi}>
|
||||
<ApiClientContext.Provider value={apiClient}>
|
||||
<div className={classes.root}>
|
||||
<ContentHeader title="Release Manager as a Service (RMaaS)" />
|
||||
<ContentHeader title="GitHub Release Manager" />
|
||||
|
||||
<Cards project={project} components={components} />
|
||||
</div>
|
||||
@@ -172,7 +172,7 @@ function Cards({ project, components }: ReleaseManagerAsAServiceProps) {
|
||||
repository: gitHubBatchInfo.value.repository,
|
||||
})
|
||||
.map((customElement, index) => (
|
||||
<div key={`rmaas-custom-element-${index}`}>{customElement}</div>
|
||||
<div key={`grm-custom-element-${index}`}>{customElement}</div>
|
||||
))}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
+2
-2
@@ -37,7 +37,7 @@ import { SemverTagParts } from '../helpers/tagParts/getSemverTagParts';
|
||||
* https://github.com/octokit/request.js/#the-data-parameter--set-request-body-directly
|
||||
*/
|
||||
|
||||
export class RMaaSApiClient {
|
||||
export class ApiClient {
|
||||
private readonly pluginApiClient: PluginApiClientConfig;
|
||||
private readonly repoPath: string;
|
||||
private readonly githubCommonPath: string;
|
||||
@@ -317,7 +317,7 @@ export class RMaaSApiClient {
|
||||
data: {
|
||||
type: 'commit',
|
||||
message:
|
||||
'Tag generated by your friendly neighborhood Release Manager as a Service',
|
||||
'Tag generated by your friendly neighborhood GitHub Release Manager',
|
||||
tag: bumpedTag,
|
||||
object: updatedReference.object.sha,
|
||||
},
|
||||
+4
-7
@@ -17,10 +17,7 @@ import { createApiRef } from '@backstage/core';
|
||||
|
||||
import { PluginApiClientConfig } from './PluginApiClientConfig';
|
||||
|
||||
export const releaseManagerAsAServiceApiRef = createApiRef<PluginApiClientConfig>(
|
||||
{
|
||||
id: 'plugin.release-manager-as-a-service.service',
|
||||
description:
|
||||
'Used by the Release Manager as a Service plugin to make requests',
|
||||
},
|
||||
);
|
||||
export const githubReleaseManagerApiRef = createApiRef<PluginApiClientConfig>({
|
||||
id: 'plugin.github-release-manager.service',
|
||||
description: 'Used by the GitHub Release Manager plugin to make requests',
|
||||
});
|
||||
+2
-2
@@ -21,11 +21,11 @@ import {
|
||||
GhGetRepositoryResponse,
|
||||
ResponseStep,
|
||||
} from '../../../types/types';
|
||||
import { RMaaSApiClient } from '../../../api/RMaaSApiClient';
|
||||
import { ApiClient } from '../../../api/ApiClient';
|
||||
import { ReleaseManagerAsAServiceError } from '../../../errors/ReleaseManagerAsAServiceError';
|
||||
|
||||
interface CreateRC {
|
||||
apiClient: RMaaSApiClient;
|
||||
apiClient: ApiClient;
|
||||
defaultBranch: GhGetRepositoryResponse['default_branch'];
|
||||
latestRelease: GhGetReleaseResponse | null;
|
||||
nextGitHubInfo: ReturnType<typeof getRcGitHubInfo>;
|
||||
+3
-3
@@ -73,9 +73,9 @@ export const Info = ({
|
||||
<Typography variant="h6">Flow</Typography>
|
||||
|
||||
<Typography className={classes.paragraph}>
|
||||
RMaaS is built with a specific flow in mind. For example, it assumes
|
||||
your project is configured to react to tags prefixed with <b>rc</b> or{' '}
|
||||
<b>version</b>.
|
||||
GitHub Release Manager is built with a specific flow in mind. For
|
||||
example, it assumes your project is configured to react to tags
|
||||
prefixed with <b>rc</b> or <b>version</b>.
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.paragraph}>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
+2
-2
@@ -21,11 +21,11 @@ import {
|
||||
} from '../../../types/types';
|
||||
import { CalverTagParts } from '../../../helpers/tagParts/getCalverTagParts';
|
||||
import { ReleaseManagerAsAServiceError } from '../../../errors/ReleaseManagerAsAServiceError';
|
||||
import { RMaaSApiClient } from '../../../api/RMaaSApiClient';
|
||||
import { ApiClient } from '../../../api/ApiClient';
|
||||
import { SemverTagParts } from '../../../helpers/tagParts/getSemverTagParts';
|
||||
|
||||
interface Patch {
|
||||
apiClient: RMaaSApiClient;
|
||||
apiClient: ApiClient;
|
||||
bumpedTag: string;
|
||||
latestRelease: GhGetReleaseResponse;
|
||||
selectedPatchCommit: GhGetCommitResponse;
|
||||
+2
-2
@@ -18,10 +18,10 @@ import {
|
||||
GhGetReleaseResponse,
|
||||
ResponseStep,
|
||||
} from '../../../types/types';
|
||||
import { RMaaSApiClient } from '../../../api/RMaaSApiClient';
|
||||
import { ApiClient } from '../../../api/ApiClient';
|
||||
|
||||
interface PromoteRc {
|
||||
apiClient: RMaaSApiClient;
|
||||
apiClient: ApiClient;
|
||||
rcRelease: GhGetReleaseResponse;
|
||||
releaseVersion: string;
|
||||
successCb?: ComponentConfigPromoteRc['successCb'];
|
||||
+2
-4
@@ -15,12 +15,10 @@
|
||||
*/
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
import { RMaaSApiClient } from '../api/RMaaSApiClient';
|
||||
import { ApiClient } from '../api/ApiClient';
|
||||
import { ReleaseManagerAsAServiceError } from '../errors/ReleaseManagerAsAServiceError';
|
||||
|
||||
export const ApiClientContext = createContext<RMaaSApiClient | undefined>(
|
||||
undefined,
|
||||
);
|
||||
export const ApiClientContext = createContext<ApiClient | undefined>(undefined);
|
||||
|
||||
export const useApiClientContext = () => {
|
||||
const apiClient = useContext(ApiClientContext);
|
||||
+1
-1
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export {
|
||||
releaseManagerAsAServicePlugin,
|
||||
gitHubReleaseManagerPlugin as releaseManagerAsAServicePlugin,
|
||||
ReleaseManagerAsAServicePage,
|
||||
} from './plugin';
|
||||
+3
-3
@@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { releaseManagerAsAServicePlugin } from './plugin';
|
||||
import { gitHubReleaseManagerPlugin } from './plugin';
|
||||
|
||||
describe('release-manager-as-a-service', () => {
|
||||
describe('github-release-manager', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(releaseManagerAsAServicePlugin).toBeDefined();
|
||||
expect(gitHubReleaseManagerPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
+6
-8
@@ -21,18 +21,18 @@ import {
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core';
|
||||
|
||||
import { releaseManagerAsAServiceApiRef } from './api/serviceApiRef';
|
||||
import { githubReleaseManagerApiRef } from './api/serviceApiRef';
|
||||
import { PluginApiClientConfig } from './api/PluginApiClientConfig';
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
export const releaseManagerAsAServicePlugin = createPlugin({
|
||||
id: 'release-manager-as-a-service',
|
||||
export const gitHubReleaseManagerPlugin = createPlugin({
|
||||
id: 'github-release-manager',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: releaseManagerAsAServiceApiRef,
|
||||
api: githubReleaseManagerApiRef,
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
githubAuthApi: githubAuthApiRef,
|
||||
@@ -43,12 +43,10 @@ export const releaseManagerAsAServicePlugin = createPlugin({
|
||||
],
|
||||
});
|
||||
|
||||
export const ReleaseManagerAsAServicePage = releaseManagerAsAServicePlugin.provide(
|
||||
export const ReleaseManagerAsAServicePage = gitHubReleaseManagerPlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () =>
|
||||
import('./ReleaseManagerAsAService').then(
|
||||
m => m.ReleaseManagerAsAService,
|
||||
),
|
||||
import('./GitHubReleaseManager').then(m => m.ReleaseManagerAsAService),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
+1
-3
@@ -15,6 +15,4 @@
|
||||
*/
|
||||
import { createRouteRef } from '@backstage/core';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
title: 'release-manager-as-a-service',
|
||||
});
|
||||
export const rootRouteRef = createRouteRef({ title: 'github-release-manager' });
|
||||
+2
-2
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RMaaSApiClient } from '../api/RMaaSApiClient';
|
||||
import { ApiClient } from '../api/ApiClient';
|
||||
import { getLatestRelease } from './getLatestRelease';
|
||||
|
||||
interface GetGitHubBatchInfo {
|
||||
apiClient: RMaaSApiClient;
|
||||
apiClient: ApiClient;
|
||||
}
|
||||
|
||||
export const getGitHubBatchInfo = ({
|
||||
+2
-2
@@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RMaaSApiClient } from '../api/RMaaSApiClient';
|
||||
import { ApiClient } from '../api/ApiClient';
|
||||
|
||||
interface GetLatestRelease {
|
||||
apiClient: RMaaSApiClient;
|
||||
apiClient: ApiClient;
|
||||
}
|
||||
|
||||
export async function getLatestRelease({ apiClient }: GetLatestRelease) {
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const TEST_IDS = {
|
||||
info: {
|
||||
info: 'grm--info',
|
||||
infoCardPlus: 'grm--info-card-plus',
|
||||
},
|
||||
createRc: {
|
||||
cta: 'grm--create-rc--cta',
|
||||
semverSelect: 'grm--create-rc--semver-select',
|
||||
},
|
||||
promoteRc: {
|
||||
mockedPromoteRcBody: 'grm-mocked-promote-rc-body',
|
||||
notRcWarning: 'grm--promote-rc--not-rc-warning',
|
||||
promoteRc: 'grm--promote-rc',
|
||||
cta: 'grm--promote-rc-body--cta',
|
||||
},
|
||||
patch: {
|
||||
error: 'grm--patch-body--error',
|
||||
loading: 'grm--patch-body--loading',
|
||||
notPrerelease: 'grm--patch-body--not-prerelease--info',
|
||||
body: 'grm--patch-body',
|
||||
},
|
||||
components: {
|
||||
divider: 'grm--divider',
|
||||
reloadButton: 'grm--reload-button',
|
||||
noLatestRelease: 'grm--no-latest-release',
|
||||
circularProgress: 'grm--circular-progress',
|
||||
responseStepListDialogContent: 'grm--response-step-list--dialog-content',
|
||||
responseStepListItem: 'grm--response-step-list-item',
|
||||
responseStepListItemIconSuccess:
|
||||
'grm--response-step-list-item--item-icon--success',
|
||||
responseStepListItemIconFailure:
|
||||
'grm--response-step-list-item--item-icon--failure',
|
||||
responseStepListItemIconLink:
|
||||
'grm--response-step-list-item--item-icon--link',
|
||||
responseStepListItemIconDefault:
|
||||
'grm--response-step-list-item--item-icon--default',
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export interface Project {
|
||||
/** A unique (in the context of RMaaS) project name */
|
||||
/** A unique (in the context of GitHub Release Manager) project name */
|
||||
name: string;
|
||||
|
||||
/** GitHub details */
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB |
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const TEST_IDS = {
|
||||
info: {
|
||||
info: 'rmaas--info',
|
||||
infoCardPlus: 'rmaas--info-card-plus',
|
||||
},
|
||||
createRc: {
|
||||
cta: 'rmaas--create-rc--cta',
|
||||
semverSelect: 'rmaas--create-rc--semver-select',
|
||||
},
|
||||
promoteRc: {
|
||||
mockedPromoteRcBody: 'rmaas-mocked-promote-rc-body',
|
||||
notRcWarning: 'rmaas--promote-rc--not-rc-warning',
|
||||
promoteRc: 'rmaas--promote-rc',
|
||||
cta: 'rmaas--promote-rc-body--cta',
|
||||
},
|
||||
patch: {
|
||||
error: 'rmaas--patch-body--error',
|
||||
loading: 'rmaas--patch-body--loading',
|
||||
notPrerelease: 'rmaas--patch-body--not-prerelease--info',
|
||||
body: 'rmaas--patch-body',
|
||||
},
|
||||
components: {
|
||||
divider: 'rmaas--divider',
|
||||
reloadButton: 'rmaas--reload-button',
|
||||
noLatestRelease: 'rmaas--no-latest-release',
|
||||
circularProgress: 'rmaas--circular-progress',
|
||||
responseStepListDialogContent: 'rmaas--response-step-list--dialog-content',
|
||||
responseStepListItem: 'rmaas--response-step-list-item',
|
||||
responseStepListItemIconSuccess:
|
||||
'rmaas--response-step-list-item--item-icon--success',
|
||||
responseStepListItemIconFailure:
|
||||
'rmaas--response-step-list-item--item-icon--failure',
|
||||
responseStepListItemIconLink:
|
||||
'rmaas--response-step-list-item--item-icon--link',
|
||||
responseStepListItemIconDefault:
|
||||
'rmaas--response-step-list-item--item-icon--default',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user