From 4f75dc079141f010d494c98a3db7b2fdba9fcd77 Mon Sep 17 00:00:00 2001 From: SmitGala Date: Thu, 4 Jan 2024 11:46:29 +0530 Subject: [PATCH] project page ui revamp --- .../ak-select/before-option/index.hbs | 25 ++ .../ak-select/before-option/index.scss | 23 ++ .../ak-select/before-option/index.ts | 61 ++++ app/components/ak-select/index.hbs | 10 +- app/components/ak-select/index.scss | 9 + app/components/ak-select/index.ts | 12 + app/components/ak-svg/project-list-empty.hbs | 54 +++ app/components/ak-text-field/index.hbs | 1 + .../file-compare/compare-list/index.hbs | 2 +- .../file-overview/file-details/index.hbs | 149 -------- .../file-overview/file-details/index.ts | 26 -- .../file-overview/header/index.hbs | 77 ---- .../file-overview/header/index.scss | 34 -- .../file-overview/header/index.ts | 36 -- .../file-compare/file-overview/index.hbs | 37 -- .../file-compare/file-overview/index.scss | 5 - .../file-overview/scan-statuses/index.scss | 6 - app/components/file-compare/header/index.hbs | 4 +- app/components/file-list/index.hbs | 2 +- .../file-overview/file-details/index.hbs | 132 +++++++ .../file-overview/file-details/index.scss | 15 +- .../file-overview/file-details/index.ts | 21 ++ app/components/file-overview/header/index.hbs | 119 ++++++ .../file-overview/header/index.scss | 47 +++ app/components/file-overview/header/index.ts | 98 +++++ app/components/file-overview/index.hbs | 344 ++---------------- app/components/file-overview/index.js | 40 -- app/components/file-overview/index.scss | 54 +-- .../{file-compare => }/file-overview/index.ts | 9 +- .../file-overview/scan-statuses/index.hbs | 41 ++- .../file-overview/scan-statuses/index.scss | 14 + .../file-overview/scan-statuses/index.ts | 8 +- app/components/file-overview/tags/index.hbs | 36 ++ app/components/file-overview/tags/index.scss | 13 + app/components/file-overview/tags/index.ts | 17 + .../filter-selected-item/index.hbs | 17 + .../filter-selected-item/index.scss | 3 + .../filter-selected-item/index.ts | 29 ++ app/components/project-list/header/index.hbs | 183 ++++++++++ app/components/project-list/header/index.scss | 88 +++++ app/components/project-list/header/index.ts | 241 ++++++++++++ app/components/project-list/index.hbs | 182 ++++----- app/components/project-list/index.scss | 48 +-- .../project-list/{index.js => index.ts} | 170 +++------ app/components/project-list/loader/index.hbs | 19 + app/components/project-list/loader/index.scss | 15 + app/components/project-list/loader/index.ts | 16 + app/components/project-overview/index.hbs | 43 +-- app/components/project-overview/index.js | 7 - app/components/project-overview/index.scss | 3 + app/components/project-overview/index.ts | 22 ++ app/services/project.ts | 2 +- app/styles/_component-variables.scss | 56 ++- app/styles/_icons.scss | 12 + tests/acceptance/file-compare-test.js | 8 +- .../file-compare/compare-list-test.js | 4 +- .../integration/components/file-list-test.js | 33 +- .../{file-compare => }/file-overview-test.js | 154 ++++---- .../file-overview/component-test.js | 238 ------------ .../components/project-list-test.js | 165 +++++++-- translations/en.json | 9 +- translations/ja.json | 9 +- types/ak-svg.d.ts | 1 + 63 files changed, 1838 insertions(+), 1520 deletions(-) create mode 100644 app/components/ak-select/before-option/index.hbs create mode 100644 app/components/ak-select/before-option/index.scss create mode 100644 app/components/ak-select/before-option/index.ts create mode 100644 app/components/ak-svg/project-list-empty.hbs delete mode 100644 app/components/file-compare/file-overview/file-details/index.hbs delete mode 100644 app/components/file-compare/file-overview/file-details/index.ts delete mode 100644 app/components/file-compare/file-overview/header/index.hbs delete mode 100644 app/components/file-compare/file-overview/header/index.scss delete mode 100644 app/components/file-compare/file-overview/header/index.ts delete mode 100644 app/components/file-compare/file-overview/index.hbs delete mode 100644 app/components/file-compare/file-overview/index.scss delete mode 100644 app/components/file-compare/file-overview/scan-statuses/index.scss create mode 100644 app/components/file-overview/file-details/index.hbs rename app/components/{file-compare => }/file-overview/file-details/index.scss (65%) create mode 100644 app/components/file-overview/file-details/index.ts create mode 100644 app/components/file-overview/header/index.hbs create mode 100644 app/components/file-overview/header/index.scss create mode 100644 app/components/file-overview/header/index.ts delete mode 100644 app/components/file-overview/index.js rename app/components/{file-compare => }/file-overview/index.ts (74%) rename app/components/{file-compare => }/file-overview/scan-statuses/index.hbs (51%) create mode 100644 app/components/file-overview/scan-statuses/index.scss rename app/components/{file-compare => }/file-overview/scan-statuses/index.ts (70%) create mode 100644 app/components/file-overview/tags/index.hbs create mode 100644 app/components/file-overview/tags/index.scss create mode 100644 app/components/file-overview/tags/index.ts create mode 100644 app/components/project-list/filter-selected-item/index.hbs create mode 100644 app/components/project-list/filter-selected-item/index.scss create mode 100644 app/components/project-list/filter-selected-item/index.ts create mode 100644 app/components/project-list/header/index.hbs create mode 100644 app/components/project-list/header/index.scss create mode 100644 app/components/project-list/header/index.ts rename app/components/project-list/{index.js => index.ts} (50%) create mode 100644 app/components/project-list/loader/index.hbs create mode 100644 app/components/project-list/loader/index.scss create mode 100644 app/components/project-list/loader/index.ts delete mode 100644 app/components/project-overview/index.js create mode 100644 app/components/project-overview/index.scss create mode 100644 app/components/project-overview/index.ts rename tests/integration/components/{file-compare => }/file-overview-test.js (67%) delete mode 100644 tests/integration/components/file-overview/component-test.js diff --git a/app/components/ak-select/before-option/index.hbs b/app/components/ak-select/before-option/index.hbs new file mode 100644 index 000000000..f4c787e96 --- /dev/null +++ b/app/components/ak-select/before-option/index.hbs @@ -0,0 +1,25 @@ +{{#if this.optionTitle}} + + {{this.optionTitle}} + +{{/if}} + +{{#if @searchEnabled}} +
+ + <:leftAdornment> + + + +
+{{/if}} \ No newline at end of file diff --git a/app/components/ak-select/before-option/index.scss b/app/components/ak-select/before-option/index.scss new file mode 100644 index 000000000..1a7d55937 --- /dev/null +++ b/app/components/ak-select/before-option/index.scss @@ -0,0 +1,23 @@ +.before-option-label { + height: 35px; + display: flex; + align-items: center; + padding-left: 1em !important; + border-bottom: 1px solid var(--ak-select-before-option-border-color); +} + +.before-option-search { + border-bottom: 1px solid var(--ak-select-before-option-border-color); + :global(.ak-text-field-left-adornment) { + margin-left: 0.8em; + } + + :global(.ak-text-input-outlined) { + border: 0; + } +} + +.team-name-text-input { + height: 35px !important; + font-size: 0.857rem !important; +} diff --git a/app/components/ak-select/before-option/index.ts b/app/components/ak-select/before-option/index.ts new file mode 100644 index 000000000..2c419edd0 --- /dev/null +++ b/app/components/ak-select/before-option/index.ts @@ -0,0 +1,61 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import { Select } from 'ember-power-select/components/power-select'; +import { later, scheduleOnce } from '@ember/runloop'; +import styles from './index.scss'; + +interface AkSelectBeforeOptionArgs { + select: Select; + searchPlaceholder?: string; + searchEnabled?: boolean; + extra?: Record; + autofocus?: boolean; + onBlur: (e: FocusEvent) => void; + onFocus: (e: FocusEvent) => void; + onInput: (e: InputEvent) => boolean; + onKeydown: (e: KeyboardEvent) => false | void; +} + +export default class AkSelectBeforeOptionComponent extends Component { + get optionTitle() { + return this.args.extra?.['optionTitle']; + } + + @action + clearSearch(): void { + scheduleOnce('actions', this.args.select.actions, 'search', ''); + } + + @action + focusInput(el: HTMLElement) { + later(() => { + if (this.args.autofocus !== false) { + el.focus(); + } + }, 0); + } + + @action + handleKeydown(e: KeyboardEvent): false | void { + if (this.args.onKeydown(e) === false) { + return false; + } + if (e.keyCode === 13) { + this.args.select.actions.close(e); + } + } + + @action + handleInput(event: Event): false | void { + const e = event as InputEvent; + if (this.args.onInput(e) === false) { + return false; + } + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'AkSelect::BeforeOption': typeof AkSelectBeforeOptionComponent; + } +} diff --git a/app/components/ak-select/index.hbs b/app/components/ak-select/index.hbs index d3921696e..380d56e4d 100644 --- a/app/components/ak-select/index.hbs +++ b/app/components/ak-select/index.hbs @@ -20,7 +20,7 @@ > diff --git a/app/components/ak-select/index.scss b/app/components/ak-select/index.scss index 5ae390fef..b8c76cdea 100644 --- a/app/components/ak-select/index.scss +++ b/app/components/ak-select/index.scss @@ -41,6 +41,15 @@ border: 1px solid var(--ak-select-dropdown-border-color) !important; z-index: var(--ak-select-dropdown-zIndex) !important; + :global(.ember-power-select-search) { + border-bottom: 1px solid var(--ak-select-option-divider-color); + + :global(.ember-power-select-search-input) { + padding: 0; + border: 0; + } + } + :global(.ember-power-select-options) { padding: 0.5em 0; diff --git a/app/components/ak-select/index.ts b/app/components/ak-select/index.ts index 7abcd9b09..2e0de3612 100644 --- a/app/components/ak-select/index.ts +++ b/app/components/ak-select/index.ts @@ -17,6 +17,7 @@ interface AkSelectNamedArgs extends PowerSelectArgs { label?: string; helperText?: string; triggerClass?: string; + dropdownClass?: string; placeholder?: string; renderInPlace?: boolean; error?: string; @@ -26,6 +27,13 @@ interface AkSelectNamedArgs extends PowerSelectArgs { labelTypographyColor?: AkSelectLabelTypographyColor; verticalPosition?: 'above' | 'below' | 'auto'; options: O[]; + searchPlaceholder?: string; + searchEnabled?: boolean; + extra?: Record; + optionTitle?: string; + onInput?: (term: string, select: Select, e: Event) => string | false | void; + onFocus?: (select: Select, event: FocusEvent) => void; + onBlur?: (select: Select, event: FocusEvent) => void; } export interface AkSelectSignature { @@ -43,6 +51,10 @@ export default class AkSelectComponent extends Component< return this.args.labelId || `ak-select-${guidFor(this)}`; } + get extra() { + return { ...(this.args.extra || {}), optionTitle: this.args.optionTitle }; + } + get classes() { return { dropdown: styles['ak-select-dropdown'], diff --git a/app/components/ak-svg/project-list-empty.hbs b/app/components/ak-svg/project-list-empty.hbs new file mode 100644 index 000000000..aeb5270e6 --- /dev/null +++ b/app/components/ak-svg/project-list-empty.hbs @@ -0,0 +1,54 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-text-field/index.hbs b/app/components/ak-text-field/index.hbs index fbb8ab8b7..42947e26d 100644 --- a/app/components/ak-text-field/index.hbs +++ b/app/components/ak-text-field/index.hbs @@ -22,6 +22,7 @@
{{yield to='leftAdornment'}}
diff --git a/app/components/file-compare/compare-list/index.hbs b/app/components/file-compare/compare-list/index.hbs index 2ebee4976..9d6c843c8 100644 --- a/app/components/file-compare/compare-list/index.hbs +++ b/app/components/file-compare/compare-list/index.hbs @@ -89,7 +89,7 @@ {{else if this.hasFiles}}
{{#each this.otherFilesInTheProject as |file|}} - - - {{#unless @file.isActive}} - - - - {{/unless}} - - - {{t 'fileID'}} - - - {{@file.id}} - - - - -
- -
- - {{! }} - - {{#if @file.submission.url}} - - - {{#if @file.submission.isIos}} - - {{/if}} - - {{#if @file.submission.isAndroid}} - - {{/if}} - - - {{/if}} -
- - - - {{#if @file.version}} - - - {{t 'version'}} - - - - - {{@file.version}} - - - - {{/if}} - - {{#if @file.versionCode}} - - - {{this.versionCode}} - - - - - {{@file.versionCode}} - - - - {{/if}} - -
- {{#if @file.tags.length}} - {{#each @file.tags as |tag|}} - - {{/each}} - {{else}} - - {{t 'fileCompare.noTagsMessage' htmlSafe=true}} - - {{/if}} -
-
\ No newline at end of file diff --git a/app/components/file-compare/file-overview/file-details/index.ts b/app/components/file-compare/file-overview/file-details/index.ts deleted file mode 100644 index ba71d4889..000000000 --- a/app/components/file-compare/file-overview/file-details/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { inject as service } from '@ember/service'; -import { capitalize } from '@ember/string'; -import Component from '@glimmer/component'; -import IntlService from 'ember-intl/services/intl'; -import FileModel from 'irene/models/file'; - -interface FileCompareFileOverviewFileDetailsSignature { - Element: HTMLElement; - Args: { - file: FileModel | null; - }; -} - -export default class FileCompareFileOverviewFileDetailsComponent extends Component { - @service declare intl: IntlService; - - get versionCode() { - return capitalize(this.intl.t('versionCode')); - } -} - -declare module '@glint/environment-ember-loose/registry' { - export default interface Registry { - 'FileCompare::FileOverview::FileDetails': typeof FileCompareFileOverviewFileDetailsComponent; - } -} diff --git a/app/components/file-compare/file-overview/header/index.hbs b/app/components/file-compare/file-overview/header/index.hbs deleted file mode 100644 index 07677d805..000000000 --- a/app/components/file-compare/file-overview/header/index.hbs +++ /dev/null @@ -1,77 +0,0 @@ -{{!@glint-nocheck }} - - - -
- -
- - - - {{@file.name}} - - - - {{this.packageName}} - - -
- - {{#unless @hideCTAs}} - - - - - - {{#unless @hideOpenInNewTabIcon}} - - - - - - {{/unless}} - - {{/unless}} -
\ No newline at end of file diff --git a/app/components/file-compare/file-overview/header/index.scss b/app/components/file-compare/file-overview/header/index.scss deleted file mode 100644 index b23a8aadd..000000000 --- a/app/components/file-compare/file-overview/header/index.scss +++ /dev/null @@ -1,34 +0,0 @@ -.file-overview-header { - padding: 0.9286em 1.1429em; - gap: 0.8714em; - background-color: var(--file-compare-file-overview-background-grey); - border-bottom: 1px solid var(--file-compare-file-overview-border-color-dark); - - .file-icon { - width: 38px; - height: 38px; - - img { - width: 100%; - height: 100%; - object-fit: contain; - background-image: none; - overflow: hidden; - } - } - - .open-in-new-tab-link-class { - display: inline-flex; - } - - .open-in-new-tab-link:hover::after { - content: ''; - position: absolute; - width: 32px; - height: 32px; - border-radius: 100%; - background-color: var( - --file-compare-file-overview-open-in-new-icon-background-color - ); - } -} diff --git a/app/components/file-compare/file-overview/header/index.ts b/app/components/file-compare/file-overview/header/index.ts deleted file mode 100644 index 376d3d498..000000000 --- a/app/components/file-compare/file-overview/header/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { action } from '@ember/object'; -import Component from '@glimmer/component'; -import FileModel from 'irene/models/file'; -import styles from './index.scss'; - -interface FileCompareFileOverviewHeaderSignature { - Args: { - file: FileModel | null; - isSelectedFile?: boolean; - disableSelection?: boolean; - onFileSelect?: (file: FileModel | null) => void; - hideCTAs?: boolean; - hideOpenInNewTabIcon?: boolean; - }; -} - -export default class FileCompareFileOverviewHeaderComponent extends Component { - get packageName() { - return this.args.file?.project.get('packageName'); - } - - @action handleFileSelect(event: Event) { - event.stopPropagation(); - this.args.onFileSelect?.(this.args.file); - } - - get openInNewTabLinkClass() { - return styles['open-in-new-tab-link-class']; - } -} - -declare module '@glint/environment-ember-loose/registry' { - export default interface Registry { - 'FileCompare::FileOverview::Header': typeof FileCompareFileOverviewHeaderComponent; - } -} diff --git a/app/components/file-compare/file-overview/index.hbs b/app/components/file-compare/file-overview/index.hbs deleted file mode 100644 index 0895966a7..000000000 --- a/app/components/file-compare/file-overview/index.hbs +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - {{yield}} - \ No newline at end of file diff --git a/app/components/file-compare/file-overview/index.scss b/app/components/file-compare/file-overview/index.scss deleted file mode 100644 index c72ce4f32..000000000 --- a/app/components/file-compare/file-overview/index.scss +++ /dev/null @@ -1,5 +0,0 @@ -.file-compare-file-overview { - border-radius: var(--file-compare-file-overview-border-radius); - border: 1px solid var(--file-compare-file-overview-border-color-dark); - background-color: var(--file-compare-file-overview-background-white); -} diff --git a/app/components/file-compare/file-overview/scan-statuses/index.scss b/app/components/file-compare/file-overview/scan-statuses/index.scss deleted file mode 100644 index 4939fecf7..000000000 --- a/app/components/file-compare/file-overview/scan-statuses/index.scss +++ /dev/null @@ -1,6 +0,0 @@ -.file-overview-statuses { - background: var(--file-compare-file-overview-background-grey); - border: 1px solid var(--file-compare-file-overview-border-color-dark); - border-left: none; - border-right: none; -} diff --git a/app/components/file-compare/header/index.hbs b/app/components/file-compare/header/index.hbs index ab8690336..503e36d39 100644 --- a/app/components/file-compare/header/index.hbs +++ b/app/components/file-compare/header/index.hbs @@ -139,7 +139,7 @@ {{#if @expandFilesOverview}} - {{#if @file2}} - {{#each this.files as |file|}} - + + {{#if (eq @file.isActive false)}} + + + + {{/if}} + + + {{t 'fileID'}} + - + + + + {{@file.id}} + + + + + + + + + {{#if @file.submission.url}} + + + {{#if @file.submission.isIos}} + + {{/if}} + + {{#if @file.submission.isAndroid}} + + {{/if}} + + + {{/if}} + + + + + + + + {{#if this.hasVersion}} + + {{t 'version'}} + - + + + + + {{@file.version}} + + + {{else}} + + {{/if}} + + + + {{#if @file.versionCode}} + + {{t 'versionCodeTitleCase'}} + - + + + + + {{@file.versionCode}} + + + + {{/if}} + + \ No newline at end of file diff --git a/app/components/file-compare/file-overview/file-details/index.scss b/app/components/file-overview/file-details/index.scss similarity index 65% rename from app/components/file-compare/file-overview/file-details/index.scss rename to app/components/file-overview/file-details/index.scss index cdafe5fcf..6ebbb2306 100644 --- a/app/components/file-compare/file-overview/file-details/index.scss +++ b/app/components/file-overview/file-details/index.scss @@ -1,6 +1,6 @@ .file-overview-file-details { padding: 0.6429em 1.1429em; - border-bottom: 1px solid var(--file-compare-file-overview-border-color-light); + border-bottom: 1px solid var(--file-overview-border-color-light); } .file-overview-versions-tags { @@ -9,8 +9,7 @@ .file-version-container { padding: 0.5714em 0em; - border-bottom: 1px solid - var(--file-compare-file-overview-border-color-light); + border-bottom: 1px solid var(--file-overview-border-color-light); .file-version-title { min-width: 140px; @@ -30,11 +29,11 @@ // Icon styles .platform-android { - color: var(--file-compare-file-overview-color-android); + color: var(--file-overview-color-android); } .platform-apple { - color: var(--file-compare-file-overview-color-ios); + color: var(--file-overview-color-ios); } .store-logo-container { @@ -45,7 +44,7 @@ justify-content: center; &:hover { - border: 1px solid var(--file-compare-store-logo-container-background-color); + border: 1px solid var(--file-overview-store-logo-container-background-color); } } @@ -53,3 +52,7 @@ height: 16px; width: 16px; } + +.file-overview-details { + padding: 0.57em 1.285em; +} diff --git a/app/components/file-overview/file-details/index.ts b/app/components/file-overview/file-details/index.ts new file mode 100644 index 000000000..f4e165823 --- /dev/null +++ b/app/components/file-overview/file-details/index.ts @@ -0,0 +1,21 @@ +import Component from '@glimmer/component'; +import FileModel from 'irene/models/file'; + +interface FileOverviewFileDetailsSignature { + Element: HTMLElement; + Args: { + file: FileModel | null; + }; +} + +export default class FileOverviewFileDetailsComponent extends Component { + get hasVersion() { + return typeof this.args.file?.version !== 'undefined'; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'FileOverview::FileDetails': typeof FileOverviewFileDetailsComponent; + } +} diff --git a/app/components/file-overview/header/index.hbs b/app/components/file-overview/header/index.hbs new file mode 100644 index 000000000..4f876fa5d --- /dev/null +++ b/app/components/file-overview/header/index.hbs @@ -0,0 +1,119 @@ +{{!@glint-nocheck }} + + + +
+ {{#if @file.iconUrl}} + + {{else}} + + {{/if}} +
+ + + + {{@file.name}} + + + + {{this.packageName}} + + +
+ + {{#if @showMenuButton}} + + + + {{/if}} + + {{#unless @hideCTAs}} + + + + + + {{#unless @hideOpenInNewTabIcon}} + + + + + + {{/unless}} + + {{/unless}} +
+ + + {{#each this.fileMoreMenuList as |it|}} + {{#if it.group}} + + + {{it.group}} + + + {{/if}} + + + + + + + + + {{/each}} + \ No newline at end of file diff --git a/app/components/file-overview/header/index.scss b/app/components/file-overview/header/index.scss new file mode 100644 index 000000000..8ba9bef33 --- /dev/null +++ b/app/components/file-overview/header/index.scss @@ -0,0 +1,47 @@ +.file-overview-header { + background-color: var(--file-overview-background-grey); + border-bottom: 1px solid var(--file-overview-border-color-dark); + padding: 1em 1.285em; + + .file-icon { + min-width: 44px; + max-width: 44px; + height: 44px; + box-sizing: border-box; + + img { + object-fit: contain; + background-image: none; + overflow: hidden; + border-radius: 4px; + } + } + + .open-in-new-tab-link-class { + display: inline-flex; + } + + .open-in-new-tab-link:hover::after { + content: ''; + position: absolute; + width: 32px; + height: 32px; + border-radius: 100%; + background-color: var(--file-overview-open-in-new-icon-background-color); + } +} + +.card-more-menu-item-group { + padding: 0 0.5em; + padding-bottom: 0.5em; + + &:not(:first-child) { + padding-top: 0.5em; + } +} + +.card-more-menu-item { + a { + padding: 0.5em !important; + } +} diff --git a/app/components/file-overview/header/index.ts b/app/components/file-overview/header/index.ts new file mode 100644 index 000000000..c5b1db14b --- /dev/null +++ b/app/components/file-overview/header/index.ts @@ -0,0 +1,98 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import FileModel from 'irene/models/file'; +import styles from './index.scss'; +import { tracked } from '@glimmer/tracking'; +import IntlService from 'ember-intl/services/intl'; +import { inject as service } from '@ember/service'; + +interface FileOverviewHeaderSignature { + Args: { + file: FileModel | null; + isSelectedFile?: boolean; + disableSelection?: boolean; + onFileSelect?: (file: FileModel | null) => void; + hideCTAs?: boolean; + hideOpenInNewTabIcon?: boolean; + showMenuButton?: boolean; + }; +} + +interface FileMoreMenuItem { + group?: string; + label: string; + iconName: string; + route: string; + routeModel: string | undefined; + hideDivider?: boolean; +} + +export default class FileOverviewHeaderComponent extends Component { + @service declare intl: IntlService; + + @tracked fileMoreMenuRef: HTMLElement | null = null; + + get file() { + return this.args.file; + } + + get packageName() { + return this.args.file?.project.get('packageName'); + } + + @action handleFileSelect(event: Event) { + event.stopPropagation(); + this.args.onFileSelect?.(this.args.file); + } + + @action + handleFileMoreMenuOpen(event: MouseEvent) { + event.stopPropagation(); + event.preventDefault(); + + this.fileMoreMenuRef = event.currentTarget as HTMLElement; + } + + @action + handleFileMoreMenuClose() { + this.fileMoreMenuRef = null; + } + + get fileMoreMenuList() { + const hasMultipleFiles = this.args.file?.project.get('hasMultipleFiles'); + + return [ + hasMultipleFiles && { + group: this.intl.t('fileLevel'), + label: this.intl.t('compare'), + iconName: 'compare-arrows', + route: 'authenticated.choose', + routeModel: this.file?.id, + }, + hasMultipleFiles && { + group: this.intl.t('projectLevel'), + label: this.intl.t('allUploads'), + iconName: 'apps', + route: 'authenticated.project.files', + routeModel: this.args.file?.project.get('id'), + }, + { + label: this.intl.t('settings'), + iconName: 'settings', + route: 'authenticated.project.settings', + routeModel: this.args.file?.project.get('id'), + hideDivider: true, + }, + ].filter(Boolean) as FileMoreMenuItem[]; + } + + get openInNewTabLinkClass() { + return styles['open-in-new-tab-link-class']; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'FileOverview::Header': typeof FileOverviewHeaderComponent; + } +} diff --git a/app/components/file-overview/index.hbs b/app/components/file-overview/index.hbs index 5bca46b24..4f77bb236 100644 --- a/app/components/file-overview/index.hbs +++ b/app/components/file-overview/index.hbs @@ -1,327 +1,51 @@ - -
- {{this.file.name}} -
-

-

-

-
+ + + -
- {{#unless this.file.isActive}} - - - - {{/unless}} - - FILE ID - - {{this.file.id}} -
-
-
- {{#if this.file.isStaticDone}} -
- -
- {{else}} -
- -
- {{/if}} -
- {{t 'static'}} -
-
-
- {{#if this.file.isDynamicDone}} -
- -
- {{else}} -
- -
- {{/if}} -
- {{t 'dynamic'}} -
-
-
- {{#if this.file.isApiDone}} -
- -
- {{else}} -
- -
- {{/if}} -
- {{t 'api'}} -
-
- {{#unless this.isManualScanDisabled}} -
- {{#if this.file.isManualDone}} -
- -
- {{else if this.file.isManualRequested}} -
- -
- {{else}} -
- -
- {{/if}} -
- {{t 'manual'}} -
-
- {{/unless}} -
+ {{#if this.profileId}} + + {{else}} + + {{/if}}
- -
-
-
- -
-
-
- -
- {{t 'started'}}  - {{dayjs-from-now this.file.createdOn}} -
-
-
-
    -
    -
    -
    -
  • - {{t 'critical'}} -
  • -
    -
    - {{this.file.countRiskCritical}} -
    -
    -
    -
    -
  • - {{t 'high'}} -
  • -
    -
    - {{this.file.countRiskHigh}} -
    -
    -
    -
    -
  • - {{t 'medium'}} -
  • -
    -
    - {{this.file.countRiskMedium}} -
    -
    -
    -
    -
  • - {{t 'low'}} -
  • -
    -
    - {{this.file.countRiskLow}} -
    -
    -
    -
    -
  • - {{t 'passed'}} -
  • -
    -
    - {{this.file.countRiskNone}} -
    -
    -
    -
    -
  • - {{t 'untested'}} -
  • -
    -
    - {{this.file.countRiskUnknown}} -
    -
    -
    -
-
-
-
+ - - - {{#each this.file.tags as |tag|}} - - {{/each}} - - -
+ {{yield}}
\ No newline at end of file diff --git a/app/components/file-overview/index.js b/app/components/file-overview/index.js deleted file mode 100644 index a559968ea..000000000 --- a/app/components/file-overview/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import { inject as service } from '@ember/service'; -import Component from '@glimmer/component'; -import { action } from '@ember/object'; - -export default class FileOverviewComponent extends Component { - @service store; - - chartOptions = { - legend: { display: false }, - animation: { animateRotate: false }, - responsive: false, - }; - - get file() { - return this.args.file || null; - } - - get isManualScanDisabled() { - return !this.file.get('project')?.get('isManualScanAvailable'); - } - - get fileOld() { - return this.args.fileOld || null; - } - - get profileId() { - return this.args.profileId; - } - - get unknownAnalysisStatus() { - return this.store.queryRecord('unknown-analysis-status', { - id: this.profileId, - }); - } - - @action - handleLinkClick(event) { - event.stopPropagation(); - } -} diff --git a/app/components/file-overview/index.scss b/app/components/file-overview/index.scss index 0f8bf4103..c54ccae64 100644 --- a/app/components/file-overview/index.scss +++ b/app/components/file-overview/index.scss @@ -1,50 +1,10 @@ -.card-header { - background-color: var(--neutral-grey-100); - padding: 1em; - box-sizing: border-box; +.file-overview { + border-radius: var(--file-overview-border-radius); + border: 1px solid var(--file-overview-border-color-dark); + background-color: var(--file-overview-background-white); + height: 100%; } -.app-information { - background-color: var(--neutral-grey-50); - padding: 0.2em 1em; - border: 1px solid var(--neutral-grey-100); - box-sizing: border-box; - - .file-id-text { - display: flex; - align-items: center; - font-weight: bold; - font-size: 0.75em; - line-height: 1.4; - color: var(--neutral-grey-800); - - .file-in-active-icon { - font-size: 13px; - } - } -} - -.file-tags-container { - overflow-x: auto; - min-height: 26px; -} - -.store-logo-container { - display: flex; - height: 24px; - width: 24px; - align-items: center; - justify-content: center; - - &:hover { - background-color: var( - --file-overview-store-logo-container-background-color - ); - border: 1px solid var(--file-overview-store-logo-container-border-color); - } -} - -.appstore-logo-vector { - height: 16px; - width: 16px; +.file-overview-chart { + flex: 1; } diff --git a/app/components/file-compare/file-overview/index.ts b/app/components/file-overview/index.ts similarity index 74% rename from app/components/file-compare/file-overview/index.ts rename to app/components/file-overview/index.ts index 311578f99..b375c68a3 100644 --- a/app/components/file-compare/file-overview/index.ts +++ b/app/components/file-overview/index.ts @@ -11,16 +11,21 @@ interface FileCompareFileOverviewSignature { profileId: string | number; hideCTAs?: boolean; hideOpenInNewTabIcon?: boolean; + showMenuButton?: boolean; }; Blocks: { default: []; }; } -export default class FileCompareFileOverviewComponent extends Component {} +export default class FileCompareFileOverviewComponent extends Component { + get profileId() { + return this.args.file?.profile.get('id'); + } +} declare module '@glint/environment-ember-loose/registry' { export default interface Registry { - 'FileCompare::FileOverview': typeof FileCompareFileOverviewComponent; + FileOverview: typeof FileCompareFileOverviewComponent; } } diff --git a/app/components/file-compare/file-overview/scan-statuses/index.hbs b/app/components/file-overview/scan-statuses/index.hbs similarity index 51% rename from app/components/file-compare/file-overview/scan-statuses/index.hbs rename to app/components/file-overview/scan-statuses/index.hbs index e9bbb8838..7cb96296b 100644 --- a/app/components/file-compare/file-overview/scan-statuses/index.hbs +++ b/app/components/file-overview/scan-statuses/index.hbs @@ -1,28 +1,25 @@ - + {{#each this.scanStatuses as |status|}} - + {{status.name}} @@ -32,32 +29,40 @@ {{#if @file.isManualDone}} {{else}} {{/if}} {{t 'manual'}} {{/unless}} + + + + \ No newline at end of file diff --git a/app/components/file-overview/scan-statuses/index.scss b/app/components/file-overview/scan-statuses/index.scss new file mode 100644 index 000000000..b245b081e --- /dev/null +++ b/app/components/file-overview/scan-statuses/index.scss @@ -0,0 +1,14 @@ +.file-overview-statuses { + background: var(--file-overview-background-grey); + border: 1px solid var(--file-overview-border-color-dark); + border-left: none; + border-right: none; +} + +.file-overview-status { + padding: 0.57em 1.2em; +} + +.file-overview-status-icon { + font-size: 1.357rem !important; +} diff --git a/app/components/file-compare/file-overview/scan-statuses/index.ts b/app/components/file-overview/scan-statuses/index.ts similarity index 70% rename from app/components/file-compare/file-overview/scan-statuses/index.ts rename to app/components/file-overview/scan-statuses/index.ts index 96132bd62..35b32331d 100644 --- a/app/components/file-compare/file-overview/scan-statuses/index.ts +++ b/app/components/file-overview/scan-statuses/index.ts @@ -3,14 +3,14 @@ import Component from '@glimmer/component'; import IntlService from 'ember-intl/services/intl'; import FileModel from 'irene/models/file'; -interface FileCompareFileOverviewScanStatusesSignature { +interface FileOverviewScanStatusesSignature { Element: HTMLElement; Args: { file: FileModel | null; }; } -export default class FileCompareFileOverviewScanStatusesComponent extends Component { +export default class FileOverviewScanStatusesComponent extends Component { @service declare intl: IntlService; get file() { @@ -18,7 +18,7 @@ export default class FileCompareFileOverviewScanStatusesComponent extends Compon } get isManualScanDisabled() { - return !this.file?.project.get('isManualScanAvailable'); + return !this.file?.project?.get('isManualScanAvailable'); } get scanStatuses() { @@ -41,6 +41,6 @@ export default class FileCompareFileOverviewScanStatusesComponent extends Compon declare module '@glint/environment-ember-loose/registry' { export default interface Registry { - 'FileCompare::FileOverview::ScanStatuses': typeof FileCompareFileOverviewScanStatusesComponent; + 'FileOverview::ScanStatuses': typeof FileOverviewScanStatusesComponent; } } diff --git a/app/components/file-overview/tags/index.hbs b/app/components/file-overview/tags/index.hbs new file mode 100644 index 000000000..306b116da --- /dev/null +++ b/app/components/file-overview/tags/index.hbs @@ -0,0 +1,36 @@ + + {{t 'tags'}} + + + {{#if (gt @file.tags.length 0)}} + {{#each @file.tags as |tag|}} + + {{/each}} + {{else}} + + {{t 'fileCompare.noTagsMessage'}} + + {{/if}} + + \ No newline at end of file diff --git a/app/components/file-overview/tags/index.scss b/app/components/file-overview/tags/index.scss new file mode 100644 index 000000000..cb20facc7 --- /dev/null +++ b/app/components/file-overview/tags/index.scss @@ -0,0 +1,13 @@ +.file-tags-container { + overflow-x: auto; + min-height: 26px; +} + +.card-chips { + background-color: var(--file-overview-background-grey); + border-radius: 4px; +} + +.text-nowrap { + white-space: nowrap; +} diff --git a/app/components/file-overview/tags/index.ts b/app/components/file-overview/tags/index.ts new file mode 100644 index 000000000..2370dcf9e --- /dev/null +++ b/app/components/file-overview/tags/index.ts @@ -0,0 +1,17 @@ +import Component from '@glimmer/component'; +import FileModel from 'irene/models/file'; + +interface FileOverviewTagsSignature { + Element: HTMLElement; + Args: { + file: FileModel | null; + }; +} + +export default class FileOverviewTagsComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'FileOverview::Tags': typeof FileOverviewTagsComponent; + } +} diff --git a/app/components/project-list/filter-selected-item/index.hbs b/app/components/project-list/filter-selected-item/index.hbs new file mode 100644 index 000000000..fa89aca27 --- /dev/null +++ b/app/components/project-list/filter-selected-item/index.hbs @@ -0,0 +1,17 @@ + + + + + + {{#if this.showLabel}} + + {{this.optionTitle}} + - + + {{/if}} + + + + {{this.selectedItem}} + + \ No newline at end of file diff --git a/app/components/project-list/filter-selected-item/index.scss b/app/components/project-list/filter-selected-item/index.scss new file mode 100644 index 000000000..fbfbde0ca --- /dev/null +++ b/app/components/project-list/filter-selected-item/index.scss @@ -0,0 +1,3 @@ +.trigger-label { + font-size: 0.857rem !important; +} diff --git a/app/components/project-list/filter-selected-item/index.ts b/app/components/project-list/filter-selected-item/index.ts new file mode 100644 index 000000000..f0fbc3350 --- /dev/null +++ b/app/components/project-list/filter-selected-item/index.ts @@ -0,0 +1,29 @@ +import Component from '@glimmer/component'; + +interface ProjectListFilterSelectedItemArgs { + extra?: Record; +} + +export default class ProjectListFilterSelectedItemComponent extends Component { + get optionTitle() { + return this.args.extra?.['optionTitle']; + } + + get selectedItem() { + return this.args.extra?.['selectedItem']; + } + + get showLabel() { + return this.args.extra?.['showLabel']; + } + + get iconName() { + return this.args.extra?.['iconName']; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectList::FilterSelectedItem': typeof ProjectListFilterSelectedItemComponent; + } +} diff --git a/app/components/project-list/header/index.hbs b/app/components/project-list/header/index.hbs new file mode 100644 index 000000000..a4e514a8f --- /dev/null +++ b/app/components/project-list/header/index.hbs @@ -0,0 +1,183 @@ + + + + {{t 'allProjects'}} + + + {{t 'allProjectsDescription'}} + + + + {{#if @hasProjects}} +
+ + <:leftAdornment> + + + + <:rightAdornment> + {{#if @query}} + + + + {{/if}} + + +
+ {{/if}} +
+ + {{#if @hasProjects}} + + + + + + + + {{#if (eq @platform platformObject.value)}} + + {{else}} + + {{/if}} + + + {{platformObject.key}} + + + + + + + {{#if (eq this.selectedTeamName team.name)}} + + {{else}} + + {{/if}} + + + {{team.name}} + + + + {{#if this.showClearFilter}} + + + + <:leftIcon> + + + + <:default> + + {{t 'clearFilter'}} + + + + {{/if}} + + + + + + {{#if (eq @sortKey sortingKeyObject.key)}} + + {{else}} + + {{/if}} + + + {{sortingKeyObject.text}} + + + + {{/if}} +
\ No newline at end of file diff --git a/app/components/project-list/header/index.scss b/app/components/project-list/header/index.scss new file mode 100644 index 000000000..086331ab7 --- /dev/null +++ b/app/components/project-list/header/index.scss @@ -0,0 +1,88 @@ +.search-input-conntainer-width { + width: 200px; +} + +.search-package-name-input { + font-size: 0.857rem !important; +} + +.header-home-page { + background-color: var(--project-list-header-background-color); + border: 1px solid var(--project-list-header-border-color); + border-radius: var(--project-list-header-border-radius); +} + +.header-home-page-title { + padding: 1em 1.285em; +} + +.header-home-page-sorting-filter { + padding: 0.785em 1.285em; +} + +.select-team-class { + padding-left: 0.714em; +} + +.filter-input { + border-color: var(--project-list-header-filter-border-color) !important; + padding: 0px 8px !important; + + :global(.ember-power-select-status-icon) { + display: none; + } + + &:hover { + border-color: var( + --project-list-header-filter-hover-border-color + ) !important; + } +} + +.divider { + width: 1px; + height: 32px; + background-color: var(--project-list-header-divider-background-color); + margin-left: 0.714em; +} + +.clear-filter-label { + font-size: 0.857rem !important; + text-decoration: underline; + color: var(--project-list-header-clear-filter-color) !important; +} + +.clear-filter-icon { + margin-right: 0.2em; + color: var(--project-list-header-clear-filter-color) !important; +} + +.clear-filter:hover { + box-shadow: none; + + &:hover { + .clear-filter-icon, + .clear-filter-label { + color: var(--project-list-header-clear-filter-hover-color) !important; + } + } +} + +.filter-input-dropdown { + min-width: max-content !important; + + :global(.ember-power-select-option) { + background-color: var(--project-list-header-background-color) !important; + padding: 0.5em 0.75em !important; + + &[aria-current='true'] { + background-color: var( + --project-list-header-filter-option-hover-background + ) !important; + } + } + + :global(.ember-power-select-options) { + padding: 0 !important; + } +} diff --git a/app/components/project-list/header/index.ts b/app/components/project-list/header/index.ts new file mode 100644 index 000000000..182d64277 --- /dev/null +++ b/app/components/project-list/header/index.ts @@ -0,0 +1,241 @@ +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import IntlService from 'ember-intl/services/intl'; + +import OrganizationService from 'irene/services/organization'; +import Store from '@ember-data/store'; +import ENUMS from 'irene/enums'; +import ProjectService from 'irene/services/project'; +import styles from './index.scss'; + +interface Team { + name: string; + id?: string; +} + +interface SortingKeyObject { + key: string; + text: string; +} + +interface PlatformObject { + key: string; + value: number; +} + +interface ProjectListHeaderArgs { + hasProjects: boolean; + query: string; + platform: number; + sortKey: string; + onQueryChange(event: Event): void; + handleClear(): void; + filterPlatform(platform: PlatformObject): void; + onSelectTeam(team: Team): void; + sortProjects(selected: SortingKeyObject): void; +} + +export default class ProjectListHeaderComponent extends Component { + @service declare intl: IntlService; + @service declare organization: OrganizationService; + @service declare store: Store; + @service('project') declare projectService: ProjectService; + + tDateUpdated: string; + tDateCreated: string; + tPackageName: string; + tMostRecent: string; + tLeastRecent: string; + + @tracked teams = [ + { + name: 'All', + }, + ]; + + @tracked defaultTeam: Team = { + name: 'All', + }; + + @tracked selectedPlatform = { + key: 'All', + value: -1, + }; + + @tracked selectedSortKey: SortingKeyObject; + + @tracked selectedTeam = { + name: 'All', + }; + + @tracked selectedTeamName = 'All'; + + constructor(owner: unknown, args: ProjectListHeaderArgs) { + super(owner, args); + + this.tDateUpdated = this.intl.t('dateUpdated'); + this.tDateCreated = this.intl.t('dateCreated'); + this.tPackageName = this.intl.t('packageName'); + this.tMostRecent = this.intl.t('mostRecent'); + this.tLeastRecent = this.intl.t('leastRecent'); + + this.selectedSortKey = { + key: '-last_file_created_on', + text: `${this.tDateUpdated} ${this.tMostRecent}`, + }; + + this.projectService.fetchProjects.perform(); + } + + get showClearFilter() { + return ( + this.selectedTeam.name !== this.defaultTeam.name || + this.selectedPlatform.value !== -1 + ); + } + + get sortingKeyObjects() { + return [ + { + key: '-last_file_created_on', + text: `${this.tDateUpdated} ${this.tMostRecent}`, + }, + { + key: 'last_file_created_on', + text: `${this.tDateUpdated} ${this.tLeastRecent}`, + }, + { + key: '-id', + text: `${this.tDateCreated} ${this.tMostRecent}`, + }, + { + key: 'id', + text: `${this.tDateCreated} ${this.tLeastRecent}`, + }, + { + key: '-package_name', + text: `${this.tPackageName} (Z -> A)`, + }, + { + key: 'package_name', + text: `${this.tPackageName} (A -> Z)`, + }, + ]; + } + + get platformObjects(): PlatformObject[] { + return [ + { + key: 'All', + value: -1, + }, + { + key: 'Android', + value: ENUMS.PLATFORM.ANDROID, + }, + { + key: 'iOS', + value: ENUMS.PLATFORM.IOS, + }, + ]; + } + + get dropDownClass() { + return styles['filter-input-dropdown']; + } + + get triggerClass() { + return styles['filter-input']; + } + + get clearFilterIconClass() { + return styles['clear-filter-icon']; + } + + @action onSortProjectsChange(selected: SortingKeyObject) { + this.selectedSortKey = selected; + + this.args.sortProjects(selected); + } + + @action filterPlatformChange(platform: PlatformObject) { + this.selectedPlatform = platform; + this.args.filterPlatform(platform); + } + + @action + clearSearchInput() { + this.args.handleClear(); + } + + @action clearFilters() { + this.onSelectTeamChange(this.defaultTeam); + + this.filterPlatformChange({ + key: 'All', + value: -1, + }); + } + + @action onSelectTeamChange(team: Team) { + this.selectedTeam = team; + + this.selectedTeamName = team.name; + + this.args.onSelectTeam(team); + } + + @action onOpenTFilter() { + const query = { + limit: 10, + }; + + this.queryTeams.perform(query); + } + + @action searchTeams(teamName: string) { + if (teamName && teamName.length) { + const query = { + q: teamName, + }; + + this.queryTeams.perform(query); + } + } + + @action onSearchQueryChange(event: Event) { + this.args.onQueryChange(event); + } + + /** + * @function queryTeams + * @param {String} teamName + * Method to query all the matching teams with given name + */ + queryTeams = task(async (query) => { + const teamList: Team[] = []; + const teams = await this.store.query('organization-team', query); + + if (Number(teams.length) > 0) { + teamList.push(this.defaultTeam); + } + + teams.forEach((team) => { + teamList.push({ + name: team.name, + id: team.id, + }); + }); + + this.teams = teamList; + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectList::Header': typeof ProjectListHeaderComponent; + } +} diff --git a/app/components/project-list/index.hbs b/app/components/project-list/index.hbs index 84645b4e0..b82c9d4ca 100644 --- a/app/components/project-list/index.hbs +++ b/app/components/project-list/index.hbs @@ -1,93 +1,24 @@ -{{#if this.hasProjects}} -
-
-
- -
- {{concat (t 'filterBy') ' ' (t 'team')}} -
- - {{team.name}} - -
-
-
- {{t 'sortBy'}}
- -
-
-
- {{t 'filterBy'}}
- -
-
-
-
-
+
+ + + {{#if this.hasProjects}} +
{{#if this.isLoading}} -
- +
+
{{else if this.showProjectResults}} {{else if this.hasNoProjects}} -
-
+ + + + {{t 'noResultsFound'}} -
-
+ + + {{t 'tryAdjustingFilter'}} -
-
+ + + {{else}} -
- +
+
{{/if}}
-
-{{else}} -
-
-

- {{t 'noProject'}}!! -

-
- {{t 'noProjectUploaded'}} -
-
- {{t 'uploadNewProject' htmlSafe=true}} -
-
-
-{{/if}} \ No newline at end of file + {{else}} + + + + + {{t 'uploadAnApp'}} + + + + {{t 'noProjectExists'}} + + + {{/if}} +
\ No newline at end of file diff --git a/app/components/project-list/index.scss b/app/components/project-list/index.scss index 8d054db4f..f0dae0291 100644 --- a/app/components/project-list/index.scss +++ b/app/components/project-list/index.scss @@ -1,45 +1,19 @@ -.filters { +.project-grid { display: grid; - grid-template-columns: repeat(4, 1fr); + justify-items: stretch; + grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 2em; } -.no-result-found { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 300px; - width: 100%; - box-sizing: border-box; -} - -.sorting-select { - text-overflow: ellipsis; - padding-right: 1.8125em; +.home-page-container { + padding: 0.714em 4.285em 0 4.285em; } .no-project-container { - display: flex; - align-items: center; - justify-content: center; - min-height: 500px; - box-sizing: border-box; - - .no-project { - width: 100%; - min-width: 300px; - max-width: 400px; - padding: 10px; - border: 1px dashed var(--primary); - border-radius: 10px; - text-align: center; - } -} - -.project-grid { - display: grid; - justify-items: stretch; - grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); - gap: 2em; + height: 60vh; + width: 100%; + padding: 0 25%; + background-color: var(--project-list-background-color); + border: 1px solid var(--project-list-border-color); + border-radius: var(--project-list-border-radius); } diff --git a/app/components/project-list/index.js b/app/components/project-list/index.ts similarity index 50% rename from app/components/project-list/index.js rename to app/components/project-list/index.ts index 6e6db31dc..8ace610a0 100644 --- a/app/components/project-list/index.js +++ b/app/components/project-list/index.ts @@ -2,25 +2,37 @@ import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; -import { task } from 'ember-concurrency'; import { isEmpty } from '@ember/utils'; +import IntlService from 'ember-intl/services/intl'; +import OrganizationService from 'irene/services/organization'; +import Store from '@ember-data/store'; -import ENUMS from 'irene/enums'; import { INPUT } from 'irene/utils/constants'; -import { DEFAULT_PROJECT_QUERY_PARAMS } from 'irene/services/project'; + +import ProjectService, { + DEFAULT_PROJECT_QUERY_PARAMS, +} from 'irene/services/project'; + +interface Team { + name: string; + id?: string; +} + +interface SortingKeyObject { + key: string; + text: string; +} + +interface PlatformObject { + key: string; + value: number; +} export default class ProjectListComponent extends Component { - @service intl; - @service organization; - @service store; - @service('project') projectService; - - tDateUpdated = this.intl.t('dateUpdated'); - tDateCreated = this.intl.t('dateCreated'); - tProjectName = this.intl.t('projectName'); - tPackageName = this.intl.t('packageName'); - tMostRecent = this.intl.t('mostRecent'); - tLeastRecent = this.intl.t('leastRecent'); + @service declare intl: IntlService; + @service declare organization: OrganizationService; + @service declare store: Store; + @service('project') declare projectService: ProjectService; @tracked limit = DEFAULT_PROJECT_QUERY_PARAMS.limit; @tracked offset = DEFAULT_PROJECT_QUERY_PARAMS.offset; @@ -29,30 +41,8 @@ export default class ProjectListComponent extends Component { @tracked platform = DEFAULT_PROJECT_QUERY_PARAMS.platform; @tracked team = DEFAULT_PROJECT_QUERY_PARAMS.team; - /** - * @property {Array} teams - * Property for list of matching teams - */ - @tracked teams = [ - { - name: 'All', - }, - ]; - - @tracked defaultTeam = { - name: 'All', - }; - - /** - * @property {Object} selectedTeam - * Property for selected team from the list - */ - @tracked selectedTeam = { - name: 'All', - }; - - constructor() { - super(...arguments); + constructor(owner: unknown, args: object) { + super(owner, args); this.projectService.fetchProjects.perform(); } @@ -67,7 +57,8 @@ export default class ProjectListComponent extends Component { get hasProjects() { return ( - this.organization.selected.projectsCount > 0 || + (this.organization.selected && + this.organization.selected.projectsCount > 0) || !isEmpty(this.projectService.projectQueryResponse) ); } @@ -93,41 +84,8 @@ export default class ProjectListComponent extends Component { return this.showProjectResults && !this.isLoading; } - get sortingKeyObjects() { - return [ - { - key: '-last_file_created_on', - text: `${this.tDateUpdated} ${this.tMostRecent}`, - }, - { - key: 'last_file_created_on', - text: `${this.tDateUpdated} ${this.tLeastRecent}`, - }, - { - key: '-id', - text: `${this.tDateCreated} ${this.tMostRecent}`, - }, - { - key: 'id', - text: `${this.tDateCreated} ${this.tLeastRecent}`, - }, - { - key: '-package_name', - text: `${this.tPackageName} (Z -> A)`, - }, - { - key: 'package_name', - text: `${this.tPackageName} (A -> Z)`, - }, - ]; - } - - get platformObjects() { - return ENUMS.PLATFORM.CHOICES.slice(0, +-4 + 1 || undefined); - } - @action - handlePrevNextAction({ limit, offset }) { + handlePrevNextAction({ limit, offset }: { limit: number; offset: number }) { this.limit = limit; this.offset = offset; @@ -142,7 +100,7 @@ export default class ProjectListComponent extends Component { } @action - handleItemPerPageChange({ limit }) { + handleItemPerPageChange({ limit }: { limit: number }) { this.limit = limit; this.offset = 0; @@ -156,8 +114,8 @@ export default class ProjectListComponent extends Component { ); } - @action sortProjects(event) { - this.sortKey = event?.target?.value; + @action sortProjects(selected: SortingKeyObject) { + this.sortKey = selected?.key; this.offset = 0; this.projectService.fetchProjects.perform( @@ -170,8 +128,8 @@ export default class ProjectListComponent extends Component { ); } - @action filterPlatform(event) { - this.platform = parseInt(event?.target?.value); + @action filterPlatform(platform: PlatformObject) { + this.platform = platform.value; this.offset = 0; this.projectService.fetchProjects.perform( @@ -184,9 +142,9 @@ export default class ProjectListComponent extends Component { ); } - @action onSelectTeam(team) { - this.selectedTeam = team; - this.team = team?.id || ''; + @action + handleClear() { + this.query = ''; this.projectService.fetchProjects.perform( this.limit, @@ -198,24 +156,21 @@ export default class ProjectListComponent extends Component { ); } - @action onOpenTFilter() { - const query = { - limit: 10, - }; - this.queryTeams.perform(query); - } + @action onSelectTeam(team: Team) { + this.team = team?.id || ''; - @action searchTeams(teamName) { - if (teamName && teamName.length) { - const query = { - q: teamName, - }; - this.queryTeams.perform(query); - } + this.projectService.fetchProjects.perform( + this.limit, + 0, + this.query, + this.sortKey, + this.platform, + this.team + ); } - @action onQueryChange(event) { - const query = event.target.value; + @action onQueryChange(event: Event) { + const query = (event.target as HTMLSelectElement).value; if (query.length >= INPUT.MIN_LENGTH || query === '') { this.query = query; @@ -230,23 +185,10 @@ export default class ProjectListComponent extends Component { ); } } +} - /** - * @function queryTeams - * @param {String} teamName - * Method to query all the matching teams with given name - */ - queryTeams = task(async (query) => { - const teamList = [this.defaultTeam]; - const teams = await this.store.query('organization-team', query); - - teams.forEach((team) => { - teamList.push({ - name: team.name, - id: team.id, - }); - }); - - this.teams = teamList; - }); +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + ProjectList: typeof ProjectListComponent; + } } diff --git a/app/components/project-list/loader/index.hbs b/app/components/project-list/loader/index.hbs new file mode 100644 index 000000000..278eda8ec --- /dev/null +++ b/app/components/project-list/loader/index.hbs @@ -0,0 +1,19 @@ + + + + + + + {{#if @loadingText}} + + {{@loadingText}} + + {{/if}} + + \ No newline at end of file diff --git a/app/components/project-list/loader/index.scss b/app/components/project-list/loader/index.scss new file mode 100644 index 000000000..57166e746 --- /dev/null +++ b/app/components/project-list/loader/index.scss @@ -0,0 +1,15 @@ +.loading { + border: 1px solid var(--project-list-loader-border-color); + border-radius: var(--project-list-loader-border-radius); + width: 100%; + min-height: 35em; + + .loading-state-vector { + margin-bottom: 1.5625em; + } + + .loading-text { + text-align: center; + max-width: 302px; + } +} diff --git a/app/components/project-list/loader/index.ts b/app/components/project-list/loader/index.ts new file mode 100644 index 000000000..b68aa7f90 --- /dev/null +++ b/app/components/project-list/loader/index.ts @@ -0,0 +1,16 @@ +import Component from '@glimmer/component'; + +interface ProjectListLoaderSignature { + Element: HTMLElement; + Args: { + loadingText?: string; + }; +} + +export default class FileCompareLoaderComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectList::Loader': typeof FileCompareLoaderComponent; + } +} diff --git a/app/components/project-overview/index.hbs b/app/components/project-overview/index.hbs index 24cf35a04..112069f53 100644 --- a/app/components/project-overview/index.hbs +++ b/app/components/project-overview/index.hbs @@ -1,44 +1,13 @@ -
- -   - {{t 'settings'}} - - - {{#if this.project.hasMultipleFiles}} - -   - {{t 'allUploads'}} - - - -   - {{t 'compare'}} - - {{/if}} -
-
+ @hideCTAs={{true}} + @showMenuButton={{true}} + />
\ No newline at end of file diff --git a/app/components/project-overview/index.js b/app/components/project-overview/index.js deleted file mode 100644 index d598ee9d0..000000000 --- a/app/components/project-overview/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import Component from '@glimmer/component'; - -export default class ProjectOverviewComponent extends Component { - get project() { - return this.args.project || null; - } -} diff --git a/app/components/project-overview/index.scss b/app/components/project-overview/index.scss new file mode 100644 index 000000000..241dba3cb --- /dev/null +++ b/app/components/project-overview/index.scss @@ -0,0 +1,3 @@ +.project-card:hover { + box-shadow: var(--project-overview-card-hover-shadow); +} diff --git a/app/components/project-overview/index.ts b/app/components/project-overview/index.ts new file mode 100644 index 000000000..3873141c0 --- /dev/null +++ b/app/components/project-overview/index.ts @@ -0,0 +1,22 @@ +import Component from '@glimmer/component'; +import ProjectModel from 'irene/models/project'; + +interface ProjectOverviewArgs { + project: ProjectModel; +} + +export default class ProjectOverviewComponent extends Component { + get project() { + return this.args.project || null; + } + + get lastFile() { + return this.project.get('lastFileId').content || null; + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + ProjectOverview: typeof ProjectOverviewComponent; + } +} diff --git a/app/services/project.ts b/app/services/project.ts index e505d9ce4..6b5183d82 100644 --- a/app/services/project.ts +++ b/app/services/project.ts @@ -16,7 +16,7 @@ type ProjectQueryResponse = DS.AdapterPopulatedRecordArray & { }; export const DEFAULT_PROJECT_QUERY_PARAMS = { - limit: 10, + limit: 12, offset: 0, query: '', sortKey: '-last_file_created_on', diff --git a/app/styles/_component-variables.scss b/app/styles/_component-variables.scss index 24976dd16..ba8c53e26 100644 --- a/app/styles/_component-variables.scss +++ b/app/styles/_component-variables.scss @@ -93,6 +93,9 @@ body { --ak-select-trigger-disabled-background: var(--disabled-background-textfield); --ak-select-trigger-arrow-color: var(--text-primary); + // variables for ak-select/before-option + --ak-select-before-option-border-color: var(--neutral-grey-100); + // variables for ak-modal --ak-modal-zIndex: var(--zIndex-modal); --ak-modal-backdrop-overlay-background: var(--backdrop-overlay-background); @@ -762,19 +765,6 @@ body { // variables for file-compare/analysis-details --file-compare-analysis-details-border-color: var(--border-color-1); - // variables for file-compare/file-overview - --file-compare-file-overview-border-radius: var(--border-radius); - --file-compare-file-overview-background-white: var(--common-white); - --file-compare-file-overview-background-grey: var(--neutral-grey-100); - --file-compare-file-overview-border-color-light: var(--neutral-grey-100); - --file-compare-file-overview-open-in-new-icon-background-color: var( - --hover-light-background - ); - --file-compare-file-overview-border-color-dark: var(--border-color-1); - --file-compare-file-overview-color-android: var(--android); - --file-compare-file-overview-color-ios: var(--ios); - --file-compare-store-logo-container-background-color: var(--neutral-grey-200); - // variables for file-compare/compare-list --file-compare-compare-list-wrapper-background-color: var(--background-main); @@ -818,6 +808,21 @@ body { --file-chart-severity-level-color-untested: var(--severity-untested); --file-chart-severity-level-color-none: var(--severity-none); + // variables for file-overview + --file-overview-border-radius: var(--border-radius); + --file-overview-background-white: var(--common-white); + --file-overview-background-grey: var(--neutral-grey-100); + --file-overview-border-color-light: var(--neutral-grey-100); + --file-overview-open-in-new-icon-background-color: var( + --hover-light-background + ); + --file-overview-border-color-dark: var(--border-color-1); + --file-overview-color-android: var(--android); + --file-overview-color-ios: var(--ios); + --file-overview-store-logo-container-background-color: var( + --neutral-grey-200 + ); + // variables for account-settings/mfa --mfa-loader-primary-contrast-text: var(--primary-contrast-text); @@ -971,4 +976,29 @@ body { // variables for file-overview --file-overview-store-logo-container-background-color: var(--common-white); --file-overview-store-logo-container-border-color: var(--neutral-grey-200); + + // variables for project-list + --project-list-background-color: var(--common-white); + --project-list-border-color: var(--neutral-white-100); + --project-list-border-radius: var(--border-radius); + + // variables for project-list/loader + --project-list-loader-border-color: var(--neutral-white-100); + --project-list-loader-border-radius: var(--border-radius); + + // variables for project-list/header + --project-list-header-background-color: var(--common-white); + --project-list-header-border-color: var(--neutral-white-100); + --project-list-header-border-radius: var(--border-radius); + --project-list-header-filter-border-color: var(--neutral-grey-200); + --project-list-header-filter-hover-border-color: var(--neutral-grey-400); + --project-list-header-filter-option-hover-background: var( + --hover-light-background + ); + --project-list-header-divider-background-color: var(--neutral-grey-200); + --project-list-header-clear-filter-color: var(--neutral-grey-600); + --project-list-header-clear-filter-hover-color: var(--primary-main); + + // variables for project-overview + --project-overview-card-hover-shadow: var(--box-shadow-7); } diff --git a/app/styles/_icons.scss b/app/styles/_icons.scss index f3a560182..38d5e826b 100644 --- a/app/styles/_icons.scss +++ b/app/styles/_icons.scss @@ -522,3 +522,15 @@ .ak-icon-keyboard-tab { @extend .mi-keyboard-tab; } + +.ak-icon-date-range { + @extend .mi-date-range; +} + +.ak-icon-filter-list { + @extend .mi-filter-list; +} + +.ak-icon-sort { + @extend .mi-sort; +} diff --git a/tests/acceptance/file-compare-test.js b/tests/acceptance/file-compare-test.js index aa7981aec..aadd47f0c 100644 --- a/tests/acceptance/file-compare-test.js +++ b/tests/acceptance/file-compare-test.js @@ -104,7 +104,7 @@ module('Acceptance | file compare', function (hooks) { const compareFileSelector = `[data-test-fileCompare-compareList-fileOverview='${compareFile.id}']`; await click( - `${compareFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` + `${compareFileSelector} [data-test-fileOverview-selectCheckBox]` ); assert @@ -129,12 +129,10 @@ module('Acceptance | file compare', function (hooks) { const compareFileSelector = `[data-test-fileList-fileOverview='${compareFile.id}']`; // Selects base and compare files - await click( - `${baseFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` - ); + await click(`${baseFileSelector} [data-test-fileOverview-selectCheckBox]`); await click( - `${compareFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` + `${compareFileSelector} [data-test-fileOverview-selectCheckBox]` ); await click('[data-test-fileList-projectOverview-header-compareBtn]'); diff --git a/tests/integration/components/file-compare/compare-list-test.js b/tests/integration/components/file-compare/compare-list-test.js index 43cb5b05a..95acd67dc 100644 --- a/tests/integration/components/file-compare/compare-list-test.js +++ b/tests/integration/components/file-compare/compare-list-test.js @@ -194,7 +194,7 @@ module('Integration | Component | file-compare/compare-list', function (hooks) { // Selects compare file await click( - `${compareFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` + `${compareFileSelector} [data-test-fileOverview-selectCheckBox]` ); assert @@ -209,7 +209,7 @@ module('Integration | Component | file-compare/compare-list', function (hooks) { // Unselects compare file await click( - `${compareFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` + `${compareFileSelector} [data-test-fileOverview-selectCheckBox]` ); assert diff --git a/tests/integration/components/file-list-test.js b/tests/integration/components/file-list-test.js index d1b7e4b18..4c7b578db 100644 --- a/tests/integration/components/file-list-test.js +++ b/tests/integration/components/file-list-test.js @@ -159,14 +159,11 @@ module('Integration | Component | file-list', function (hooks) { .containsText(this.fileRecords[0].versionCode); assert - .dom('[data-test-fileCompare-fileOverview-selectCheckBox]', fileOverview1) + .dom('[data-test-fileOverview-selectCheckBox]', fileOverview1) .exists(); assert - .dom( - '[data-test-fileCompare-fileOverview-openInNewTabLink]', - fileOverview1 - ) + .dom('[data-test-fileOverview-openInNewTabLink]', fileOverview1) .exists(); }); @@ -183,9 +180,7 @@ module('Integration | Component | file-list', function (hooks) { const compareFileSelector = `[data-test-fileList-fileOverview='${compareFile.id}']`; // Selects only base file - await click( - `${baseFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` - ); + await click(`${baseFileSelector} [data-test-fileOverview-selectCheckBox]`); assert .dom('[data-test-fileList-projectOverview-header-noSelectedFileText]') @@ -242,7 +237,7 @@ module('Integration | Component | file-list', function (hooks) { // Selects compare file await click( - `${compareFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` + `${compareFileSelector} [data-test-fileOverview-selectCheckBox]` ); assert @@ -252,9 +247,7 @@ module('Integration | Component | file-list', function (hooks) { .doesNotExist(); // Unselects base file - await click( - `${baseFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` - ); + await click(`${baseFileSelector} [data-test-fileOverview-selectCheckBox]`); assert .dom( @@ -271,7 +264,7 @@ module('Integration | Component | file-list', function (hooks) { // Unselects compare file await click( - `${compareFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` + `${compareFileSelector} [data-test-fileOverview-selectCheckBox]` ); assert @@ -300,18 +293,14 @@ module('Integration | Component | file-list', function (hooks) { ); // Selects base and compare files - await click( - `${baseFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` - ); + await click(`${baseFileSelector} [data-test-fileOverview-selectCheckBox]`); await click( - `${compareFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` + `${compareFileSelector} [data-test-fileOverview-selectCheckBox]` ); assert - .dom( - `${otherFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` - ) + .dom(`${otherFileSelector} [data-test-fileOverview-selectCheckBox]`) .exists() .hasAttribute('disabled'); @@ -321,9 +310,7 @@ module('Integration | Component | file-list', function (hooks) { ); assert - .dom( - `${otherFileSelector} [data-test-fileCompare-fileOverview-selectCheckBox]` - ) + .dom(`${otherFileSelector} [data-test-fileOverview-selectCheckBox]`) .exists() .doesNotHaveAttribute('disabled'); }); diff --git a/tests/integration/components/file-compare/file-overview-test.js b/tests/integration/components/file-overview-test.js similarity index 67% rename from tests/integration/components/file-compare/file-overview-test.js rename to tests/integration/components/file-overview-test.js index d4cc25a35..b92d77d44 100644 --- a/tests/integration/components/file-compare/file-overview-test.js +++ b/tests/integration/components/file-overview-test.js @@ -22,24 +22,28 @@ module( this.server.create('analysis', { vulnerability: v.id }).toJSON() ); + // Profile Model + const profile = this.server.create('profile'); + + const normalizedProfile = this.store.normalize( + 'profile', + profile.toJSON() + ); + + const profileModel = this.store.push(normalizedProfile); + // File Model const file = this.server.create('file'); + const normalizedFile = this.store.normalize('file', { ...file.toJSON(), project: project.id, + profile: profile.id, analyses, tags, }); const fileModel = this.store.push(normalizedFile); - // Profile Model - const profile = this.server.create('profile'); - const normalizedProfile = this.store.normalize( - 'profile', - profile.toJSON() - ); - const profileModel = this.store.push(normalizedProfile); - // Common test props this.setProperties({ file: fileModel, @@ -61,66 +65,67 @@ module( test('it renders', async function (assert) { await render( - hbs`` + hbs`` ); - assert.dom('[data-test-fileCompare-fileOverview-root]').exists(); - assert.dom('[data-test-fileCompare-fileOverview-header]').exists(); + assert.dom('[data-test-fileOverview-root]').exists(); + assert.dom('[data-test-fileOverview-header]').exists(); - const fileIcon = find('[data-test-fileCompare-fileOverview-iconUrl]'); + const fileIcon = find('[data-test-fileOverview-iconUrl]'); assert.strictEqual(fileIcon?.getAttribute('src'), this.file?.iconUrl); assert - .dom('[data-test-fileCompare-fileOverview-fileName]') + .dom('[data-test-fileOverview-fileName]') .exists() .hasText(this.file.name); assert - .dom('[data-test-fileCompare-fileOverview-packageName]') + .dom('[data-test-fileOverview-packageName]') .exists() .hasText(`${this.file.project.get('packageName')}`); - assert - .dom('[data-test-fileCompare-fileOverview-selectCheckBox]') - .exists(); + assert.dom('[data-test-fileOverview-selectCheckBox]').exists(); assert - .dom('[data-test-fileCompare-fileOverview-openInNewTabLink]') + .dom('[data-test-fileOverview-openInNewTabLink]') .exists() .hasAttribute('href', new RegExp(this.file.id)) .hasAttribute('target', '_blank'); - assert.dom('[data-test-fileCompare-fileOverview-icon]').exists(); + assert.dom('[data-test-fileOverview-icon]').exists(); assert - .dom('[data-test-fileCompare-fileOverview-version]') + .dom('[data-test-fileOverview-version]') .exists() .containsText('t:version:()') .containsText(this.file.version); assert - .dom('[data-test-fileCompare-fileOverview-versionCode]') + .dom('[data-test-fileOverview-versionCodeText]') + .exists() + .containsText('t:versionCodeTitleCase:() -'); + + assert + .dom('[data-test-fileOverview-versionCode]') .exists() - .containsText('T:versionCode:()') .containsText(this.file.versionCode); - assert.dom('[data-test-fileCompare-fileOverview-scanStatuses]').exists(); + assert.dom('[data-test-fileOverview-scanStatuses]').exists(); assert - .dom('[data-test-fileCompare-fileOverview-fileID]') - .containsText('t:fileID:()') - .containsText(this.file.id); + .dom('[data-test-fileOverview-fileID-text]') + .containsText('t:fileID:() -'); - assert.dom('[data-test-fileCompare-fileOverview-platformIcon]').exists(); + assert.dom('[data-test-fileOverview-fileID]').containsText(this.file.id); + + assert.dom('[data-test-fileOverview-platformIcon]').exists(); assert - .dom( - '[ data-test-fileCompare-fileOverview-severityCountChartAndValues]' - ) + .dom('[ data-test-fileOverview-severityCountChartAndValues]') .exists(); - assert.dom('[data-test-fileCompare-fileOverview-chart]').exists(); + assert.dom('[data-test-fileOverview-chart]').exists(); // Chart legend data was formulated from the file severity level counts const severityValues = [ @@ -184,11 +189,11 @@ module( }); // Tags test - assert.dom('[data-test-fileCompare-fileOverview-tags]').exists(); + assert.dom('[data-test-fileOverview-tags]').exists(); this.file.tags.forEach((tag) => { assert - .dom(`[data-test-fileCompare-fileOverview-tag='${tag.name}']`) + .dom(`[data-test-fileOverview-tag='${tag.name}']`) .exists() .hasText(tag.name); }); @@ -199,7 +204,7 @@ module( this.set('hideOpenInNewTabIcon', false); await render( - hbs`` ); - assert - .dom('[data-test-fileCompare-fileOverview-selectCheckBox]') - .doesNotExist(); + assert.dom('[data-test-fileOverview-selectCheckBox]').doesNotExist(); - assert - .dom('[data-test-fileCompare-fileOverview-openInNewTabLink]') - .doesNotExist(); + assert.dom('[data-test-fileOverview-openInNewTabLink]').doesNotExist(); this.set('hideCTAs', false); - assert - .dom('[data-test-fileCompare-fileOverview-selectCheckBox]') - .exists(); + assert.dom('[data-test-fileOverview-selectCheckBox]').exists(); - assert - .dom('[data-test-fileCompare-fileOverview-openInNewTabLink]') - .exists(); + assert.dom('[data-test-fileOverview-openInNewTabLink]').exists(); this.set('hideOpenInNewTabIcon', true); - assert - .dom('[data-test-fileCompare-fileOverview-selectCheckBox]') - .exists(); + assert.dom('[data-test-fileOverview-selectCheckBox]').exists(); - assert - .dom('[data-test-fileCompare-fileOverview-openInNewTabLink]') - .doesNotExist(); + assert.dom('[data-test-fileOverview-openInNewTabLink]').doesNotExist(); }); test('it checks and unchecks the overview select checkbox', async function (assert) { @@ -245,7 +238,7 @@ module( }); await render( - hbs`` + hbs`` ); - assert - .dom('[data-test-fileCompare-fileOverview-fileInactiveIndicator]') - .exists(); + assert.dom('[data-test-fileOverview-fileInactiveIndicator]').exists(); - const tooltipSelector = - '[data-test-fileCompare-fileOverview-fileInactive-tooltip]'; + const tooltipSelector = '[data-test-fileOverview-fileInactive-tooltip]'; const tooltipContentSelector = '[data-test-ak-tooltip-content]'; const fileInactiveTooltip = find(tooltipSelector); @@ -308,7 +296,7 @@ module( this.file.isActive = false; await render( - hbs`` + hbs`` ); // All scan statuses except manual scan @@ -329,42 +317,40 @@ module( scanStatuses.forEach((status) => { assert - .dom( - `[data-test-fileCompare-fileOverview-scanStatuses='${status.name}']` - ) + .dom(`[data-test-fileOverview-scanStatuses='${status.name}']`) .exists(); const iconName = status.isDone ? /check-circle/ : /circle/; assert .dom( - `[data-test-fileCompare-fileOverview-scanStatuses='${status.name}'] [data-test-fileCompare-fileOverview-scanStatus-icon]` + `[data-test-fileOverview-scanStatuses='${status.name}'] [data-test-fileOverview-scanStatus-icon]` ) .hasClass(iconName); }); // Test for manual scan status const manualScanContainerSelector = - '[data-test-fileCompare-fileOverview-manualScanStatus]'; + '[data-test-fileOverview-manualScanStatus]'; assert.dom(manualScanContainerSelector).exists(); if (this.file.isManualDone) { assert .dom( - `${manualScanContainerSelector} [data-test-fileCompare-fileOverview-manualScanStatus-doneIcon]` + `${manualScanContainerSelector} [data-test-fileOverview-manualScanStatus-doneIcon]` ) .hasClass(/check-circle/); } else { assert .dom( - `${manualScanContainerSelector} [data-test-fileCompare-fileOverview-manualScanStatus-requestedPendingIcon]` + `${manualScanContainerSelector} [data-test-fileOverview-manualScanStatus-requestedPendingIcon]` ) .hasClass(this.file.isManualRequested ? /timer/ : /circle/); } assert - .dom('[data-test-fileCompare-fileOverview-manualScanStatus-name]') + .dom('[data-test-fileOverview-manualScanStatus-name]') .hasText('t:manual:()'); }); @@ -377,30 +363,26 @@ module( }); await render( - hbs`` + hbs`` ); const legendContainer = - "[data-test-fileCompare-fileOverview-chartSeverity='t:untested:()']"; + "[data-test-fileOverview-chartSeverity='t:untested:()']"; assert.dom(legendContainer).doesNotExist(); assert .dom( - `${legendContainer} [data-test-fileCompare-fileOverview-chartSeverity-colorIndicator]` + `${legendContainer} [data-test-fileOverview-chartSeverity-colorIndicator]` ) .doesNotExist(); assert - .dom( - `${legendContainer} [data-test-fileCompare-fileOverview-chartSeverityTitle]` - ) + .dom(`${legendContainer} [data-test-fileOverview-chartSeverityTitle]`) .doesNotExist(); assert - .dom( - `${legendContainer} [data-test-fileCompare-fileOverview-chartSeverityCount]` - ) + .dom(`${legendContainer} [data-test-fileOverview-chartSeverityCount]`) .doesNotExist(); }); @@ -409,12 +391,12 @@ module( await render( hbs` - + ` ); assert - .dom('[data-test-fileCompare-fileOverview-tags-empty]') + .dom('[data-test-fileOverview-tags-empty]') .exists() .hasText('t:fileCompare.noTagsMessage:()'); }); @@ -422,9 +404,9 @@ module( test('it renders yielded content', async function (assert) { await render( hbs` - + Button - ` + ` ); assert.dom('[data-test-yielded-content]').exists(); diff --git a/tests/integration/components/file-overview/component-test.js b/tests/integration/components/file-overview/component-test.js deleted file mode 100644 index 8b4aa9fdc..000000000 --- a/tests/integration/components/file-overview/component-test.js +++ /dev/null @@ -1,238 +0,0 @@ -import { find, findAll, render, triggerEvent } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; -import { setupMirage } from 'ember-cli-mirage/test-support'; -import { setupIntl } from 'ember-intl/test-support'; -import { setupRenderingTest } from 'ember-qunit'; -import { faker } from '@faker-js/faker'; -import { module, test } from 'qunit'; - -module('Integration | Component | file-overview', function (hooks) { - setupRenderingTest(hooks); - setupMirage(hooks); - setupIntl(hooks); - - hooks.beforeEach(async function () { - this.store = this.owner.lookup('service:store'); - this.project = this.store.createRecord('project', { - id: 1, - isManualScanAvailable: true, - name: faker.company.name(), - packageName: 'MFVA', - }); - - this.file = this.store.createRecord('file', { - id: 1, - project: this.project, - name: faker.company.name(), - iconUrl: - 'https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/315.jpg', - version: faker.number.int(), - versionCode: faker.number.int(), - }); - - this.server.create('profile'); - - this.server.get('/profiles/:id/unknown_analysis_status', () => { - return { - id: 1, - status: true, - }; - }); - }); - - test('it renders with yielded content', async function (assert) { - await render( - hbs` - -
- Yielded Content -
-
` - ); - - assert - .dom(`[data-test-yielded-content]`) - .exists() - .containsText('Yielded Content'); - }); - - test('it renders and displays the right content ', async function (assert) { - await render( - hbs` - ` - ); - - assert.dom(`[data-test-file-overview-container]`).exists(); - - // File name - assert - .dom(`[data-test-file-overview-file-name]`) - .exists() - .containsText(this.file.name); - - // Package name - assert - .dom(`[data-test-file-overview-package-name]`) - .exists() - .containsText(this.file.project.get('packageName')); - - // File Version - assert - .dom(`[data-test-file-overview-version]`) - .exists() - .includesText(this.file.version) - .includesText(this.file.versionCode); - - // File ID - assert - .dom(`[data-test-file-overview-file-id]`) - .exists() - .includesText(this.file.id); - - // File creation date - assert - .dom(`[data-test-file-overview-date-created]`) - .exists() - .includesText(`t:started:()`); - - // File critical risk count/levels - assert - .dom(`[data-test-critical-risk-count]`) - .exists() - .hasText(`${this.file.countRiskCritical}`); - - assert - .dom(`[data-test-count-risk-high]`) - .exists() - .includesText(this.file.countRiskHigh); - - assert - .dom(`[data-test-count-risk-medium]`) - .exists() - .includesText(this.file.countRiskMedium); - - assert - .dom(`[data-test-count-risk-low]`) - .exists() - .includesText(this.file.countRiskLow); - - assert - .dom(`[data-test-count-risk-none]`) - .exists() - .includesText(this.file.countRiskNone); - - assert - .dom(`[data-test-count-risk-unknown]`) - .exists() - .includesText(this.file.countRiskUnknown); - - // Status tags - assert.dom(`[data-test-static-scan-status-tag]`).exists(); - assert - .dom(`[data-test-static-scan-status-tag-label]`) - .exists() - .containsText('t:static:()'); - assert.dom(`[data-test-api-scan-status-tag]`).exists(); - assert - .dom(`[data-test-api-scan-status-tag-label]`) - .exists() - .containsText('t:api:()'); - assert.dom(`[data-test-manual-scan-status-tag]`).exists(); - assert - .dom(`[data-test-manual-scan-status-tag-label]`) - .exists() - .containsText('t:manual:()'); - assert.dom(`[data-test-dynamic-scan-status-tag]`).exists(); - assert - .dom(`[data-test-dynamic-scan-status-tag-label]`) - .exists() - .containsText('t:dynamic:()'); - - // Platform Icon - assert.dom(`[data-test-file-overview-platform-icon]`).exists(); - const platformIconElementClass = this.element.querySelector( - `[data-test-file-overview-platform-icon]` - )?.className; - - assert.ok( - platformIconElementClass?.includes( - this.file.project.get('platformIconClass') - ), - 'Contains the right platform icon class.' - ); - - // File Icon - const fileIconImageElement = this.element.querySelector( - `[data-test-file-overview-icon-url]` - ); - assert.strictEqual( - fileIconImageElement.src, - this.file.iconUrl, - 'Displays the correct file icon URL.' - ); - }); - - test('It shows inactive icon when file is inactive ', async function (assert) { - this.file.isActive = false; - await render( - hbs` - ` - ); - - assert.dom(`[data-test-file-overview-file-inactive-icon]`).exists(); - - const fileInactiveTooltip = find( - '[data-test-file-overview-file-id] [data-test-file-overview-file-inactive-tooltip]' - ); - - await triggerEvent(fileInactiveTooltip, 'mouseenter'); - - assert.dom('[data-test-ak-tooltip-content]').hasText('t:fileInactive:()'); - - this.file.isActive = true; - - await render( - hbs` - ` - ); - - assert.dom('[data-test-file-overview-file-inactive-icon]').doesNotExist(); - }); - - test('It renders the right number of tags if available', async function (assert) { - let tags = []; - for (let i = 0; i < 2; i++) { - const tag = this.server.create('tag', { id: i }); - tags.push(this.store.push(this.store.normalize('tag', tag.toJSON()))); - } - - this.file.tags = tags; - this.file.isActive = true; - - await render( - hbs` - ` - ); - assert.dom('[data-test-file-tags]').exists(); - - const fileTags = findAll('[data-test-file-tag]'); - assert.strictEqual(fileTags.length, 2); - - tags.map((tag) => - assert.dom(`[data-test-tag="${tag.name}"]`).exists().hasText(tag.name) - ); - }); - - test('It hides manual scan tag if manual scan feature is disabled', async function (assert) { - this.project.isManualScanAvailable = false; - this.file.project = this.project; - - await render( - hbs` - ` - ); - - assert.dom(`[data-test-manual-scan-status-tag]`).doesNotExist(); - assert.dom(`[data-test-manual-scan-status-tag-label]`).doesNotExist(); - }); -}); diff --git a/tests/integration/components/project-list-test.js b/tests/integration/components/project-list-test.js index c056e928c..16cc7a3ae 100644 --- a/tests/integration/components/project-list-test.js +++ b/tests/integration/components/project-list-test.js @@ -1,6 +1,6 @@ import Service from '@ember/service'; import { isEmpty } from '@ember/utils'; -import { fillIn, find, findAll, render, select } from '@ember/test-helpers'; +import { fillIn, findAll, render, click } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupIntl } from 'ember-intl/test-support'; @@ -55,15 +55,11 @@ module('Integration | Component | project list', function (hooks) { assert .dom('[data-test-no-project-header]') - .hasTextContaining('t:noProject:()'); + .hasTextContaining('t:uploadAnApp:()'); assert - .dom('[data-test-no-project-uploaded-text]') - .hasTextContaining('t:noProjectUploaded:()'); - - assert - .dom('[data-test-upload-new-project-text]') - .hasTextContaining('t:uploadNewProject:()'); + .dom('[data-test-no-project-text]') + .hasTextContaining('t:noProjectExists:()'); }); test('It renders successfully with at least one project', async function (assert) { @@ -141,10 +137,10 @@ module('Integration | Component | project list', function (hooks) { 'Team list length is correct.' ); - assert.dom(teamSelectOptions[0]).hasText('All'); + assert.dom(teamSelectOptions[0]).containsText('All'); teams.forEach((t, i) => { - assert.dom(teamSelectOptions[i + 1]).hasText(t.name); + assert.dom(teamSelectOptions[i + 1]).containsText(t.name); }); }); @@ -220,14 +216,14 @@ module('Integration | Component | project list', function (hooks) { test.each( 'it renders with correct sortBy selected', [ - ['-last_file_created_on', 't:dateUpdated:() t:mostRecent:()'], - ['last_file_created_on', 't:dateUpdated:() t:leastRecent:()'], - ['-id', 't:dateCreated:() t:mostRecent:()'], - ['id', 't:dateCreated:() t:leastRecent:()'], - ['-package_name', 't:packageName:() (Z -> A)'], - ['package_name', 't:packageName:() (A -> Z)'], + ['t:dateUpdated:() t:mostRecent:()', 0], + ['t:dateUpdated:() t:leastRecent:()', 1], + ['t:dateCreated:() t:mostRecent:()', 2], + ['t:dateCreated:() t:leastRecent:()', 3], + ['t:packageName:() (Z -> A)', 4], + ['t:packageName:() (A -> Z)', 5], ], - async function (assert, [sortKey, sortLabel]) { + async function (assert, [sortLabel, index]) { const projects = this.server.createList('project', 4); this.server.get('/organizations/:id/projects', (schema) => { @@ -251,16 +247,15 @@ module('Integration | Component | project list', function (hooks) { assert.strictEqual(projects.length, projectContainerList.length); - await select('[data-test-project-sort-property]', sortKey); - - assert.dom('[data-test-project-sort-property]').hasValue(sortKey); + await clickTrigger('[data-test-project-sort-property]'); - const sortOption = find( - `[data-test-project-sort-property-option=${sortKey}]` + await selectChoose( + '.select-sort-class', + '.ember-power-select-option', + index ); - assert.true(sortOption.selected); - assert.dom(sortOption).hasText(sortLabel); + assert.dom('[data-test-project-sort-property]').containsText(sortLabel); } ); @@ -351,41 +346,58 @@ module('Integration | Component | project list', function (hooks) { await render(hbs``); - const platformOptions = findAll('[data-test-platform-filter-option]'); + let projectContainerList = findAll( + '[data-test-project-overview-container]' + ); - // Selecting a platform value equal to 0 from the plaform filter options - await select('[data-test-platform-filter]', platformOptions[1].value); + assert.strictEqual( + projectContainerList.length, + projects.length, + 'Contains correct number of project overview cards.' + ); - assert.strictEqual(this.platform, platformOptions[1].value); + await clickTrigger('[data-test-select-platform-container]'); - let projectContainerList = findAll( - '[data-test-project-overview-container]' + await selectChoose( + '.select-platform-class', + '.ember-power-select-option', + 1 ); + assert.strictEqual(this.platform, '0'); + + projectContainerList = findAll('[data-test-project-overview-container]'); + assert.strictEqual( - projects.filter((p) => p.platform === parseInt(platformOptions[1].value)) - .length, + projects.filter((p) => p.platform === 0).length, projectContainerList.length, 'Project list items all have platform values matching "0".' ); // Selecting a platform value equal to 1 from the plaform filter options - await select('[data-test-platform-filter]', platformOptions[2].value); + await selectChoose( + '.select-platform-class', + '.ember-power-select-option', + 2 + ); - assert.strictEqual(this.platform, platformOptions[2].value); + assert.strictEqual(this.platform, '1'); projectContainerList = findAll('[data-test-project-overview-container]'); assert.strictEqual( - projects.filter((p) => p.platform === parseInt(platformOptions[2].value)) - .length, + projects.filter((p) => p.platform === 1).length, projectContainerList.length, 'Project list items all have platform values matching "1".' ); // Selecting a platform value equal to -1 from the plaform filter options // This should return the entire project list - await select('[data-test-platform-filter]', platformOptions[0].value); + await selectChoose( + '.select-platform-class', + '.ember-power-select-option', + 0 + ); assert.strictEqual(typeof this.platform, 'undefined'); @@ -397,4 +409,83 @@ module('Integration | Component | project list', function (hooks) { 'Project list defaults to complete list when platform value is "-1".' ); }); + + test('It clears filter after filter is applied', async function (assert) { + // Creating project list with atleast 1 item having platform value of 0 + const projects = Array.from(new Array(8)).map((_, i) => { + return this.server.create('project', { + platform: i === 2 ? 0 : faker.helpers.arrayElement([0, 1]), + }); + }); + + this.server.get('/organizations/:id/projects', (schema, req) => { + const platform = req.queryParams.platform; + + this.set('platform', platform); + + const results = + !isEmpty(platform) && parseInt(platform) !== -1 + ? schema.projects.where((p) => p.platform === parseInt(platform)) + .models + : schema.projects.all().models; + + return { count: results.length, next: null, previous: null, results }; + }); + + this.server.get('/profiles/:id/unknown_analysis_status', (schema, req) => { + return { id: req.params.id, status: faker.datatype.boolean() }; + }); + + await render(hbs``); + + assert.dom('[data-test-project-list-header-clear-filter]').doesNotExist(); + + await clickTrigger('[data-test-select-platform-container]'); + + await selectChoose( + '.select-platform-class', + '.ember-power-select-option', + 1 + ); + + assert.dom('[data-test-project-list-header-clear-filter]').exists(); + + // Clear Filter + await click('[data-test-project-list-header-clear-filter]'); + + let projectContainerList = findAll( + '[data-test-project-overview-container]' + ); + + assert.strictEqual( + projects.length, + projectContainerList.length, + 'Project list defaults to complete list when platform value is "-1".' + ); + + assert.dom('[data-test-project-list-header-clear-filter]').doesNotExist(); + + await clickTrigger('[data-test-select-platform-container]'); + + await selectChoose( + '.select-platform-class', + '.ember-power-select-option', + 1 + ); + + await clickTrigger('[data-test-select-team-container]'); + + // Select third team in power select dropdown + await selectChoose('.select-team-class', '.ember-power-select-option', 2); + + assert.dom('[data-test-project-list-header-clear-filter]').exists(); + + //change again to all and still it exists as platform filter is still applied + await clickTrigger('[data-test-select-team-container]'); + + // Select third team in power select dropdown + await selectChoose('.select-team-class', '.ember-power-select-option', 0); + + assert.dom('[data-test-project-list-header-clear-filter]').exists(); + }); }); diff --git a/translations/en.json b/translations/en.json index 2d34c9591..d708209ce 100644 --- a/translations/en.json +++ b/translations/en.json @@ -48,6 +48,7 @@ "allFutureAnalyses": "All Future Analyses", "allPlatforms": "All Platforms", "allProjects": "All Projects", + "allProjectsDescription": "Here you can view all the apps that have been uploaded to the dashboard.", "allScans": "All Scans", "allUploads": "All Uploads", "allowEdit": "Allow Edit", @@ -130,6 +131,7 @@ "secure": "SECURE" }, "circleCIPipeline": "Circle CI Pipeline", + "clearFilter": "Clear Filter", "clickOnDynamicScan": "Click on Start Dynamic Scan", "clickHere": "Click here", "clickingHere": "clicking here", @@ -365,7 +367,7 @@ "summary1": "You are comparing file of ID", "summary2": "with file of ID", "nameOfTestCase": "Name of Test Case", - "noTagsMessage": "No tags available.", + "noTagsMessage": "No Tags Available", "selectAFile": "Select a File", "selectCompareFile": "Select One More File ID", "fileSelection": "File Selection", @@ -724,6 +726,7 @@ "noPersonalToken": "no tokens found", "noPreference": "No Preference", "noProject": "No projects", + "noProjectExists": "No Projects/Apps exist. Please upload an app to initiate the vulnerability assessment.", "noProjectUploaded": "You haven't uploaded any project yet", "noReportsGenerated": "No reports generated yet", "noReportExists": "{format} doesn't exists, please generate and try again", @@ -1283,7 +1286,8 @@ "update": "Update", "upload": "Upload", "uploadApp": "Upload App", - "uploadAppModule": { + "uploadAnApp": "Upload an App", + "uploadAppModule":{ "linkPastePlaceholder": "Paste the link here...", "viaLink": "Via Link", "supportedStores": "Supported Stores", @@ -1322,6 +1326,7 @@ "versionCode": "version code", "viaLink": "VIA LINK", "viaSystem": "VIA SYSTEM", + "versionCodeTitleCase": "Version Code", "viewAll": "View All", "viewDetails": "View Details", "viewFile": "View file", diff --git a/translations/ja.json b/translations/ja.json index a3551c375..d33d28665 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -48,6 +48,7 @@ "allFutureAnalyses": "All Future Analyses", "allPlatforms": "全プラットフォーム", "allProjects": "All Projects", + "allProjectsDescription": "Here you can view all the apps that have been uploaded to the dashboard.", "allScans": "全診断", "allUploads": "全アップロード", "allowEdit": "Allow Edit", @@ -130,6 +131,7 @@ "secure": "SECURE" }, "circleCIPipeline": "Circle CI Pipeline", + "clearFilter": "Clear Filter", "clickOnDynamicScan": "動的診断の開始をクリック", "clickHere": "Click here", "clickingHere": "clicking here", @@ -365,7 +367,7 @@ "summary1": "You are comparing file of ID", "summary2": "with file of ID", "nameOfTestCase": "Name of Test Case", - "noTagsMessage": "No tags available.", + "noTagsMessage": "No Tags Available", "selectAFile": "Select a File", "selectCompareFile": "Select One More File ID", "fileSelection": "File Selection", @@ -724,6 +726,7 @@ "noPersonalToken": "トークンが見つかりません", "noPreference": "指定なし", "noProject": "プロジェクトがありません", + "noProjectExists": "No Projects/Apps exist. Please upload an app to initiate the vulnerability assessment.", "noProjectUploaded": "プロジェクトがアップロードされていません", "noReportsGenerated": "No reports generated yet", "noReportExists": "{format} doesn't exists, please generate and try again", @@ -1282,7 +1285,8 @@ "update": "Update", "upload": "Upload", "uploadApp": "アプリのアップロード", - "uploadAppModule": { + "uploadAnApp": "Upload an App", + "uploadAppModule":{ "linkPastePlaceholder": "Paste the link here...", "viaLink": "Via Link", "supportedStores": "Supported Stores", @@ -1321,6 +1325,7 @@ "versionCode": "バージョンコード", "viaLink": "VIA LINK", "viaSystem": "VIA SYSTEM", + "versionCodeTitleCase": "Version Code", "viewAll": "View All", "viewDetails": "View Details", "viewFile": "View file", diff --git a/types/ak-svg.d.ts b/types/ak-svg.d.ts index 98c4ea7ab..e459a81d7 100644 --- a/types/ak-svg.d.ts +++ b/types/ak-svg.d.ts @@ -32,6 +32,7 @@ export enum AkSvgComponentInvocationByNames { AppstoreLogo, PlaystoreLogo, NoParameterData, + ProjectListEmpty, } export enum AkSvgComponentInvocationByPaths {