Просмотр исходного кода

Merge branch 'master' into support/143856-bookmark-sidebar-rayout

satof3 2 лет назад
Родитель
Сommit
19a506e7f0

+ 9 - 9
.github/workflows/ci-app-prod.yml

@@ -48,13 +48,13 @@ concurrency:
 
 jobs:
 
-  # test-prod-node18:
-  #   uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
-  #   with:
-  #     node-version: 18.x
-  #     skip-cypress: true
-  #   secrets:
-  #     SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+  test-prod-node18:
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+    with:
+      node-version: 18.x
+      skip-cypress: true
+    secrets:
+      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
   test-prod-node20:
@@ -62,7 +62,7 @@ jobs:
     with:
       node-version: 20.x
       skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
-      cypress-report-artifact-name: Cypress report
+      cypress-report-artifact-name-prefix: cypress-report-
       cypress-config-video: ${{ inputs.cypress-config-video || false }}
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
@@ -78,7 +78,7 @@ jobs:
     with:
       node-version: 20.x
       skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
-      cypress-report-artifact-name: Cypress report
+      cypress-report-artifact-name-pattern: cypress-report-*
     secrets:
       REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
       AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}

+ 3 - 4
.github/workflows/reusable-app-prod.yml

@@ -8,7 +8,7 @@ on:
         type: string
       skip-cypress:
         type: boolean
-      cypress-report-artifact-name:
+      cypress-report-artifact-name-prefix:
         type: string
       cypress-config-video:
         type: boolean
@@ -214,8 +214,7 @@ jobs:
       fail-fast: false
       matrix:
         # List string expressions that is comma separated ids of tests in "test/cypress/integration"
-        # spec-group: ['10', '20', '21', '22', '23', '30', '40', '50', '60']
-        spec-group: ['50']
+        spec-group: ['10', '20', '21', '22', '23', '30', '40', '50', '60']
 
     services:
       mongodb:
@@ -326,7 +325,7 @@ jobs:
       if: always()
       uses: actions/upload-artifact@v4
       with:
-        name: ${{ inputs.cypress-report-artifact-name }}
+        name: ${{ inputs.cypress-report-artifact-name-prefix }}${{ matrix.spec-group }}
         path: |
           apps/app/test/cypress/screenshots
           apps/app/test/cypress/videos

+ 3 - 2
.github/workflows/reusable-app-reg-suit.yml

@@ -11,7 +11,7 @@ on:
         default: ${{ github.head_ref }}
       skip-reg-suit:
         type: boolean
-      cypress-report-artifact-name:
+      cypress-report-artifact-name-pattern:
         required: true
         type: string
     secrets:
@@ -88,8 +88,9 @@ jobs:
     - name: Download screenshots taken by cypress
       uses: actions/download-artifact@v4
       with:
-        name: ${{ inputs.cypress-report-artifact-name }}
         path: apps/app/test/cypress
+        pattern: ${{ inputs.cypress-report-artifact-name-pattern }}
+        merge-multiple: true
 
     - name: Run reg-suit
       working-directory: ./apps/app

+ 2 - 0
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -66,6 +66,7 @@ declare global {
 }
 
 export type SaveOptions = {
+  wip: boolean,
   slackChannels: string,
   overwriteScopesOfDescendants?: boolean
 }
