diff --git a/src/sentry/api/endpoints/organization_incident_index.py b/src/sentry/api/endpoints/organization_incident_index.py index c72686d45ac2c6..9ee92b631f0a34 100644 --- a/src/sentry/api/endpoints/organization_incident_index.py +++ b/src/sentry/api/endpoints/organization_incident_index.py @@ -1,8 +1,6 @@ from __future__ import absolute_import from rest_framework import serializers -from rest_framework.exceptions import PermissionDenied -from rest_framework.response import Response from sentry import features from sentry.api.bases.incident import IncidentPermission @@ -11,8 +9,7 @@ from sentry.api.paginator import OffsetPaginator from sentry.api.serializers import serialize from sentry.api.serializers.rest_framework import ListField -from sentry.incidents.logic import create_incident -from sentry.incidents.models import Incident, IncidentStatus, IncidentType +from sentry.incidents.models import Incident, IncidentStatus from sentry.models.group import Group from sentry.models.project import Project from sentry.snuba.models import QueryAggregations @@ -84,32 +81,3 @@ def get(self, request, organization): on_results=lambda x: serialize(x, request.user), default_per_page=25, ) - - def post(self, request, organization): - if not features.has("organizations:incidents", organization, actor=request.user): - return self.respond(status=404) - - serializer = IncidentSerializer(data=request.data, context={"organization": organization}) - - if serializer.is_valid(): - - result = serializer.validated_data - groups = result["groups"] - all_projects = set(result["projects"]) | set(g.project for g in result["groups"]) - if any(p for p in all_projects if not request.access.has_project_access(p)): - raise PermissionDenied - - incident = create_incident( - organization=organization, - type=IncidentType.CREATED, - title=result["title"], - query=result.get("query", ""), - aggregation=result["aggregation"], - date_started=result.get("dateStarted"), - date_detected=result.get("dateDetected"), - projects=result["projects"], - groups=groups, - user=request.user, - ) - return Response(serialize(incident, request.user), status=201) - return Response(serializer.errors, status=400) diff --git a/src/sentry/static/sentry/app/actionCreators/incident.jsx b/src/sentry/static/sentry/app/actionCreators/incident.jsx index f6b57d6b29e876..4f5aad6e9775d4 100644 --- a/src/sentry/static/sentry/app/actionCreators/incident.jsx +++ b/src/sentry/static/sentry/app/actionCreators/incident.jsx @@ -1,41 +1,6 @@ -import { - addErrorMessage, - addLoadingMessage, - clearIndicators, -} from 'app/actionCreators/indicator'; +import {addErrorMessage, clearIndicators} from 'app/actionCreators/indicator'; import {t} from 'app/locale'; -/** - * Creates a new incident - * - * @param {Object} api API Client - * @param {Object} organization Organization object - * @param {String} title Title of the incident - * @param {String[]} groups List of group ids - */ -export async function createIncident(api, organization, title, groups) { - addLoadingMessage(t('Creating new incident...')); - - try { - const resp = await api.requestPromise( - `/organizations/${organization.slug}/incidents/`, - { - method: 'POST', - data: { - title, - groups, - query: '', - }, - } - ); - clearIndicators(); - return resp; - } catch (err) { - addErrorMessage(t('Unable to create incident')); - throw err; - } -} - /** * Fetches a list of activities for an incident */ diff --git a/src/sentry/static/sentry/app/actionCreators/modal.tsx b/src/sentry/static/sentry/app/actionCreators/modal.tsx index 965d50f1f34ba3..a3c1543354f5d6 100644 --- a/src/sentry/static/sentry/app/actionCreators/modal.tsx +++ b/src/sentry/static/sentry/app/actionCreators/modal.tsx @@ -100,20 +100,6 @@ export function openDiffModal(options: ModalOptions) { }); } -/** - * @param Object options - * @param Object options.organization The organization to create a team for - */ -export function openCreateIncidentModal(options: ModalOptions = {}) { - import(/* webpackChunkName: "CreateIncidentModal" */ 'app/components/modals/createIncidentModal') - .then(mod => mod.default) - .then(Modal => { - openModal(deps => ( - - )); - }); -} - /** * @param Object options * @param Object options.organization The organization to create a team for diff --git a/src/sentry/static/sentry/app/components/modals/createIncidentModal.jsx b/src/sentry/static/sentry/app/components/modals/createIncidentModal.jsx deleted file mode 100644 index 45cb7a4ee3b118..00000000000000 --- a/src/sentry/static/sentry/app/components/modals/createIncidentModal.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import {browserHistory} from 'react-router'; -import PropTypes from 'prop-types'; -import React from 'react'; - -import {createIncident} from 'app/actionCreators/incident'; -import {t} from 'app/locale'; -import Form from 'app/views/settings/components/forms/form'; -import SentryTypes from 'app/sentryTypes'; -import TextField from 'app/views/settings/components/forms/textField'; -import withApi from 'app/utils/withApi'; - -class CreateIncidentModal extends React.Component { - static propTypes = { - api: PropTypes.object.isRequired, - issues: PropTypes.arrayOf(PropTypes.string), - closeModal: PropTypes.func, - onClose: PropTypes.func, - Body: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, - Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired, - organization: SentryTypes.Organization.isRequired, - }; - - handleSubmit = async (data, onSuccess, onError, _e, model) => { - const {api, organization, issues} = this.props; - - model.setFormSaving(); - - try { - const incident = await createIncident(api, organization, data.title, issues); - onSuccess(incident); - } catch (err) { - onError(err); - } - }; - - handleSuccess = data => { - const {organization, onClose, closeModal} = this.props; - - if (onClose) { - onClose(data); - } - - closeModal(); - - if (data) { - browserHistory.push( - `/organizations/${organization.slug}/incidents/${data.identifier}/` - ); - } - }; - - render() { - const {Body, Header, closeModal} = this.props; - - return ( - -
- {t('Create New Incident')} -
- -
- - - -
- ); - } -} - -export default withApi(CreateIncidentModal); diff --git a/src/sentry/static/sentry/app/views/incidents/list/index.tsx b/src/sentry/static/sentry/app/views/incidents/list/index.tsx index a0fe569733769a..f6767ec33c4820 100644 --- a/src/sentry/static/sentry/app/views/incidents/list/index.tsx +++ b/src/sentry/static/sentry/app/views/incidents/list/index.tsx @@ -8,8 +8,7 @@ import styled from 'react-emotion'; import {PageContent, PageHeader} from 'app/styles/organization'; import {Panel, PanelBody, PanelHeader, PanelItem} from 'app/components/panels'; -import {t, tct} from 'app/locale'; -import AlertLink from 'app/components/alertLink'; +import {t} from 'app/locale'; import AsyncComponent from 'app/components/asyncComponent'; import BetaTag from 'app/components/betaTag'; import Button from 'app/components/button'; @@ -175,17 +174,6 @@ class IncidentsListContainer extends React.Component { - - {tct( - 'To create a new Incident, select one or more issues from the Issues view. Then, click the [create:Create Incident] button.', - {create: } - )} - - diff --git a/src/sentry/static/sentry/app/views/issueList/actions.jsx b/src/sentry/static/sentry/app/views/issueList/actions.jsx index 926abae699ee30..1dcb9167faec1a 100644 --- a/src/sentry/static/sentry/app/views/issueList/actions.jsx +++ b/src/sentry/static/sentry/app/views/issueList/actions.jsx @@ -6,7 +6,6 @@ import Reflux from 'reflux'; import createReactClass from 'create-react-class'; import styled from 'react-emotion'; -import {openCreateIncidentModal} from 'app/actionCreators/modal'; import {t, tct, tn} from 'app/locale'; import space from 'app/styles/space'; import theme from 'app/utils/theme'; @@ -14,11 +13,9 @@ import ActionLink from 'app/components/actions/actionLink'; import Checkbox from 'app/components/checkbox'; import DropdownLink from 'app/components/dropdownLink'; import ExternalLink from 'app/components/links/externalLink'; -import Feature from 'app/components/acl/feature'; import GroupStore from 'app/stores/groupStore'; import IgnoreActions from 'app/components/actions/ignore'; import IndicatorStore from 'app/stores/indicatorStore'; -import InlineSvg from 'app/components/inlineSvg'; import MenuItem from 'app/components/menuItem'; import Projects from 'app/utils/projects'; import ResolveActions from 'app/components/actions/resolve'; @@ -156,7 +153,6 @@ const IssueListActions = createReactClass({ statsPeriod: PropTypes.string.isRequired, query: PropTypes.string.isRequired, queryCount: PropTypes.number, - organization: SentryTypes.Organization, }, mixins: [Reflux.listenTo(SelectedGroupStore, 'handleSelectedGroupChange')], @@ -315,12 +311,6 @@ const IssueListActions = createReactClass({ }); }, - handleCreateIncident() { - const {organization} = this.props; - const issues = this.state.selectedIds; - openCreateIncidentModal({organization, issues: Array.from(issues)}); - }, - handleSelectAll() { SelectedGroupStore.toggleSelectAll(); }, @@ -404,7 +394,6 @@ const IssueListActions = createReactClass({ // merges require a single project to be active in an org context // selectedProjectSlug is null when 0 or >1 projects are selected. const mergeDisabled = !(multiSelected && selectedProjectSlug); - const createNewIncidentDisabled = !anySelected || allInQuerySelected; return ( @@ -457,25 +446,6 @@ const IssueListActions = createReactClass({ {t('Merge')} - -
- - - - {t('Create Incident')} - - -
-
- - - {t('Create Incident')} - - - , - TestStubs.routerContext() - ); - - wrapper.find('Input[name="title"]').simulate('change', {target: {value: 'Oh no'}}); - - wrapper.find('Form').simulate('submit'); - - expect(mock).toHaveBeenCalledWith( - '/organizations/org-slug/incidents/', - expect.objectContaining({ - data: { - groups: ['123', '456'], - query: '', - title: 'Oh no', - }, - method: 'POST', - }) - ); - await tick(); - expect(onClose).toHaveBeenCalled(); - expect(closeModal).toHaveBeenCalled(); - - expect(browserHistory.push).toHaveBeenCalledWith( - '/organizations/org-slug/incidents/11111/' - ); - }); -}); diff --git a/tests/js/spec/views/issueList/actions.spec.jsx b/tests/js/spec/views/issueList/actions.spec.jsx index 857c7e714431d7..84a4648207963b 100644 --- a/tests/js/spec/views/issueList/actions.spec.jsx +++ b/tests/js/spec/views/issueList/actions.spec.jsx @@ -17,11 +17,7 @@ describe('IssueListActions', function() { describe('Bulk', function() { describe('Total results > bulk limit', function() { beforeAll(function() { - const {routerContext} = initializeOrg({ - organization: { - features: ['incidents'], - }, - }); + const {routerContext} = initializeOrg(); SelectedGroupStore.records = {}; SelectedGroupStore.add([1, 2, 3]); @@ -59,22 +55,6 @@ describe('IssueListActions', function() { expect(wrapper.find('.stream-select-all-notice')).toMatchSnapshot(); }); - it('has "Create Incidents" disabled', function() { - // Do not allow users to create incidents with "bulk" selection - expect( - wrapper - .find('a[aria-label="Create new incident"]') - .at(0) - .prop('disabled') - ).toBe(true); - expect( - wrapper - .find('a[aria-label="Create new incident"]') - .at(1) - .prop('disabled') - ).toBe(true); - }); - it('bulk resolves', async function() { const apiMock = MockApiClient.addMockResponse({ url: '/organizations/1337/issues/', diff --git a/tests/js/spec/views/issueList/createIncident.spec.jsx b/tests/js/spec/views/issueList/createIncident.spec.jsx deleted file mode 100644 index 7321e9d7508f1d..00000000000000 --- a/tests/js/spec/views/issueList/createIncident.spec.jsx +++ /dev/null @@ -1,201 +0,0 @@ -import {browserHistory} from 'react-router'; -import React from 'react'; - -import {initializeOrg} from 'sentry-test/initializeOrg'; -import {mountWithTheme} from 'sentry-test/enzyme'; -import GlobalModal from 'app/components/globalModal'; -import IssueListWithStores from 'app/views/issueList/overview'; -import TagStore from 'app/stores/tagStore'; - -jest.mock('app/views/issueList/sidebar', () => jest.fn(() => null)); - -describe('IssueList --> Create Incident', function() { - let wrapper; - - const {project, router, routerContext} = initializeOrg({ - organization: { - features: ['global-views', 'incidents'], - access: ['releases'], - slug: 'org-slug', - }, - router: { - location: {query: {}, search: ''}, - params: {orgId: 'org-slug'}, - }, - }); - const defaultProps = {}; - - const group = TestStubs.Group({project}); - const savedSearch = TestStubs.Search({ - id: '789', - query: 'is:unresolved', - name: 'Unresolved Issues', - projectId: project.id, - }); - - TagStore.init(); - - const createWrapper = ({params, location, ...p} = {}) => { - const newRouter = { - ...router, - params: { - ...router.params, - ...params, - }, - location: { - ...router.location, - ...location, - }, - }; - - wrapper = mountWithTheme( -
- - -
, - routerContext - ); - }; - - beforeEach(function() { - MockApiClient.clearMockResponses(); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/projects/', - body: [], - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/searches/', - body: [savedSearch], - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/recent-searches/', - body: [], - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/processingissues/', - method: 'GET', - body: [ - { - project: 'test-project', - numIssues: 1, - hasIssues: true, - lastSeen: '2019-01-16T15:39:11.081Z', - }, - ], - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/tags/', - method: 'GET', - body: TestStubs.Tags(), - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/users/', - method: 'GET', - body: [TestStubs.Member({projects: [project.slug]})], - }); - - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/recent-searches/', - method: 'GET', - body: [], - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/searches/', - body: [savedSearch], - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/issues/', - body: [ - group, - TestStubs.Group({ - id: '2', - }), - ], - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/sent-first-event/', - body: {sentFirstEvent: true}, - }); - MockApiClient.addMockResponse({ - url: '/organizations/org-slug/projects/', - body: [project], - }); - }); - - afterEach(function() { - MockApiClient.clearMockResponses(); - if (wrapper) { - wrapper.unmount(); - } - wrapper = null; - }); - - it('creates an incident by selecting issues from stream', async function() { - const createIncident = MockApiClient.addMockResponse({ - url: '/organizations/org-slug/incidents/', - method: 'POST', - body: { - identifier: '468', - }, - }); - createWrapper({ - selection: { - projects: [123], - environments: ['prod'], - datetime: {}, - }, - location: {query: {project: ['123'], environment: ['prod']}}, - }); - - await tick(); - await tick(); - wrapper.update(); - - // Select checkboxes - wrapper - .find('GroupCheckbox') - .at(0) - .simulate('click'); - wrapper - .find('GroupCheckbox') - .at(1) - .simulate('click'); - - wrapper - .find('[data-test-id="create-incident"]') - .at(0) - .simulate('click'); - - // Needs two ticks, one for reflux and one for dynamic import maybe? idk - await tick(); - await tick(); - wrapper.update(); - - wrapper - .find('input[name="title"]') - .simulate('change', {target: {value: 'New Incident'}}) - .simulate('blur'); - - wrapper.find('Form').simulate('submit'); - - expect(createIncident).toHaveBeenCalledWith( - '/organizations/org-slug/incidents/', - expect.objectContaining({ - data: { - groups: ['1', '2'], - query: '', - title: 'New Incident', - }, - }) - ); - - // form model submitting requires this? - await tick(); - wrapper.update(); - - // redirect to details - expect(browserHistory.push).toHaveBeenCalledWith( - '/organizations/org-slug/incidents/468/' - ); - }); -}); diff --git a/tests/sentry/api/endpoints/test_organization_incident_index.py b/tests/sentry/api/endpoints/test_organization_incident_index.py index 946db6a9280d62..1e6b3555256f7f 100644 --- a/tests/sentry/api/endpoints/test_organization_incident_index.py +++ b/tests/sentry/api/endpoints/test_organization_incident_index.py @@ -1,11 +1,9 @@ from __future__ import absolute_import -from django.utils import timezone from exam import fixture -from freezegun import freeze_time from sentry.api.serializers import serialize -from sentry.incidents.models import Incident, IncidentStatus, IncidentActivity +from sentry.incidents.models import IncidentStatus from sentry.testutils import APITestCase @@ -55,88 +53,3 @@ def test_no_feature(self): self.login_as(self.user) resp = self.get_response(self.organization.slug) assert resp.status_code == 404 - - -@freeze_time() -class IncidentCreateEndpointTest(APITestCase): - endpoint = "sentry-api-0-organization-incident-index" - method = "post" - - @fixture - def organization(self): - return self.create_organization() - - @fixture - def project(self): - return self.create_project(organization=self.organization) - - @fixture - def user(self): - return self.create_user() - - def test_simple(self): - self.create_member( - user=self.user, organization=self.organization, role="owner", teams=[self.team] - ) - self.login_as(self.user) - with self.feature("organizations:incidents"): - resp = self.get_valid_response( - self.organization.slug, - title="hello", - query="hi", - dateStarted=timezone.now(), - projects=[self.project.slug], - groups=[self.group.id], - status_code=201, - ) - assert resp.data == serialize([Incident.objects.get(id=resp.data["id"])])[0] - - # should create an activity authored by user - activity = IncidentActivity.objects.get(incident_id=resp.data["id"]) - assert activity.user == self.user - - def test_project_access(self): - self.create_member( - user=self.user, organization=self.organization, role="owner", teams=[self.team] - ) - self.login_as(self.user) - - other_org = self.create_organization(owner=self.create_user()) - other_project = self.create_project(organization=other_org, teams=[]) - with self.feature("organizations:incidents"): - resp = self.get_response( - other_org.slug, - title="hello", - query="hi", - dateStarted=timezone.now(), - projects=[other_project.slug], - groups=[self.group.id], - ) - assert resp.status_code == 403 - - def test_group_access(self): - self.create_member( - user=self.user, organization=self.organization, role="owner", teams=[self.team] - ) - self.login_as(self.user) - - other_org = self.create_organization(owner=self.create_user()) - other_project = self.create_project(organization=other_org, teams=[]) - other_group = self.create_group(project=other_project) - with self.feature("organizations:incidents"): - resp = self.get_response( - self.organization.slug, - title="hello", - query="hi", - dateStarted=timezone.now(), - groups=[self.group.id, other_group.id], - ) - assert resp.status_code == 400, resp.content - - def test_no_feature(self): - self.create_member( - user=self.user, organization=self.organization, role="owner", teams=[self.team] - ) - self.login_as(self.user) - resp = self.get_response(self.organization.slug) - assert resp.status_code == 404