Card order by date modified (#3170)

Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
Co-authored-by: Opender Singh <opender.singh@konghq.com>
This commit is contained in:
Mike Ellan 2021-03-09 23:33:38 -05:00 committed by GitHub
parent f1539e2b40
commit 84b9b84fdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 98 additions and 21 deletions

View File

@ -1,6 +1,6 @@
import * as misc from '../misc';
import { globalBeforeEach } from '../../__jest__/before-each';
import { diffPatchObj, pluralize, snapNumberToLimits } from '../misc';
import { diffPatchObj, isNotNullOrUndefined, pluralize, snapNumberToLimits } from '../misc';
describe('hasAuthHeader()', () => {
beforeEach(globalBeforeEach);
@ -271,3 +271,14 @@ describe('snapNumberToLimits()', () => {
expect(snapNumberToLimits(5, NaN, 3)).toBe(3);
});
});
describe('isNotNullOrUndefined', () => {
it('should return correctly', () => {
expect(isNotNullOrUndefined(0)).toBe(true);
expect(isNotNullOrUndefined('')).toBe(true);
expect(isNotNullOrUndefined(false)).toBe(true);
expect(isNotNullOrUndefined(null)).toBe(false);
expect(isNotNullOrUndefined(undefined)).toBe(false);
});
});

View File

@ -1,4 +1,9 @@
import { metaSortKeySort, sortMethodMap } from '../sorting';
import {
ascendingNumberSort,
descendingNumberSort,
metaSortKeySort,
sortMethodMap,
} from '../sorting';
import { request, requestGroup, grpcRequest } from '../../models';
import {
METHOD_DELETE,
@ -340,4 +345,16 @@ describe('Sorting methods', () => {
expect(metaSortKeySort({ metaSortKey: 1, _id: 2 }, { metaSortKey: 1, _id: 1 })).toBe(-1);
expect(metaSortKeySort({ metaSortKey: 1, _id: 1 }, { metaSortKey: 1, _id: 2 })).toBe(1);
});
it('sorts by number', () => {
expect(ascendingNumberSort(1, 2)).toBe(-1);
expect(ascendingNumberSort(-2, 1)).toBe(-1);
expect(ascendingNumberSort(2, 1)).toBe(1);
expect(ascendingNumberSort(1, -2)).toBe(1);
expect(descendingNumberSort(1, 2)).toBe(1);
expect(descendingNumberSort(-2, 1)).toBe(1);
expect(descendingNumberSort(2, 1)).toBe(-1);
expect(descendingNumberSort(1, -2)).toBe(-1);
});
});

View File

@ -453,3 +453,7 @@ export function snapNumberToLimits(value: number, min?: number, max?: number): n
return value;
}
export function isNotNullOrUndefined(obj: any): boolean {
return obj !== null && obj !== undefined;
}

View File

