]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19922 - Onboarding Tutorial - Clean up
authorKevin Silva <kevin.silva@sonarsource.com>
Tue, 18 Jul 2023 13:57:39 +0000 (15:57 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 21 Jul 2023 20:03:16 +0000 (20:03 +0000)
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
server/sonar-web/src/main/js/app/styles/init/type.css
server/sonar-web/src/main/js/apps/overview/components/App.tsx
server/sonar-web/src/main/js/apps/overview/components/EmptyOverview.tsx
server/sonar-web/src/main/js/apps/tutorials/components/TutorialsApp.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-it.tsx
server/sonar-web/src/main/js/helpers/urls.ts

index 3d98cb0a5bbd9d77fe7895fa0bd4f59b82adc131..b0d4039c39e43d99dd1407e4bf9579cc82506ab7 100644 (file)
@@ -29,7 +29,7 @@ import { getComponentNavigation } from '../../api/navigation';
 import { Location, Router, withRouter } from '../../components/hoc/withRouter';
 import { translateWithParameters } from '../../helpers/l10n';
 import { HttpStatus } from '../../helpers/request';
-import { getPortfolioUrl } from '../../helpers/urls';
+import { getPortfolioUrl, getProjectUrl } from '../../helpers/urls';
 import {
   ProjectAlmBindingConfigurationErrors,
   ProjectAlmBindingResponse,
@@ -88,7 +88,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
     window.clearTimeout(this.watchStatusTimer);
   }
 
-  fetchComponent = async () => {
+  fetchComponent = async (shouldRedirectToDashboard = false) => {
     const { branch, id: key, pullRequest } = this.props.location.query;
     this.setState({ loading: true });
 
@@ -131,11 +131,18 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
     }
 
     if (this.mounted) {
-      this.setState({
-        component: componentWithQualifier,
-        projectBinding,
-        loading: false,
-      });
+      this.setState(
+        {
+          component: componentWithQualifier,
+          projectBinding,
+          loading: false,
+        },
+        () => {
+          if (shouldRedirectToDashboard && this.props.location.pathname.match('tutorials')) {
+            this.props.router.replace(getProjectUrl(key));
+          }
+        }
+      );
 
       this.fetchStatus(componentWithQualifier.key);
       this.fetchProjectBindingErrors(componentWithQualifier);
@@ -147,7 +154,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
       ({ current, queue }) => {
         if (this.mounted) {
           let shouldFetchComponent = false;
-
+          let shouldRedirectToDashboard = false;
           this.setState(
             ({ component, currentTask, tasksInProgress }) => {
               const newCurrentTask = this.getCurrentTask(current);
@@ -162,6 +169,9 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
                 component
               );
 
+              shouldRedirectToDashboard =
+                component !== undefined && Boolean(!component.analysisDate);
+
               if (this.needsAnotherCheck(shouldFetchComponent, component, newTasksInProgress)) {
                 // Refresh the status as long as there is tasks in progress or no analysis
                 window.clearTimeout(this.watchStatusTimer);
@@ -182,7 +192,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
             },
             () => {
               if (shouldFetchComponent) {
-                this.fetchComponent();
+                this.fetchComponent(shouldRedirectToDashboard);
               }
             }
           );
index 05c5c8148ca20f2085d96df0513568aefe87051b..2d99e1311e48496e55b271300acbc9c4e5b1acef 100644 (file)
@@ -49,6 +49,8 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND = [
   '/project/information',
 ];
 
+const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = ['/tutorials'];
+
 export default function GlobalContainer() {
   // it is important to pass `location` down to `GlobalNav` to trigger render on url change
   const location = useLocation();
@@ -63,6 +65,9 @@ export default function GlobalContainer() {
               <div
                 className={classNames('page-wrapper', {
                   'new-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND.includes(location.pathname),
+                  'white-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE.includes(
+                    location.pathname
+                  ),
                 })}
                 id="container"
               >
index 0ed48b25ac1d736383956248507fdb461998ecc0..48bf7483773702a9788785dbd29fe030c9246deb 100644 (file)
@@ -298,6 +298,25 @@ it('only fully loads a non-empty component once', async () => {
   expect(getTasksForComponent).toHaveBeenCalledTimes(1);
 });
 
+it('should redirect if in tutorials and ends the first analyses', async () => {
+  (getComponentData as jest.Mock<any>).mockResolvedValueOnce({
+    component: { key: 'bar', analysisDate: undefined },
+  });
+  (getTasksForComponent as jest.Mock<any>).mockResolvedValueOnce({
+    queue: [],
+    current: { id: 'foo', status: TaskStatuses.Success, type: TaskTypes.Report },
+  });
+
+  const replace = jest.fn();
+  const wrapper = shallowRender({
+    location: mockLocation({ pathname: '/tutorials' }),
+    router: mockRouter({ replace }),
+  });
+
+  await waitAndUpdate(wrapper);
+  expect(replace).toHaveBeenCalledTimes(1);
+});
+
 it('only fully reloads a non-empty component if there was previously some task in progress', async () => {
   jest.mocked(getComponentData).mockResolvedValueOnce({
     component: { key: 'bar', analysisDate: '2019-01-01' },
index fdf6e89d8de984051158a9b585ee0e7d64f57c26..260102cbdfe889fc41123ce10f329efcb67482e7 100644 (file)
@@ -298,3 +298,7 @@ small,
 .new-background {
   background-color: #fcfcfd;
 }
+
+.white-background {
+  background-color: #ffffff;
+}
index 96a4bcf287bf25c7e1d1980b9601591a62da4f5a..d6231334fcdcd09bd3e28897a00b02b28e321cd9 100644 (file)
@@ -71,7 +71,6 @@ export function App(props: AppProps) {
               branchLikes={branchLikes}
               component={component}
               hasAnalyses={isPending ?? isInProgress}
-              projectBinding={projectBinding}
             />
           )}
 
index 6da4abfc6ae2dc696a6d9063cb49cb2f176de90b..5ef4b9708c033f1367627de1988f39bb2dd6b3de 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { FlagMessage, LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
 import * as React from 'react';
+import { Navigate } from 'react-router-dom';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import TutorialSelection from '../../../components/tutorials/TutorialSelection';
-import { Alert } from '../../../components/ui/Alert';
 import { getBranchLikeDisplayName, isBranch, isMainBranch } from '../../../helpers/branch-like';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
+import { getProjectTutorialLocation } from '../../../helpers/urls';
 import { BranchLike } from '../../../types/branch-like';
 import { ComponentQualifier } from '../../../types/component';
 import { Component } from '../../../types/types';
@@ -35,19 +35,22 @@ export interface EmptyOverviewProps {
   component: Component;
   currentUser: CurrentUser;
   hasAnalyses?: boolean;
-  projectBinding?: ProjectAlmBindingResponse;
 }
 
 export function EmptyOverview(props: EmptyOverviewProps) {
-  const { branchLike, branchLikes, component, currentUser, hasAnalyses, projectBinding } = props;
+  const { branchLike, branchLikes, component, currentUser, hasAnalyses } = props;
 
   if (component.qualifier === ComponentQualifier.Application) {
     return (
-      <div className="page page-limited">
-        <Alert variant="warning" aria-label={translate('provisioning.no_analysis.application')}>
+      <LargeCenteredLayout className="sw-pt-8">
+        <FlagMessage
+          className="sw-w-full"
+          variant="warning"
+          aria-label={translate('provisioning.no_analysis.application')}
+        >
           {translate('provisioning.no_analysis.application')}
-        </Alert>
-      </div>
+        </FlagMessage>
+      </LargeCenteredLayout>
     );
   } else if (!isBranch(branchLike)) {
     return null;
@@ -60,6 +63,10 @@ export function EmptyOverview(props: EmptyOverviewProps) {
 
   const showTutorial = isMainBranch(branchLike) && !hasBranches && !hasAnalyses;
 
+  if (showTutorial && isLoggedIn(currentUser)) {
+    return <Navigate replace to={getProjectTutorialLocation(component.key)} />;
+  }
+
   let warning;
   if (isLoggedIn(currentUser) && isMainBranch(branchLike) && hasBranches && hasBadBranchConfig) {
     warning = translateWithParameters(
@@ -75,29 +82,23 @@ export function EmptyOverview(props: EmptyOverviewProps) {
   }
 
   return (
-    <div className="page page-limited">
-      {isLoggedIn(currentUser) ? (
-        <>
-          {hasBranches && (
-            <Alert variant="warning" aria-label={warning}>
-              {warning}
-            </Alert>
-          )}
-          {showTutorial && (
-            <TutorialSelection
-              component={component}
-              currentUser={currentUser}
-              projectBinding={projectBinding}
-              willRefreshAutomatically
-            />
-          )}
-        </>
-      ) : (
-        <Alert variant="warning" aria-label={warning}>
-          {warning}
-        </Alert>
-      )}
-    </div>
+    <LargeCenteredLayout className="sw-pt-8">
+      <PageContentFontWrapper>
+        {isLoggedIn(currentUser) ? (
+          <>
+            {hasBranches && (
+              <FlagMessage className="sw-w-full" variant="warning" aria-label={warning}>
+                {warning}
+              </FlagMessage>
+            )}
+          </>
+        ) : (
+          <FlagMessage className="sw-w-full" variant="warning" aria-label={warning}>
+            {warning}
+          </FlagMessage>
+        )}
+      </PageContentFontWrapper>
+    </LargeCenteredLayout>
   );
 }
 
index 890451abf2d27166e5e6b95adb9b83e23470ddb3..bff4b922f507b09f9859dcea6436a07cae394fc3 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
 import * as React from 'react';
 import withComponentContext from '../../../app/components/componentContext/withComponentContext';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
@@ -41,13 +42,15 @@ export function TutorialsApp(props: TutorialsAppProps) {
   }
 
   return (
-    <div className="page page-limited">
-      <TutorialSelection
-        component={component}
-        currentUser={currentUser}
-        projectBinding={projectBinding}
-      />
-    </div>
+    <LargeCenteredLayout className="sw-pt-8">
+      <PageContentFontWrapper>
+        <TutorialSelection
+          component={component}
+          currentUser={currentUser}
+          projectBinding={projectBinding}
+        />
+      </PageContentFontWrapper>
+    </LargeCenteredLayout>
   );
 }
 
index 8824df09ed11bfb61d75204ae8ef5ca18bf41343..511fadce2f05cd75488145d1289cfcee6a3b4963 100644 (file)
@@ -128,7 +128,7 @@ it('should correctly fetch the corresponding ALM setting', async () => {
     {
       projectBinding: mockProjectAlmBindingResponse({ alm: AlmKeys.GitHub, key: 'binding' }),
     },
-    `dashboard?selectedTutorial=${TutorialModes.Jenkins}&id=bar`
+    `tutorials?selectedTutorial=${TutorialModes.Jenkins}&id=bar`
   );
   await waitOnDataLoaded();
 
@@ -190,10 +190,10 @@ async function startLocalTutorial(user: UserEvent) {
 
 function renderTutorialSelection(
   props: Partial<ComponentPropsType<typeof TutorialSelection>> = {},
-  navigateTo: string = 'dashboard?id=bar'
+  navigateTo: string = 'tutorials?id=bar'
 ) {
   return renderApp(
-    '/dashboard',
+    '/tutorials',
     <TutorialSelection
       component={mockComponent({ key: 'foo' })}
       currentUser={mockLoggedInUser({ permissions: { global: [Permissions.Scan] } })}
index 3c319b2881ab146e4c367a012e9276d6d320f7db..93060a015ba6fc0775ad94960a42b907e48f237e 100644 (file)
@@ -326,6 +326,7 @@ export function getProjectTutorialLocation(
   selectedTutorial?: string
 ): Partial<Path> {
   return {
+    pathname: '/tutorials',
     search: queryToSearch({ id: project, selectedTutorial }),
   };
 }