Files
2024-02-15 17:01:50 +00:00

278 lines
7.4 KiB
TypeScript

import {Component, Fragment} from 'react';
import type {RouteComponentProps} from 'react-router';
import styled from '@emotion/styled';
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import type {Client} from 'sentry/api';
import Access from 'sentry/components/acl/access';
import {Button} from 'sentry/components/button';
import EmptyMessage from 'sentry/components/emptyMessage';
import ListLink from 'sentry/components/links/listLink';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import NavTabs from 'sentry/components/navTabs';
import Panel from 'sentry/components/panels/panel';
import PanelBody from 'sentry/components/panels/panelBody';
import PanelHeader from 'sentry/components/panels/panelHeader';
import PanelItem from 'sentry/components/panels/panelItem';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import {ALL_ENVIRONMENTS_KEY} from 'sentry/constants';
import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Environment, Organization, Project} from 'sentry/types';
import {getDisplayName, getUrlRoutingName} from 'sentry/utils/environment';
import recreateRoute from 'sentry/utils/recreateRoute';
import withApi from 'sentry/utils/withApi';
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
type Props = {
api: Client;
organization: Organization;
project: Project;
} & RouteComponentProps<{projectId: string}, {}>;
type State = {
environments: null | Environment[];
isLoading: boolean;
};
class ProjectEnvironments extends Component<Props, State> {
state: State = {
environments: null,
isLoading: true,
};
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps: Props) {
if (
this.props.location.pathname.endsWith('hidden/') !==
prevProps.location.pathname.endsWith('hidden/')
) {
this.fetchData();
}
}
fetchData() {
const isHidden = this.props.location.pathname.endsWith('hidden/');
if (!this.state.isLoading) {
this.setState({isLoading: true});
}
const {organization} = this.props;
const {projectId} = this.props.params;
this.props.api.request(`/projects/${organization.slug}/${projectId}/environments/`, {
query: {
visibility: isHidden ? 'hidden' : 'visible',
},
success: environments => {
this.setState({environments, isLoading: false});
},
});
}
// Toggle visibility of environment
toggleEnv = (env: Environment, shouldHide: boolean) => {
const {organization} = this.props;
const {projectId} = this.props.params;
this.props.api.request(
`/projects/${organization.slug}/${projectId}/environments/${getUrlRoutingName(
env
)}/`,
{
method: 'PUT',
data: {
name: env.name,
isHidden: shouldHide,
},
success: () => {
addSuccessMessage(
tct('Updated [environment]', {
environment: getDisplayName(env),
})
);
},
error: () => {
addErrorMessage(
tct('Unable to update [environment]', {
environment: getDisplayName(env),
})
);
},
complete: this.fetchData.bind(this),
}
);
};
renderEmpty() {
const isHidden = this.props.location.pathname.endsWith('hidden/');
const message = isHidden
? t("You don't have any hidden environments.")
: t("You don't have any environments yet.");
return <EmptyMessage>{message}</EmptyMessage>;
}
/**
* Renders rows for "system" environments:
* - "All Environments"
* - "No Environment"
*
*/
renderAllEnvironmentsSystemRow() {
// Not available in "Hidden" tab
const isHidden = this.props.location.pathname.endsWith('hidden/');
if (isHidden) {
return null;
}
const {project} = this.props;
return (
<EnvironmentRow
project={project}
name={ALL_ENVIRONMENTS_KEY}
environment={{
id: ALL_ENVIRONMENTS_KEY,
name: ALL_ENVIRONMENTS_KEY,
displayName: ALL_ENVIRONMENTS_KEY,
}}
isSystemRow
/>
);
}
renderEnvironmentList(envs: Environment[]) {
const {project} = this.props;
const isHidden = this.props.location.pathname.endsWith('hidden/');
const buttonText = isHidden ? t('Show') : t('Hide');
return (
<Fragment>
{this.renderAllEnvironmentsSystemRow()}
{envs.map(env => (
<EnvironmentRow
project={project}
key={env.id}
name={env.name}
environment={env}
isHidden={isHidden}
onHide={this.toggleEnv}
actionText={buttonText}
shouldShowAction
/>
))}
</Fragment>
);
}
renderBody() {
const {environments, isLoading} = this.state;
if (isLoading) {
return <LoadingIndicator />;
}
return (
<PanelBody>
{environments?.length
? this.renderEnvironmentList(environments)
: this.renderEmpty()}
</PanelBody>
);
}
render() {
const {routes, params, location, project} = this.props;
const isHidden = location.pathname.endsWith('hidden/');
const baseUrl = recreateRoute('', {routes, params, stepBack: -1});
return (
<div>
<SentryDocumentTitle title={t('Environments')} projectSlug={params.projectId} />
<SettingsPageHeader
title={t('Manage Environments')}
tabs={
<NavTabs underlined>
<ListLink to={baseUrl} index isActive={() => !isHidden}>
{t('Environments')}
</ListLink>
<ListLink to={`${baseUrl}hidden/`} index isActive={() => isHidden}>
{t('Hidden')}
</ListLink>
</NavTabs>
}
/>
<PermissionAlert project={project} />
<Panel>
<PanelHeader>{isHidden ? t('Hidden') : t('Active Environments')}</PanelHeader>
{this.renderBody()}
</Panel>
</div>
);
}
}
type RowProps = {
environment: Environment;
name: string;
project: Project;
actionText?: string;
isHidden?: boolean;
isSystemRow?: boolean;
onHide?: (env: Environment, isHidden: boolean) => void;
shouldShowAction?: boolean;
};
function EnvironmentRow({
project,
environment,
name,
onHide,
shouldShowAction = false,
isSystemRow = false,
isHidden = false,
actionText = '',
}: RowProps) {
return (
<EnvironmentItem>
<Name>{isSystemRow ? t('All Environments') : name}</Name>
<Access access={['project:write']} project={project}>
{({hasAccess}) => (
<Fragment>
{shouldShowAction && onHide && (
<EnvironmentButton
size="xs"
disabled={!hasAccess}
onClick={() => onHide(environment, !isHidden)}
>
{actionText}
</EnvironmentButton>
)}
</Fragment>
)}
</Access>
</EnvironmentItem>
);
}
const EnvironmentItem = styled(PanelItem)`
align-items: center;
justify-content: space-between;
`;
const Name = styled('div')`
display: flex;
align-items: center;
`;
const EnvironmentButton = styled(Button)`
margin-left: ${space(0.5)};
`;
export {ProjectEnvironments};
export default withApi(ProjectEnvironments);