@@ -178,6 +179,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
       const { page } = await updatePage({
         pageId,
         revisionId,
+        wip: opts?.wip,
         body: markdown ?? '',
         grant: grantData?.grant,
         origin: Origin.Editor,

+ 6 - 0
apps/app/src/components/PageTags/TagsInput.tsx

@@ -42,6 +42,12 @@ export const TagsInput: FC<Props> = (props: Props) => {
     if (event.key === ' ') {
       event.preventDefault();
 
+      // fix: https://redmine.weseek.co.jp/issues/140689
+      const isComposing = event.nativeEvent.isComposing;
+      if (isComposing) {
+        return;
+      }
+
       const initialItem = tagsInputRef?.current?.state?.initialItem;
       const handleMenuItemSelect = tagsInputRef?.current?._handleMenuItemSelect;
 

+ 10 - 33
apps/app/src/components/SavePageControls.tsx

@@ -10,25 +10,20 @@ import {
   DropdownToggle, DropdownMenu, DropdownItem, Modal,
 } from 'reactstrap';
 
-import { toastSuccess, toastError } from '~/client/util/toastr';
 import type { IPageGrantData } from '~/interfaces/page';
 import {
   useIsEditable, useIsAclEnabled,
   useIsSlackConfigured,
 } from '~/stores/context';
 import { useWaitingSaveProcessing, useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
-import { useSWRMUTxCurrentPage, useSWRxCurrentPage, useCurrentPagePath } from '~/stores/page';
-import { mutatePageTree } from '~/stores/page-listing';
+import { useSWRxCurrentPage, useCurrentPagePath } from '~/stores/page';
 import {
   useSelectedGrant,
   useEditorMode, useIsDeviceLargerThanMd,
-  EditorMode,
+
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
-
-import { unpublish } from '../client/services/page-operation';
-
 import { GrantSelector } from './SavePageControls/GrantSelector';
 import { SlackNotification } from './SlackNotification';
 
@@ -45,10 +40,7 @@ const logger = loggerFactory('growi:SavePageControls');
 const SavePageButton = (props: {slackChannels: string, isDeviceLargerThanMd?: boolean}) => {
 
   const { t } = useTranslation();
-  const { data: currentPage } = useSWRxCurrentPage();
   const { data: _isWaitingSaveProcessing } = useWaitingSaveProcessing();
-  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
-  const { mutate: mutateEditorMode } = useEditorMode();
   const [isSavePageModalShown, setIsSavePageModalShown] = useState<boolean>(false);
 
   const { slackChannels, isDeviceLargerThanMd } = props;
@@ -57,33 +49,18 @@ const SavePageButton = (props: {slackChannels: string, isDeviceLargerThanMd?: bo
 
   const save = useCallback(async(): Promise<void> => {
     // save
-    globalEmitter.emit('saveAndReturnToView', { slackChannels });
+    globalEmitter.emit('saveAndReturnToView', { wip: false, slackChannels });
   }, [slackChannels]);
 
   const saveAndOverwriteScopesOfDescendants = useCallback(() => {
     // save
-    globalEmitter.emit('saveAndReturnToView', { overwriteScopesOfDescendants: true, slackChannels });
+    globalEmitter.emit('saveAndReturnToView', { wip: false, overwriteScopesOfDescendants: true, slackChannels });
   }, [slackChannels]);
 
-  const clickUnpublishButtonHandler = useCallback(async() => {
-    const pageId = currentPage?._id;
-
-    if (pageId == null) {
-      return;
-    }
-
-    try {
-      await unpublish(pageId);
-      await mutateCurrentPage();
-      await mutatePageTree();
-      await mutateEditorMode(EditorMode.View);
-      toastSuccess(t('wip_page.success_save_as_wip'));
-    }
-    catch (err) {
-      logger.error(err);
-      toastError(t('wip_page.fail_save_as_wip'));
-    }
-  }, [currentPage?._id, mutateCurrentPage, mutateEditorMode, t]);
+  const saveAndMakeWip = useCallback(() => {
+    // save
+    globalEmitter.emit('saveAndReturnToView', { wip: true, slackChannels });
+  }, [slackChannels]);
 
   const labelSubmitButton = t('Update');
   const labelOverwriteScopes = t('page_edit.overwrite_scopes', { operation: labelSubmitButton });
@@ -113,7 +90,7 @@ const SavePageButton = (props: {slackChannels: string, isDeviceLargerThanMd?: bo
                 <DropdownItem onClick={saveAndOverwriteScopesOfDescendants}>
                   {labelOverwriteScopes}
                 </DropdownItem>
-                <DropdownItem onClick={clickUnpublishButtonHandler}>
+                <DropdownItem onClick={saveAndMakeWip}>
                   {labelUnpublishPage}
                 </DropdownItem>
               </DropdownMenu>
@@ -130,7 +107,7 @@ const SavePageButton = (props: {slackChannels: string, isDeviceLargerThanMd?: bo
                   <button type="button" className="btn btn-primary" onClick={() => { setIsSavePageModalShown(false); saveAndOverwriteScopesOfDescendants() }}>
                     {labelOverwriteScopes}
                   </button>
-                  <button type="button" className="btn btn-primary" onClick={() => { setIsSavePageModalShown(false); clickUnpublishButtonHandler() }}>
+                  <button type="button" className="btn btn-primary" onClick={() => { setIsSavePageModalShown(false); saveAndMakeWip() }}>
                     {labelUnpublishPage}
                   </button>
                   <button type="button" className="btn btn-outline-neutral-secondary mx-auto mt-1" onClick={() => setIsSavePageModalShown(false)}>

+ 1 - 0
apps/app/src/interfaces/apiv3/page.ts

@@ -32,6 +32,7 @@ export type IApiv3PageUpdateParams = IOptionsForUpdate & {
   origin?: Origin,
   isSlackEnabled?: boolean,
   slackChannels?: string,
+  wip?: boolean
 };
 
 export type IApiv3PageUpdateResponse = {

+ 1 - 0
apps/app/src/interfaces/page.ts

@@ -34,6 +34,7 @@ export type IDeleteManyPageApiv3Result = {
 
 export type IOptionsForUpdate = {
   origin?: Origin
+  wip?: boolean,
   grant?: PageGrant,
   userRelatedGrantUserGroupIds?: IGrantedGroup[],
   // isSyncRevisionToHackmd?: boolean,

+ 5 - 2
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -76,6 +76,7 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
     body('isSlackEnabled').optional().isBoolean().withMessage('isSlackEnabled must be boolean'),
     body('slackChannels').optional().isString().withMessage('slackChannels must be string'),
     body('origin').optional().isIn(allOrigin).withMessage('origin must be "view" or "editor"'),
+    body('wip').optional().isBoolean().withMessage('wip must be boolean'),
   ];
 
 
@@ -158,8 +159,10 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
 
       let updatedPage;
       try {
-        const { grant, userRelatedGrantUserGroupIds, overwriteScopesOfDescendants } = req.body;
-        const options: IOptionsForUpdate = { overwriteScopesOfDescendants, origin };
+        const {
+          grant, userRelatedGrantUserGroupIds, overwriteScopesOfDescendants, wip,
+        } = req.body;
+        const options: IOptionsForUpdate = { overwriteScopesOfDescendants, origin, wip };
         if (grant != null) {
           options.grant = grant;
           options.userRelatedGrantUserGroupIds = userRelatedGrantUserGroupIds;

+ 10 - 3
apps/app/src/server/service/page/index.ts

@@ -4146,9 +4146,16 @@ class PageService implements IPageService {
     const clonedPageData = Page.hydrate(pageData.toObject());
     const newPageData = pageData;
 
-    // If updated at least once, publish
-    pageData.publish();
-
+    // Once updated it's exempt from automatic deletion
+    if (options.wip == null) {
+      newPageData.ttlTimestamp = undefined;
+    }
+    else if (options.wip) {
+      newPageData.unpublish();
+    }
+    else {
+      newPageData.publish();
+    }
 
     // use the previous data if absent
     const grant = options.grant ?? clonedPageData.grant;

+ 11 - 11
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--use-tools.cy.ts

@@ -10,7 +10,7 @@ context('Modal for page operation', () => {
   });
 
   it('Page Deletion and PutBack is executed successfully', { scrollBehavior: false }, () => {
-    cy.visit('/Sandbox/Bootstrap4');
+    cy.visit('/Sandbox/Bootstrap5');
 
     cy.waitUntil(() => {
       // do
@@ -30,7 +30,7 @@ context('Modal for page operation', () => {
 
     cy.getByTestid('trash-page-alert').should('be.visible');
     cy.collapseSidebar(true);
-    cy.screenshot(`${ssPrefix}-bootstrap4-is-in-garbage-box`);
+    cy.screenshot(`${ssPrefix}-bootstrap5-is-in-garbage-box`);
 
     cy.getByTestid('put-back-button').click();
     cy.getByTestid('put-back-page-modal').should('be.visible').within(() => {
@@ -39,11 +39,11 @@ context('Modal for page operation', () => {
     });
 
     cy.collapseSidebar(true);
-    cy.screenshot(`${ssPrefix}-put-backed-bootstrap4-page`);
+    cy.screenshot(`${ssPrefix}-put-backed-bootstrap5-page`);
   });
 
   it('PageDuplicateModal is shown successfully', () => {
-    cy.visit('/Sandbox/Bootstrap4');
+    cy.visit('/Sandbox/5');
     cy.waitUntilSkeletonDisappear();
 
     cy.waitUntil(() => {
@@ -57,11 +57,11 @@ context('Modal for page operation', () => {
 
     cy.getByTestid('open-page-duplicate-modal-btn').filter(':visible').click({force: true});
 
-    cy.getByTestid('page-duplicate-modal').should('be.visible').screenshot(`${ssPrefix}-duplicate-bootstrap4`);
+    cy.getByTestid('page-duplicate-modal').should('be.visible').screenshot(`${ssPrefix}-duplicate-bootstrap5`);
   });
 
   it('PageMoveRenameModal is shown successfully', () => {
-    cy.visit('/Sandbox/Bootstrap4');
+    cy.visit('/Sandbox/Bootstrap5');
     cy.waitUntilSkeletonDisappear();
 
     cy.waitUntil(() => {
@@ -76,7 +76,7 @@ context('Modal for page operation', () => {
     cy.getByTestid('open-page-move-rename-modal-btn').filter(':visible').click({force: true});
     cy.getByTestid('grw-page-rename-button').should('be.disabled');
 
-    cy.getByTestid('page-rename-modal').should('be.visible').screenshot(`${ssPrefix}-rename-bootstrap4`);
+    cy.getByTestid('page-rename-modal').should('be.visible').screenshot(`${ssPrefix}-rename-bootstrap5`);
   });
 
 });
@@ -144,7 +144,7 @@ context('Page Accessories Modal', () => {
     cy.getByTestid('page-history').should('be.visible');
 
     cy.waitUntilSpinnerDisappear();
-    cy.screenshot(`${ssPrefix}-open-page-history-bootstrap4`);
+    cy.screenshot(`${ssPrefix}-open-page-history-bootstrap5`);
   });
 
   it('Page Attachment Data is shown successfully', () => {
@@ -155,7 +155,7 @@ context('Page Accessories Modal', () => {
     cy.waitUntilSpinnerDisappear();
     cy.getByTestid('page-attachment').should('be.visible').contains('No attachments yet.');
 
-    cy.screenshot(`${ssPrefix}-open-page-attachment-data-bootstrap4`);
+    cy.screenshot(`${ssPrefix}-open-page-attachment-data-bootstrap5`);
   });
 
   it('Share Link Management is shown successfully', () => {
@@ -167,7 +167,7 @@ context('Page Accessories Modal', () => {
     cy.getByTestid('page-accessories-modal').should('be.visible');
     cy.getByTestid('share-link-management').should('be.visible');
 
-    cy.screenshot(`${ssPrefix}-open-share-link-management-bootstrap4`);
+    cy.screenshot(`${ssPrefix}-open-share-link-management-bootstrap5`);
   });
 });
 
@@ -184,7 +184,7 @@ context('Tag Oprations', { scrollBehavior: false }, () =>{
     const ssPrefix = 'tag-operations-add-new-tag-'
     const tag = 'we';
 
-    cy.visit('/Sandbox/Bootstrap4');
+    cy.visit('/Sandbox/Bootstrap5');
     cy.collapseSidebar(true);
 
     // Add tag

+ 1 - 1
apps/app/test/cypress/e2e/22-sharelink/22-sharelink--access-to-sharelink.cy.ts

@@ -9,7 +9,7 @@ context('Access to sharelink by guest', () => {
       cy.login(user.username, user.password);
     });
 
-    cy.visit('/Sandbox/Bootstrap4');
+    cy.visit('/Sandbox/Bootstrap5');
 
     // open dropdown
     cy.waitUntil(() => {