Browse Source

feat(releases): Releases in progress / setup state (#11863)

Displays the landing page if no selected project has any releases.
Renders release progress if a single project is selected and releases
are not fully set up yet.
Lyn Nagara 6 years ago
parent
commit
09bc98df6e

+ 50 - 21
src/sentry/static/sentry/app/views/releases/list/organizationReleases/index.jsx

@@ -13,6 +13,7 @@ import Feature from 'app/components/acl/feature';
 import GlobalSelectionHeader from 'app/components/organizations/globalSelectionHeader';
 import AsyncView from 'app/views/asyncView';
 import withOrganization from 'app/utils/withOrganization';
+import withGlobalSelection from 'app/utils/withGlobalSelection';
 
 import {PageContent, PageHeader} from 'app/styles/organization';
 import PageHeading from 'app/components/pageHeading';
@@ -20,11 +21,13 @@ import PageHeading from 'app/components/pageHeading';
 import ReleaseList from '../shared/releaseList';
 import ReleaseListHeader from '../shared/releaseListHeader';
 import ReleaseLanding from '../shared/releaseLanding';
+import ReleaseProgress from '../shared/releaseProgress';
 import {getQuery} from '../shared/utils';
 
 class OrganizationReleasesContainer extends React.Component {
   static propTypes = {
-    organization: SentryTypes.Organization,
+    organization: SentryTypes.Organization.isRequired,
+    selection: SentryTypes.GlobalSelection.isRequired,
   };
 
   renderNoAccess() {
@@ -81,19 +84,14 @@ class OrganizationReleases extends AsyncView {
     });
   };
 
-  hasFilters() {
-    const {location} = this.props;
-
-    const hasQueryFilter = !!location.query.query;
-    const hasEnvironmentFilter = !!location.query.environment;
-
-    // If all or one project is selected show the setup screen
-    // We check the type since a single project value is represented as a
-    // string when deserialized and multiple values as an array.
-    const hasProjectFilter =
-      !location.query.project || Array.isArray(location.query.project);
-
-    return hasQueryFilter || hasProjectFilter || hasEnvironmentFilter;
+  // Returns true if there has been a release in any selected project, otherwise false
+  hasAnyRelease() {
+    const {organization: {projects}, selection} = this.props;
+    const projectIds = new Set(selection.projects);
+    const activeProjects = projects.filter(project =>
+      projectIds.has(parseInt(project.id, 10))
+    );
+    return activeProjects.some(project => !!project.latestRelease);
   }
 
   renderStreamBody() {
@@ -105,21 +103,52 @@ class OrganizationReleases extends AsyncView {
     }
 
     if (releaseList.length === 0) {
-      return this.hasFilters() ? this.renderNoQueryResults() : this.renderEmpty();
+      return this.hasAnyRelease() ? this.renderNoQueryResults() : this.renderLanding();
+    }
+
+    return (
+      <React.Fragment>
+        {this.renderReleaseProgress()}
+        <ReleaseList releaseList={releaseList} orgId={organization.slug} />
+      </React.Fragment>
+    );
+  }
+
+  renderReleaseProgress() {
+    const {organization, selection} = this.props;
+    const allAccessibleProjects = organization.projects.filter(
+      project => project.isMember
+    );
+
+    const hasSingleProject =
+      selection.projects.length === 1 ||
+      (selection.projects === 0 && allAccessibleProjects.length === 1);
+
+    if (!hasSingleProject) {
+      return null;
     }
 
-    return <ReleaseList releaseList={releaseList} orgId={organization.slug} />;
+    const releaseProject = selection.projects.length
+      ? allAccessibleProjects.find(
+          project => parseInt(project.id, 10) === selection.projects[0]
+        )
+      : allAccessibleProjects[0];
+
+    return <ReleaseProgress project={releaseProject} />;
   }
 
   renderNoQueryResults() {
     return (
-      <EmptyStateWarning>
-        <p>{t('Sorry, no releases match your filters.')}</p>
-      </EmptyStateWarning>
+      <React.Fragment>
+        {this.renderReleaseProgress()}
+        <EmptyStateWarning>
+          <p>{t('Sorry, no releases match your filters.')}</p>
+        </EmptyStateWarning>
+      </React.Fragment>
     );
   }
 
-  renderEmpty() {
+  renderLanding() {
     return <ReleaseLanding />;
   }
 
@@ -155,4 +184,4 @@ class OrganizationReleases extends AsyncView {
   }
 }
 
-export default withOrganization(OrganizationReleasesContainer);
+export default withOrganization(withGlobalSelection(OrganizationReleasesContainer));

+ 24 - 1
tests/js/spec/views/releases/list/organizationReleases.spec.jsx

@@ -24,8 +24,17 @@ describe('OrganizationReleases', function() {
       body: TestStubs.Environments(),
     });
 
+    MockApiClient.addMockResponse({
+      url: '/promptsactivity/',
+    });
+
+    MockApiClient.addMockResponse({
+      url: '/projects/org-slug/project-slug/releases/completion/',
+    });
+
     props = {
       organization,
+      selection: {projects: [2]},
       params: {orgId: organization.slug},
       location: {query: {per_page: 0, query: 'derp'}},
     };
@@ -47,11 +56,12 @@ describe('OrganizationReleases', function() {
     expect(releases.map(item => item.text())).toEqual(['abc', 'def']);
   });
 
-  it('renders empty state', function() {
+  it('renders no query state if project has a release', function() {
     MockApiClient.addMockResponse({
       url: '/organizations/org-slug/releases/',
       body: [],
     });
+    organization.projects = [TestStubs.Project({latestRelease: 'abcdef'})];
     wrapper = mount(
       <OrganizationReleases {...props} />,
       TestStubs.routerContext([{organization}])
@@ -59,4 +69,17 @@ describe('OrganizationReleases', function() {
     const content = wrapper.find('PageContent');
     expect(content.text()).toContain('Sorry, no releases match your filters');
   });
+
+  it('renders landing state if project does not have a release', function() {
+    MockApiClient.addMockResponse({
+      url: '/organizations/org-slug/releases/',
+      body: [],
+    });
+    wrapper = mount(
+      <OrganizationReleases {...props} />,
+      TestStubs.routerContext([{organization}])
+    );
+    const landing = wrapper.find('ReleaseLanding');
+    expect(landing).toHaveLength(1);
+  });
 });