@ -97,6 +97,14 @@ export const metaSortKeySort: SortFunction = (a, b) => {
return a.metaSortKey < b.metaSortKey ? -1 : 1;
};
export const ascendingNumberSort = (a: number, b: number): number => {
return a < b ? -1 : 1;
};
export const descendingNumberSort = (a: number, b: number): number => {
return ascendingNumberSort(b, a);
};
export const sortMethodMap: { [SortOrder]: SortFunction } = {
[SORT_NAME_ASC]: ascendingNameSort,
[SORT_NAME_DESC]: descendingNameSort,

View File

@ -19,6 +19,7 @@ import HelpTooltip from '../help-tooltip';
import Link from '../base/link';
import { trackEvent } from '../../../common/analytics';
import { docsGitSync } from '../../../common/documentation';
import { isNotNullOrUndefined } from '../../../common/misc';
type Props = {|
handleInitializeEntities: () => Promise<void>,
@ -68,11 +69,20 @@ class GitSyncDropdown extends React.PureComponent<Props, State> {
// Clear cached items and return if no state
if (!vcs.isInitialized() || !workspaceMeta.gitRepositoryId) {
await models.workspaceMeta.updateByParentId(workspace._id, {
cachedGitRepositoryBranch: null,
cachedGitLastAuthor: null,
cachedGitLastCommitTime: null,
});
// Don't update unnecessarily
const needsUpdate = [
workspaceMeta.cachedGitRepositoryBranch,
workspaceMeta.cachedGitLastAuthor,
workspaceMeta.cachedGitLastCommitTime,
].some(isNotNullOrUndefined);
if (needsUpdate) {
await models.workspaceMeta.updateByParentId(workspace._id, {
cachedGitRepositoryBranch: null,
cachedGitLastAuthor: null,
cachedGitLastCommitTime: null,
});
}
return;
}

View File

@ -36,7 +36,7 @@ import TimeFromNow from './time-from-now';
import Highlight from './base/highlight';
import type { GlobalActivity } from '../../common/constants';
import { fuzzyMatchAll, pluralize } from '../../common/misc';
import { fuzzyMatchAll, isNotNullOrUndefined, pluralize } from '../../common/misc';
import type {
HandleImportClipboardCallback,
HandleImportFileCallback,
@ -60,6 +60,8 @@ import RemoteWorkspacesDropdown from './dropdowns/remote-workspaces-dropdown';
import SettingsButton from './buttons/settings-button';
import AccountDropdown from './dropdowns/account-dropdown';
import { strings } from '../../common/strings';
import { WorkspaceScopeKeys } from '../../models/workspace';
import { descendingNumberSort } from '../../common/sorting';
type Props = {|
wrapperProps: WrapperProps,
@ -316,7 +318,6 @@ class WrapperHome extends React.PureComponent<Props, State> {
const { activeActivity } = await models.workspaceMeta.getOrCreateByParentId(id);
if (!activeActivity || !isWorkspaceActivity(activeActivity)) {
// or migration or onboarding
handleSetActiveActivity(defaultActivity);
} else {
handleSetActiveActivity(activeActivity);
@ -324,7 +325,7 @@ class WrapperHome extends React.PureComponent<Props, State> {
handleSetActiveWorkspace(id);
}
renderCard(w: Workspace) {
renderCard(workspace: Workspace): { card: React.Node, lastModifiedTimestamp: number } {
const {
apiSpecs,
handleSetActiveWorkspace,
@ -334,7 +335,7 @@ class WrapperHome extends React.PureComponent<Props, State> {
const { filter } = this.state;
const apiSpec = apiSpecs.find(s => s.parentId === w._id);
const apiSpec = apiSpecs.find(s => s.parentId === workspace._id);
let spec = null;
let specFormat = null;
@ -350,18 +351,25 @@ class WrapperHome extends React.PureComponent<Props, State> {
}
// Get cached branch from WorkspaceMeta
const workspaceMeta = workspaceMetas.find(wm => wm.parentId === w._id);
const workspaceMeta = workspaceMetas.find(wm => wm.parentId === workspace._id);
const lastActiveBranch = workspaceMeta ? workspaceMeta.cachedGitRepositoryBranch : null;
const lastCommitAuthor = workspaceMeta ? workspaceMeta.cachedGitLastAuthor : null;
const lastCommitTime = workspaceMeta ? workspaceMeta.cachedGitLastCommitTime : null;
// WorkspaceMeta is a good proxy for last modified time
const workspaceModified = workspaceMeta ? workspaceMeta.modified : w.modified;
const modifiedLocally = apiSpec ? apiSpec.modified : workspaceModified;
const workspaceModified = workspaceMeta ? workspaceMeta.modified : workspace.modified;
const modifiedLocally =
apiSpec && workspace.scope === WorkspaceScopeKeys.designer
? apiSpec.modified
: workspaceModified;
let log = <TimeFromNow timestamp={modifiedLocally} />;
let branch = lastActiveBranch;
if (w.scope === 'designer' && lastCommitTime && apiSpec?.modified > lastCommitTime) {
if (
workspace.scope === WorkspaceScopeKeys.designer &&
lastCommitTime &&
apiSpec?.modified > lastCommitTime
) {
// Show locally unsaved changes for spec
// NOTE: this doesn't work for non-spec workspaces
branch = lastActiveBranch + '*';
@ -383,7 +391,7 @@ class WrapperHome extends React.PureComponent<Props, State> {
const docMenu = (
<DocumentCardDropdown
apiSpec={apiSpec}
workspace={w}
workspace={workspace}
handleSetActiveWorkspace={handleSetActiveWorkspace}
isLastWorkspace={workspaces.length === 1}>
<SvgIcon icon="ellipsis" />
@ -394,9 +402,9 @@ class WrapperHome extends React.PureComponent<Props, State> {
let format: string = '';
let labelIcon = <i className="fa fa-bars" />;
let defaultActivity = ACTIVITY_DEBUG;
let title = w.name;
let title = workspace.name;
if (w.scope === 'designer') {
if (workspace.scope === WorkspaceScopeKeys.designer) {
label = 'Document';
labelIcon = <i className="fa fa-file-o" />;
if (specFormat === 'openapi') {
@ -421,7 +429,17 @@ class WrapperHome extends React.PureComponent<Props, State> {
return null;
}
return (
const lastModifiedFrom = [
workspace?.modified,
workspaceMeta?.modified,
apiSpec?.modified,
workspaceMeta?.cachedGitLastCommitTime,
];
const lastModifiedTimestamp = lastModifiedFrom
.filter(isNotNullOrUndefined)
.sort(descendingNumberSort)[0];
const card = (
<Card
key={apiSpec._id}
docBranch={branch && <Highlight search={filter} text={branch} />}
@ -438,9 +456,14 @@ class WrapperHome extends React.PureComponent<Props, State> {
docLog={log}
docMenu={docMenu}
docFormat={format}
onClick={() => this._handleClickCard(w._id, defaultActivity)}
onClick={() => this._handleClickCard(workspace._id, defaultActivity)}
/>
);
return {
card,
lastModifiedTimestamp,
};
}
renderCreateMenu() {
@ -508,7 +531,11 @@ class WrapperHome extends React.PureComponent<Props, State> {
const { filter } = this.state;
// Render each card, removing all the ones that don't match the filter
const cards = workspaces.map(this.renderCard).filter(c => c !== null);
const cards = workspaces
.map(this.renderCard)
.filter(isNotNullOrUndefined)
.sort((a, b) => descendingNumberSort(a.lastModifiedTimestamp, b.lastModifiedTimestamp))
.map(c => c.card);
const countLabel = cards.length > 1 ? pluralize(strings.document) : strings.document;