@@ -9,11 +9,18 @@
{{#if this.skInventoryApp.appIsNotAvailableOnAppknox}}
<:icon>
-
+
<:default>
-
+
{{t 'storeknox.appNotPartOfAppknox'}}
@@ -29,6 +36,7 @@
local-class='app-info-logo'
src={{@skInventoryApp.appMetadata.iconUrl}}
alt={{@skInventoryApp.appMetadata.title}}
+ data-test-storeknoxInventoryDetails-appLogoImage
/>
{{@skInventoryApp.appMetadata.title}}
-
+
{{@skInventoryApp.appMetadata.packageName}}
@@ -48,6 +61,7 @@
@@ -56,15 +70,22 @@
@storeLink={{@skInventoryApp.appMetadata.url}}
>
{{#if this.skInventoryApp.isAndroid}}
-
+
+
{{else}}
-
+
{{/if}}
{{#unless this.skInventoryApp.appIsNotAvailableOnAppknox}}
-
+
{{/unless}}
@@ -89,6 +110,7 @@
@label={{this.activeRouteTagProps.label}}
@hideRightIcon={{true}}
{{style height='32px' border='none'}}
+ data-test-storeknoxInventoryDetails-pageInfoTag
/>
{{else}}
@@ -106,11 +128,15 @@
this.toggleSkInventoryAppMonitoring.isRunning
}}
{{style margin='0em'}}
+ data-test-storeknoxInventoryDetails-monitoringStatusToggle
/>
-
- Monitoring Action
+
+ {{t 'storeknox.monitoringAction'}}
{{/if}}
diff --git a/app/components/storeknox/inventory-details/header/index.ts b/app/components/storeknox/inventory-details/header/index.ts
index a6637c59a..531d42f78 100644
--- a/app/components/storeknox/inventory-details/header/index.ts
+++ b/app/components/storeknox/inventory-details/header/index.ts
@@ -6,7 +6,6 @@ import type IntlService from 'ember-intl/services/intl';
import type RouterService from '@ember/routing/router-service';
import type Store from '@ember-data/store';
-import ENUMS from 'irene/enums';
import parseError from 'irene/utils/parse-error';
import type SkInventoryAppModel from 'irene/models/sk-inventory-app';
import type OrganizationService from 'irene/services/organization';
@@ -71,18 +70,15 @@ export default class StoreknoxInventoryDetailsHeaderComponent extends Component<
get routeTagList() {
const skInventoryAppId = this.skInventoryApp?.id as string;
- const appCoreProjectLatestVersionId = this.appCoreProjectLatestVersion?.id;
return [
{
id: 'unscanned-version',
disabled: false,
label: this.intl.t('storeknox.unscannedVersion'),
- needsAction:
- this.skInventoryApp?.storeMonitoringStatus ===
- ENUMS.SK_APP_MONITORING_STATUS.UNSCANNED,
+ needsAction: this.skInventoryApp?.containsUnscannedVersion,
route: 'authenticated.storeknox.inventory-details.unscanned-version',
- models: [skInventoryAppId, appCoreProjectLatestVersionId as string],
+ models: [skInventoryAppId],
},
{
id: 'brand-abuse',
@@ -115,18 +111,15 @@ export default class StoreknoxInventoryDetailsHeaderComponent extends Component<
toggleSkInventoryAppMonitoring = task(async (checked: boolean) => {
try {
- this.skInventoryApp?.set('monitoringEnabled', checked);
-
await this.skInventoryApp?.toggleMonitoring(checked);
await this.skInventoryApp?.reload();
this.notify.success(
- 'Monitoring ' + `${checked ? 'Enabled' : 'Disabled'}`
+ this.intl.t('storeknox.monitoring') +
+ ` ${checked ? this.intl.t('enabled') : this.intl.t('disabled')}`
);
} catch (error) {
this.notify.error(parseError(error));
-
- this.skInventoryApp?.rollbackAttributes();
}
});
}
diff --git a/app/components/storeknox/inventory-details/malware-detected/index.hbs b/app/components/storeknox/inventory-details/malware-detected/index.hbs
index af450c3db..c6b1292ce 100644
--- a/app/components/storeknox/inventory-details/malware-detected/index.hbs
+++ b/app/components/storeknox/inventory-details/malware-detected/index.hbs
@@ -1,9 +1,11 @@
<:illustration>
-
+
diff --git a/app/components/storeknox/inventory-details/section-header/index.scss b/app/components/storeknox/inventory-details/section-header/index.scss
index 7a18de81a..ea17620ea 100644
--- a/app/components/storeknox/inventory-details/section-header/index.scss
+++ b/app/components/storeknox/inventory-details/section-header/index.scss
@@ -1,9 +1,10 @@
.storeknox-inventory-details-section-header-root {
height: 40px;
- border-bottom: 0px;
border: 1px solid
var(--storeknox-inventory-details-section-header-root-border-color);
+ border-bottom: 0px;
+
&.default {
background: var(
--storeknox-inventory-details-section-header-default-bg-color
diff --git a/app/components/storeknox/inventory/app-list/index.hbs b/app/components/storeknox/inventory/app-list/index.hbs
index 8194613a0..ba37b5244 100644
--- a/app/components/storeknox/inventory/app-list/index.hbs
+++ b/app/components/storeknox/inventory/app-list/index.hbs
@@ -19,5 +19,5 @@
--}}
-
+
\ No newline at end of file
diff --git a/app/components/storeknox/inventory/app-list/index.ts b/app/components/storeknox/inventory/app-list/index.ts
index 626e384c0..4f3cd2e1d 100644
--- a/app/components/storeknox/inventory/app-list/index.ts
+++ b/app/components/storeknox/inventory/app-list/index.ts
@@ -2,15 +2,7 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
-import { type StoreknoxInventoryAppListQueryParams } from 'irene/routes/authenticated/storeknox/inventory/app-list';
-
-export interface StoreknoxInventoryAppListSignature {
- Args: {
- queryParams: StoreknoxInventoryAppListQueryParams;
- };
-}
-
-export default class StoreknoxInventoryAppListComponent extends Component {
+export default class StoreknoxInventoryAppListComponent extends Component {
@tracked searchQuery = '';
@tracked discoverClicked = false;
diff --git a/app/components/storeknox/inventory/app-list/table/index.hbs b/app/components/storeknox/inventory/app-list/table/index.hbs
index b6b2d0867..8e92b61f3 100644
--- a/app/components/storeknox/inventory/app-list/table/index.hbs
+++ b/app/components/storeknox/inventory/app-list/table/index.hbs
@@ -1,69 +1,101 @@
-
-
-
-
-
- {{#if column.headerComponent}}
- {{#let (component column.headerComponent) as |Component|}}
-
- {{/let}}
- {{else}}
- {{column.name}}
- {{/if}}
-
-
-
+{{#if this.hasNoApps}}
+
+
-
-
-
- {{#let (component r.columnValue.cellComponent) as |Component|}}
-
- {{/let}}
-
-
-
-
+
+ {{t 'storeknox.noRequestedAppsFound'}}
+
- {{#unless this.isFetchingTableData}}
-
- {{/unless}}
-
\ No newline at end of file
+
+ {{t 'storeknox.noRequestedAppsFoundDescription' htmlSafe=true}}
+
+
+
+{{else}}
+
+
+
+
+
+ {{#if column.headerComponent}}
+ {{#let (component column.headerComponent) as |Component|}}
+
+ {{/let}}
+ {{else}}
+ {{column.name}}
+ {{/if}}
+
+
+
+
+
+
+
+ {{#let (component r.columnValue.cellComponent) as |Component|}}
+
+ {{/let}}
+
+
+
+
+
+ {{#unless this.isFetchingTableData}}
+
+ {{/unless}}
+
+{{/if}}
\ No newline at end of file
diff --git a/app/components/storeknox/inventory/app-list/table/index.scss b/app/components/storeknox/inventory/app-list/table/index.scss
new file mode 100644
index 000000000..1ff4af5ee
--- /dev/null
+++ b/app/components/storeknox/inventory/app-list/table/index.scss
@@ -0,0 +1,17 @@
+.empty-container {
+ padding-top: 5em;
+ padding-bottom: 5em;
+ border: 1px solid
+ var(--storeknox-discover-requested-apps-table-row-border-color);
+ background-color: var(--storeknox-discover-requested-apps-table-row-color);
+ border-top: 0;
+
+ .header {
+ margin-top: 1.5625em;
+ }
+
+ .body-text {
+ text-align: center;
+ max-width: 550px;
+ }
+}
diff --git a/app/components/storeknox/inventory/app-list/table/index.ts b/app/components/storeknox/inventory/app-list/table/index.ts
index 7ffa76c3e..68e031340 100644
--- a/app/components/storeknox/inventory/app-list/table/index.ts
+++ b/app/components/storeknox/inventory/app-list/table/index.ts
@@ -1,27 +1,17 @@
-// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
-import DS from 'ember-data';
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
-import { task } from 'ember-concurrency';
import type RouterService from '@ember/routing/router-service';
-import type { Owner } from '@ember/test-helpers/build-owner';
+import type IntlService from 'ember-intl/services/intl';
+import type Store from '@ember-data/store';
-import parseError from 'irene/utils/parse-error';
import ENUMS from 'irene/enums';
-import type IntlService from 'ember-intl/services/intl';
import type MeService from 'irene/services/me';
-import type Store from '@ember-data/store';
-import type SkInventoryAppModel from 'irene/models/sk-inventory-app';
import type { StoreknoxCommonTableColumnsData } from 'irene/components/storeknox/table-columns';
import type { PaginationProviderActionsArgs } from 'irene/components/ak-pagination-provider';
-import type { StoreknoxInventoryAppListQueryParams } from 'irene/routes/authenticated/storeknox/inventory/app-list';
-
-type SkAppsQueryResponse =
- DS.AdapterPopulatedRecordArray & {
- meta: { count: number };
- };
+import type SkInventoryAppModel from 'irene/models/sk-inventory-app';
+import type SkInventoryAppService from 'irene/services/sk-inventory-apps';
type StoreknoxInventoryTableDataItem = StoreknoxCommonTableColumnsData & {
appIsSelected: boolean;
@@ -33,7 +23,6 @@ export interface StoreknoxInventoryAppListTableSignature {
Args: {
isAddingAppToInventory?: boolean;
loadDisabledApps?: boolean;
- queryParams: StoreknoxInventoryAppListQueryParams;
handleSelectedDisabledApps?: (apps: string[]) => void;
};
}
@@ -45,27 +34,18 @@ export default class StoreknoxInventoryAppListTableComponent extends Component();
@tracked selectedDisabledAppIds: string[] = [];
- constructor(
- owner: Owner,
- args: StoreknoxInventoryAppListTableSignature['Args']
- ) {
- super(owner, args);
-
- const { app_limit, app_offset } = args.queryParams;
-
- this.fetchSkInventoryApps.perform(app_limit, app_offset, false);
- }
-
get loadDisabledApps() {
return this.args.loadDisabledApps;
}
get disableRowClick() {
- return this.loadDisabledApps || this.fetchSkInventoryApps.isRunning;
+ return this.loadDisabledApps || this.isFetchingTableData;
}
get columns() {
@@ -110,7 +90,11 @@ export default class StoreknoxInventoryAppListTableComponent extends Component {
+ return (this.skInventoryAppsService.skInventoryApps?.map((app) => {
const { appMetadata } = app;
return {
@@ -145,7 +129,7 @@ export default class StoreknoxInventoryAppListTableComponent extends Component {
- if (updateQueryParams) {
- this.updateRouteParams(limit, offset);
- }
-
- const query = !this.loadDisabledApps
- ? {
- approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED,
- app_status: ENUMS.SK_APP_STATUS.ACTIVE,
- }
- : {
- app_status: ENUMS.SK_APP_STATUS.INACTIVE,
- };
-
- try {
- const data = (await this.store.query('sk-app', {
- limit: limit,
- offset: offset,
- ...query,
- })) as SkAppsQueryResponse;
-
- this.skAppsResponse = data;
- } catch (error) {
- this.notify.error(parseError(error, this.intl.t('somethingWentWrong')));
- }
- }
- );
}
declare module '@glint/environment-ember-loose/registry' {
diff --git a/app/components/storeknox/inventory/app-list/table/monitoring-status/index.hbs b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.hbs
index e790d86f7..89f2e3696 100644
--- a/app/components/storeknox/inventory/app-list/table/monitoring-status/index.hbs
+++ b/app/components/storeknox/inventory/app-list/table/monitoring-status/index.hbs
@@ -6,16 +6,18 @@
>
<:tooltipContent>
-
-
- {{t 'reason'}}
-
-
+
+ {{t 'reason'}}
+
-
+
{{this.tooltipMessage}}
{{t 'storeknox.haveBeenDetected'}}
@@ -33,9 +35,12 @@
@iconName={{this.monitoringStatusIconDetails.name}}
@color={{this.monitoringStatusIconDetails.color}}
@size='small'
+ data-test-storeknoxInventory-appListTable-monitoringStatusIcon
/>
-
+
{{#if this.appIsDisabled}}
{{t 'disabled'}}
{{else if this.needsAction}}
diff --git a/app/components/storeknox/inventory/index.hbs b/app/components/storeknox/inventory/index.hbs
index 963b3b6f1..bf2f96f87 100644
--- a/app/components/storeknox/inventory/index.hbs
+++ b/app/components/storeknox/inventory/index.hbs
@@ -2,14 +2,21 @@
@direction='row'
@alignItems='center'
@justifyContent='space-between'
- local-class='header-storeknox-discover-page'
+ local-class='header-storeknox-inventory-page'
>
-
+
{{t 'storeknox.inventoryHeader'}}
-
+
{{t 'storeknox.inventoryDescription'}}
@@ -20,6 +27,7 @@
@underline='none'
@color='inherit'
@route='authenticated.storeknox.discover.result'
+ data-test-storeknoxInventory-discoveryPageLink
>
{{t 'storeknox.discoverHeader'}}
@@ -37,6 +45,7 @@
@size='medium'
@variant='outlined'
{{on 'click' this.openSettingsDrawer}}
+ data-test-storeknoxInventory-settingsDrawerTrigger
>
@@ -52,6 +61,7 @@
@route={{item.route}}
@hasBadge={{item.hasBadge}}
@badgeCount={{item.badgeCount}}
+ data-test-storeknoxInventory-tabs='{{item.id}}-tab'
>
{{item.label}}
@@ -75,7 +85,7 @@
@color='inherit'
@variant='h5'
@fontWeight='medium'
- data-test-addProjectTeam-drawerContainer-title
+ data-test-storeknoxInventory-settingsDrawer-title
>
{{t 'storeknox.inventorySettings'}}
@@ -83,7 +93,7 @@
@@ -96,11 +106,18 @@
class='px-3 py-4'
>
-
+
{{t 'storeknox.addAppknoxProjectsByDefault'}}
-
+
{{t 'storeknox.addAppknoxProjectsByDefaultToggleDescription'}}
@@ -109,6 +126,7 @@
@size='small'
@checked={{this.selectedSkOrg.addAppknoxProjectToInventoryByDefault}}
@onChange={{this.onToggleAddToInventoryByDefault}}
+ data-test-storeknoxInventory-settingsDrawer-addAppknoxProjectsByDefaultToggle
/>
diff --git a/app/components/storeknox/inventory/index.scss b/app/components/storeknox/inventory/index.scss
index 2301f9b45..df10e188d 100644
--- a/app/components/storeknox/inventory/index.scss
+++ b/app/components/storeknox/inventory/index.scss
@@ -1,4 +1,4 @@
-.header-storeknox-discover-page {
+.header-storeknox-inventory-page {
background-color: white;
margin: 0.714em 0;
border: 1px solid var(--storeknox-discover-header-border-color);
diff --git a/app/components/storeknox/inventory/index.ts b/app/components/storeknox/inventory/index.ts
index 68e359b00..d81b1a7f8 100644
--- a/app/components/storeknox/inventory/index.ts
+++ b/app/components/storeknox/inventory/index.ts
@@ -1,5 +1,3 @@
-// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
-import DS from 'ember-data';
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
@@ -8,17 +6,11 @@ import { task } from 'ember-concurrency';
import type Store from '@ember-data/store';
import type IntlService from 'ember-intl/services/intl';
-import ENUMS from 'irene/enums';
import parseError from 'irene/utils/parse-error';
import type SkPendingReviewService from 'irene/services/sk-pending-review';
import type MeService from 'irene/services/me';
-import type SkInventoryAppModel from 'irene/models/sk-inventory-app';
import type SkOrganizationModel from 'irene/models/sk-organization';
-
-type SkAppsQueryResponse =
- DS.AdapterPopulatedRecordArray & {
- meta: { count: number };
- };
+import type SkInventoryAppService from 'irene/services/sk-inventory-apps';
export default class StoreknoxInventoryComponent extends Component {
@service declare intl: IntlService;
@@ -27,22 +19,23 @@ export default class StoreknoxInventoryComponent extends Component {
@service declare skPendingReview: SkPendingReviewService;
@service('notifications') declare notify: NotificationService;
+ @service('sk-inventory-apps')
+ declare skInventoryAppsService: SkInventoryAppService;
+
@tracked selectedSkOrg: SkOrganizationModel | undefined;
@tracked showWelcomeModal = false;
@tracked showSettingsDrawer = false;
- @tracked totalInventoryAppsCount = 0;
@tracked totalDisabledAppsCount = 0;
constructor(owner: unknown, args: object) {
super(owner, args);
- this.getTabItemsCount.perform();
this.getSkOrganization.perform();
}
get isOwner() {
- return this.me.org?.is_owner;
+ return !!this.me.org?.is_owner;
}
get tabItems() {
@@ -51,15 +44,15 @@ export default class StoreknoxInventoryComponent extends Component {
id: 'app-inventory',
route: 'authenticated.storeknox.inventory.app-list',
label: this.intl.t('storeknox.appInventory'),
- hasBadge: this.totalInventoryAppsCount > 0,
- badgeCount: this.totalInventoryAppsCount,
+ hasBadge: this.skInventoryAppsService.skInventoryAppsCount > 0,
+ badgeCount: this.skInventoryAppsService.skInventoryAppsCount,
},
this.isOwner && {
id: 'pending-review',
route: 'authenticated.storeknox.inventory.pending-reviews',
label: this.intl.t('storeknox.pendingReview'),
- hasBadge: this.skPendingReview.totalCount > 0,
- badgeCount: this.skPendingReview.totalCount,
+ hasBadge: this.skPendingReview.skPendingReviewAppsCount > 0,
+ badgeCount: this.skPendingReview.skPendingReviewAppsCount,
},
// {
// id: 'disabled-apps',
@@ -88,21 +81,6 @@ export default class StoreknoxInventoryComponent extends Component {
this.toggleAddToInventoryByDefault.perform(checked);
}
- getTabItemsCount = task(async () => {
- // Fetches the inventory list items total count
- this.totalInventoryAppsCount = (
- (await this.store.query('sk-app', {
- approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED,
- app_status: ENUMS.SK_APP_STATUS.ACTIVE,
- })) as SkAppsQueryResponse
- ).meta.count;
-
- // Get Pending review data
- if (this.isOwner) {
- this.skPendingReview.fetchPendingReviewApps.perform(10, 0, false);
- }
- });
-
toggleAddToInventoryByDefault = task(async (checked?: boolean) => {
try {
const org =
diff --git a/app/components/storeknox/inventory/pending-review/empty/index.hbs b/app/components/storeknox/inventory/pending-review/empty/index.hbs
index c2be72cf3..b019e431e 100644
--- a/app/components/storeknox/inventory/pending-review/empty/index.hbs
+++ b/app/components/storeknox/inventory/pending-review/empty/index.hbs
@@ -1,11 +1,21 @@
-
+
-
+
{{t 'storeknox.noPendingItems'}}
-
+
{{t 'storeknox.noPendingItemsDescription'}}
\ No newline at end of file
diff --git a/app/components/storeknox/inventory/pending-review/index.hbs b/app/components/storeknox/inventory/pending-review/index.hbs
index 0361298cc..ff6c20e14 100644
--- a/app/components/storeknox/inventory/pending-review/index.hbs
+++ b/app/components/storeknox/inventory/pending-review/index.hbs
@@ -50,15 +50,13 @@
{{#if this.showTable}}
{{else}}
diff --git a/app/components/storeknox/inventory/pending-review/index.ts b/app/components/storeknox/inventory/pending-review/index.ts
index f28c2aec4..3c84ba63e 100644
--- a/app/components/storeknox/inventory/pending-review/index.ts
+++ b/app/components/storeknox/inventory/pending-review/index.ts
@@ -2,17 +2,11 @@ import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import type IntlService from 'ember-intl/services/intl';
+import type RouterService from '@ember/routing/router-service';
-import type { StoreknoxInventoryPendingReviewsQueryParam } from 'irene/routes/authenticated/storeknox/inventory/pending-reviews';
import type SkPendingReviewService from 'irene/services/sk-pending-review';
import type SkAppModel from 'irene/models/sk-app';
-export interface StoreknoxInventoryResultsSignature {
- Args: {
- queryParams: StoreknoxInventoryPendingReviewsQueryParam;
- };
-}
-
interface LimitOffset {
limit: number;
offset: number;
@@ -20,60 +14,9 @@ interface LimitOffset {
export default class StoreknoxInventoryPendingReviewComponent extends Component {
@service declare intl: IntlService;
+ @service declare router: RouterService;
@service declare skPendingReview: SkPendingReviewService;
- constructor(
- owner: unknown,
- args: StoreknoxInventoryResultsSignature['Args']
- ) {
- super(owner, args);
-
- const { app_limit, app_offset } = args.queryParams;
-
- this.skPendingReview.fetchPendingReviewApps.perform(app_limit, app_offset);
- }
-
- get columns() {
- return [
- // {
- // headerComponent: 'storeknox/table-columns/checkbox-header',
- // cellComponent: 'storeknox/table-columns/checkbox',
- // minWidth: 10,
- // width: 10,
- // textAlign: 'center',
- // },
- {
- headerComponent: 'storeknox/table-columns/store-header',
- cellComponent: 'storeknox/table-columns/store',
- minWidth: 50,
- width: 50,
- textAlign: 'center',
- },
- {
- name: this.intl.t('application'),
- cellComponent: 'storeknox/table-columns/application',
- width: 200,
- },
- {
- headerComponent:
- 'storeknox/inventory/pending-review/table/found-by-header',
- cellComponent: 'storeknox/inventory/pending-review/table/found-by',
- },
- // {
- // headerComponent:
- // 'storeknox/inventory/pending-review/table/availability-header',
- // cellComponent: 'storeknox/inventory/pending-review/table/availability',
- // textAlign: 'center',
- // },
- {
- name: this.intl.t('status'),
- cellComponent: 'storeknox/inventory/pending-review/table/status',
- textAlign: 'center',
- width: 80,
- },
- ];
- }
-
get reviewAppsData() {
if (this.isFetchingData) {
return Array.from({ length: 5 }, () => ({})) as SkAppModel[];
@@ -83,15 +26,23 @@ export default class StoreknoxInventoryPendingReviewComponent extends Component<
}
// Table Actions
- @action goToPage(args: LimitOffset) {
- this.skPendingReview.fetchPendingReviewApps.perform(
- args.limit,
- args.offset
- );
+ @action goToPage({ limit, offset }: LimitOffset) {
+ // Route model reloads and fetches apps in service
+ this.router.transitionTo({
+ queryParams: {
+ app_limit: limit,
+ app_offset: offset,
+ },
+ });
}
@action changePerPageItem(args: LimitOffset) {
- this.skPendingReview.fetchPendingReviewApps.perform(args.limit, 0);
+ this.router.transitionTo({
+ queryParams: {
+ app_limit: args.limit,
+ app_offset: 0,
+ },
+ });
}
// TO DO: When Multiple Scenario comes in
@@ -105,13 +56,13 @@ export default class StoreknoxInventoryPendingReviewComponent extends Component<
get isFetchingData() {
return (
- this.skPendingReview.fetchPendingReviewApps.isRunning &&
+ this.skPendingReview.isFetchingSkPendingReviewApps &&
!this.skPendingReview.singleUpdate
);
}
get totalCount() {
- return this.skPendingReview.totalCount;
+ return this.skPendingReview.skPendingReviewAppsCount;
}
}
diff --git a/app/components/storeknox/inventory/pending-review/table/index.hbs b/app/components/storeknox/inventory/pending-review/table/index.hbs
index 5917bd83f..8034ed166 100644
--- a/app/components/storeknox/inventory/pending-review/table/index.hbs
+++ b/app/components/storeknox/inventory/pending-review/table/index.hbs
@@ -4,13 +4,13 @@
@totalItems={{@totalCount}}
@nextAction={{@goToPage}}
@prevAction={{@goToPage}}
- @itemPerPageOptions={{this.itemPerPageOptions}}
+ @itemPerPageOptions={{array 10 25 50}}
@defaultLimit={{@limit}}
@offset={{@offset}}
as |pgc|
>
-
+
{{#if column.headerComponent}}
@@ -24,14 +24,14 @@
-
+
{{#let (component r.columnValue.cellComponent) as |Component|}}
-
+
{{/let}}
diff --git a/app/components/storeknox/inventory/pending-review/table/index.ts b/app/components/storeknox/inventory/pending-review/table/index.ts
index cd19e3742..7c2f57075 100644
--- a/app/components/storeknox/inventory/pending-review/table/index.ts
+++ b/app/components/storeknox/inventory/pending-review/table/index.ts
@@ -1,6 +1,7 @@
import Component from '@glimmer/component';
+import { service } from '@ember/service';
+import type IntlService from 'ember-intl/services/intl';
-import type { StoreknoxInventoryPendingReviewsQueryParam } from 'irene/routes/authenticated/storeknox/inventory/pending-reviews';
import type SkAppModel from 'irene/models/sk-app';
interface LimitOffset {
@@ -17,13 +18,51 @@ export interface StoreknoxInventoryPendingReviewTableSignature {
totalCount: number;
goToPage: (args: LimitOffset) => void;
onItemPerPageChange: (args: LimitOffset) => void;
- queryParams: StoreknoxInventoryPendingReviewsQueryParam;
};
}
export default class StoreknoxInventoryPendingReviewTableComponent extends Component {
- get itemPerPageOptions() {
- return [10, 25, 50];
+ @service declare intl: IntlService;
+
+ get columns() {
+ return [
+ // {
+ // headerComponent: 'storeknox/table-columns/checkbox-header',
+ // cellComponent: 'storeknox/table-columns/checkbox',
+ // minWidth: 10,
+ // width: 10,
+ // textAlign: 'center',
+ // },
+ {
+ headerComponent: 'storeknox/table-columns/store-header',
+ cellComponent: 'storeknox/table-columns/store',
+ minWidth: 50,
+ width: 50,
+ textAlign: 'center',
+ },
+ {
+ name: this.intl.t('application'),
+ cellComponent: 'storeknox/table-columns/application',
+ width: 200,
+ },
+ {
+ headerComponent:
+ 'storeknox/inventory/pending-review/table/requested-by-header',
+ cellComponent: 'storeknox/inventory/pending-review/table/requested-by',
+ },
+ // {
+ // headerComponent:
+ // 'storeknox/inventory/pending-review/table/availability-header',
+ // cellComponent: 'storeknox/inventory/pending-review/table/availability',
+ // textAlign: 'center',
+ // },
+ {
+ name: this.intl.t('status'),
+ cellComponent: 'storeknox/inventory/pending-review/table/status',
+ textAlign: 'center',
+ width: 80,
+ },
+ ];
}
get showPagination() {
diff --git a/app/components/storeknox/inventory/pending-review/table/found-by-header/index.hbs b/app/components/storeknox/inventory/pending-review/table/requested-by-header/index.hbs
similarity index 100%
rename from app/components/storeknox/inventory/pending-review/table/found-by-header/index.hbs
rename to app/components/storeknox/inventory/pending-review/table/requested-by-header/index.hbs
diff --git a/app/components/storeknox/inventory/pending-review/table/found-by-header/index.scss b/app/components/storeknox/inventory/pending-review/table/requested-by-header/index.scss
similarity index 100%
rename from app/components/storeknox/inventory/pending-review/table/found-by-header/index.scss
rename to app/components/storeknox/inventory/pending-review/table/requested-by-header/index.scss
diff --git a/app/components/storeknox/inventory/pending-review/table/found-by-header/index.ts b/app/components/storeknox/inventory/pending-review/table/requested-by-header/index.ts
similarity index 84%
rename from app/components/storeknox/inventory/pending-review/table/found-by-header/index.ts
rename to app/components/storeknox/inventory/pending-review/table/requested-by-header/index.ts
index baebb11d8..41e7147f5 100644
--- a/app/components/storeknox/inventory/pending-review/table/found-by-header/index.ts
+++ b/app/components/storeknox/inventory/pending-review/table/requested-by-header/index.ts
@@ -6,7 +6,7 @@ import type IntlService from 'ember-intl/services/intl';
import ENUMS from 'irene/enums';
-export default class StoreknoxInventoryPendingReviewTableFoundByHeaderComponent extends Component {
+export default class StoreknoxInventoryPendingReviewTableRequestedByHeaderComponent extends Component {
@service declare intl: IntlService;
@tracked anchorRef: HTMLElement | null = null;
@@ -61,6 +61,6 @@ export default class StoreknoxInventoryPendingReviewTableFoundByHeaderComponent
declare module '@glint/environment-ember-loose/registry' {
export default interface Registry {
- 'Storeknox::Inventory::PendingReview::Table::FoundByHeader': typeof StoreknoxInventoryPendingReviewTableFoundByHeaderComponent;
+ 'Storeknox::Inventory::PendingReview::Table::RequestedByHeader': typeof StoreknoxInventoryPendingReviewTableRequestedByHeaderComponent;
}
}
diff --git a/app/components/storeknox/inventory/pending-review/table/found-by/index.hbs b/app/components/storeknox/inventory/pending-review/table/requested-by/index.hbs
similarity index 58%
rename from app/components/storeknox/inventory/pending-review/table/found-by/index.hbs
rename to app/components/storeknox/inventory/pending-review/table/requested-by/index.hbs
index 98ed1c632..2b12673b5 100644
--- a/app/components/storeknox/inventory/pending-review/table/found-by/index.hbs
+++ b/app/components/storeknox/inventory/pending-review/table/requested-by/index.hbs
@@ -3,11 +3,17 @@
{{else}}
{{#if this.isManual}}
-
+
{{@data.addedBy.email}}
{{else}}
-
+
{{t 'system'}}
{{/if}}
@@ -19,11 +25,15 @@
@color='inherit'
@fontWeight='medium'
local-class='section-header'
+ data-test-storeknoxInventory-pendingReviewTable-requestedOnHeaderText
>
{{t 'requestedOn'}}
-
+
{{this.addedOn}}
@@ -35,11 +45,15 @@
@color='inherit'
@fontWeight='medium'
local-class='section-header'
+ data-test-storeknoxInventory-pendingReviewTable-foundByHeader
>
{{t 'type'}}
-
+
{{this.foundBy}}
@@ -47,7 +61,11 @@
<:default>
-
+
diff --git a/app/components/storeknox/inventory/pending-review/table/found-by/index.scss b/app/components/storeknox/inventory/pending-review/table/requested-by/index.scss
similarity index 100%
rename from app/components/storeknox/inventory/pending-review/table/found-by/index.scss
rename to app/components/storeknox/inventory/pending-review/table/requested-by/index.scss
diff --git a/app/components/storeknox/inventory/pending-review/table/found-by/index.ts b/app/components/storeknox/inventory/pending-review/table/requested-by/index.ts
similarity index 58%
rename from app/components/storeknox/inventory/pending-review/table/found-by/index.ts
rename to app/components/storeknox/inventory/pending-review/table/requested-by/index.ts
index 88325c781..7230244b1 100644
--- a/app/components/storeknox/inventory/pending-review/table/found-by/index.ts
+++ b/app/components/storeknox/inventory/pending-review/table/requested-by/index.ts
@@ -1,29 +1,31 @@
import Component from '@glimmer/component';
import dayjs from 'dayjs';
-import type IntlService from 'ember-intl/services/intl';
import { inject as service } from '@ember/service';
+import type IntlService from 'ember-intl/services/intl';
-import type SkAppModel from 'irene/models/sk-app';
import ENUMS from 'irene/enums';
+import type SkAppModel from 'irene/models/sk-app';
-interface StoreknoxInventoryPendingReviewTableFoundBySignature {
+interface StoreknoxInventoryPendingReviewTableRequestedBySignature {
Args: {
data: SkAppModel;
loading: boolean;
};
}
-export default class StoreknoxInventoryPendingReviewTableFoundByComponent extends Component {
+export default class StoreknoxInventoryPendingReviewTableRequestedByComponent extends Component {
@service declare intl: IntlService;
get addedOn() {
return dayjs(this.args.data.addedOn).format('MMMM D, YYYY, HH:mm');
}
- get foundBy() {
- const appSource = this.args.data.appSource;
+ get appSource() {
+ return this.args.data.appSource;
+ }
- if (appSource === ENUMS.SK_DISCOVERY.MANUAL) {
+ get foundBy() {
+ if (this.appSource === ENUMS.SK_DISCOVERY.MANUAL) {
return this.intl.t('storeknox.manualDiscovery');
} else {
return this.intl.t('storeknox.autoDiscovery');
@@ -31,14 +33,12 @@ export default class StoreknoxInventoryPendingReviewTableFoundByComponent extend
}
get isManual() {
- const appSource = this.args.data.appSource;
-
- return appSource === ENUMS.SK_DISCOVERY.MANUAL;
+ return this.appSource === ENUMS.SK_DISCOVERY.MANUAL;
}
}
declare module '@glint/environment-ember-loose/registry' {
export default interface Registry {
- 'Storeknox::Inventory::PendingReview::Table::FoundBy': typeof StoreknoxInventoryPendingReviewTableFoundByComponent;
+ 'Storeknox::Inventory::PendingReview::Table::RequestedBy': typeof StoreknoxInventoryPendingReviewTableRequestedByComponent;
}
}
diff --git a/app/components/storeknox/inventory/pending-review/table/status/index.hbs b/app/components/storeknox/inventory/pending-review/table/status/index.hbs
index 3daa4ef62..a86833bfb 100644
--- a/app/components/storeknox/inventory/pending-review/table/status/index.hbs
+++ b/app/components/storeknox/inventory/pending-review/table/status/index.hbs
@@ -7,16 +7,34 @@
{{else}}
-
-
+
+
-
-
+
+
{{/if}}
-{{else}}
+
+ {{! TODO: Intended for Review Logs Table }}
+ {{!-- {{else}}
{{this.statusDetails.text}}
@@ -44,5 +62,5 @@
-
+ --}}
{{/if}}
\ No newline at end of file
diff --git a/app/components/storeknox/inventory/pending-review/table/status/index.ts b/app/components/storeknox/inventory/pending-review/table/status/index.ts
index 99944da98..3426c50bd 100644
--- a/app/components/storeknox/inventory/pending-review/table/status/index.ts
+++ b/app/components/storeknox/inventory/pending-review/table/status/index.ts
@@ -3,20 +3,18 @@ import { action } from '@ember/object';
import dayjs from 'dayjs';
import { task } from 'ember-concurrency';
import { inject as service } from '@ember/service';
-
-import parseError from 'irene/utils/parse-error';
+import { waitForPromise } from '@ember/test-waiters';
import type IntlService from 'ember-intl/services/intl';
+import parseError from 'irene/utils/parse-error';
+import ENUMS from 'irene/enums';
import type SkAppModel from 'irene/models/sk-app';
-import type { StoreknoxInventoryPendingReviewsQueryParam } from 'irene/routes/authenticated/storeknox/inventory/pending-reviews';
import type SkPendingReviewService from 'irene/services/sk-pending-review';
-import ENUMS from 'irene/enums';
interface StoreknoxInventoryPendingReviewTableStatusSignature {
Args: {
data: SkAppModel;
loading: boolean;
- queryParams: StoreknoxInventoryPendingReviewsQueryParam;
};
}
@@ -29,14 +27,6 @@ export default class StoreknoxInventoryPendingReviewTableStatusComponent extends
return this.rejectApp.isRunning || this.approveApp.isRunning;
}
- get approvedOn() {
- return dayjs(this.args.data.approvedOn).format('MMMM D, YYYY, HH:mm');
- }
-
- get rejectedOn() {
- return dayjs(this.args.data.rejectedOn).format('MMMM D, YYYY, HH:mm');
- }
-
get isPending() {
return (
this.args.data.approvalStatus ===
@@ -52,6 +42,16 @@ export default class StoreknoxInventoryPendingReviewTableStatusComponent extends
return this.args.data.approvalStatus === ENUMS.SK_APPROVAL_STATUS.REJECTED;
}
+ // TODO: Review when feature is to be worked on
+ // Intended for Review Logs table
+ get approvedOn() {
+ return dayjs(this.args.data.approvedOn).format('MMMM D, YYYY, HH:mm');
+ }
+
+ get rejectedOn() {
+ return dayjs(this.args.data.rejectedOn).format('MMMM D, YYYY, HH:mm');
+ }
+
get statusDetails() {
if (this.isApproved) {
return {
@@ -84,14 +84,9 @@ export default class StoreknoxInventoryPendingReviewTableStatusComponent extends
try {
const skApp = this.args.data;
- await skApp.approveApp(skApp.id);
-
- const { app_limit, app_offset } = this.args.queryParams;
+ await waitForPromise(skApp.approveApp(skApp.id));
- this.skPendingReview.fetchPendingReviewApps.perform(
- app_limit,
- app_offset
- );
+ this.skPendingReview.reload();
this.notify.success(
this.intl.t('storeknox.appAddedToInventory', {
@@ -109,14 +104,9 @@ export default class StoreknoxInventoryPendingReviewTableStatusComponent extends
try {
const skApp = this.args.data;
- await skApp.rejectApp(skApp.id);
-
- const { app_limit, app_offset } = this.args.queryParams;
+ await waitForPromise(skApp.rejectApp(skApp.id));
- this.skPendingReview.fetchPendingReviewApps.perform(
- app_limit,
- app_offset
- );
+ this.skPendingReview.reload();
this.notify.success(
this.intl.t('storeknox.appRejected', {
diff --git a/app/components/storeknox/product-icon/index.hbs b/app/components/storeknox/product-icon/index.hbs
index b19a659ed..f564329d2 100644
--- a/app/components/storeknox/product-icon/index.hbs
+++ b/app/components/storeknox/product-icon/index.hbs
@@ -1,62 +1,69 @@
-
-
- {{yield}}
-
+ {{on 'click' this.toggleProductInfo}}
+ data-test-storeknox-productIconContainer
+>
+ {{yield}}
+
-
+
-
-
-
-
-
- INFO
-
+
+
+
-
- {{t 'storeknox.appIsPartOf'}}
- {{get this.productTitle @product}}
+
+ {{t 'infoCapitalCase'}}
- {{#if this.isAStore}}
-
-
-
- {{t 'storeknox.checkOn'}}
- {{get this.productTitle @product}}
-
-
-
- {{/if}}
+
+ {{t 'storeknox.appIsPartOf'}}
+ {{get this.productTitle @product}}
+
-
-
\ No newline at end of file
+
+ {{#if this.isAStore}}
+
+
+
+ {{t 'storeknox.checkOn'}}
+ {{get this.productTitle @product}}
+
+
+
+ {{/if}}
+
+
\ No newline at end of file
diff --git a/app/components/storeknox/table-columns/application/index.hbs b/app/components/storeknox/table-columns/application/index.hbs
index 2b2dcbf8b..9dcefc984 100644
--- a/app/components/storeknox/table-columns/application/index.hbs
+++ b/app/components/storeknox/table-columns/application/index.hbs
@@ -31,6 +31,7 @@
@underline='always'
title={{@data.title}}
{{style width='100%'}}
+ data-test-storeknoxTableColumns-applicationTitle
>
{{@data.title}}
@@ -41,6 +42,7 @@
@noWrap={{true}}
title={{@data.packageName}}
{{style width='100%'}}
+ data-test-storeknoxTableColumns-applicationPackageName
>
{{@data.packageName}}
diff --git a/app/components/storeknox/table-columns/developer/index.hbs b/app/components/storeknox/table-columns/developer/index.hbs
index 40e7fdc20..6db5d015a 100644
--- a/app/components/storeknox/table-columns/developer/index.hbs
+++ b/app/components/storeknox/table-columns/developer/index.hbs
@@ -11,12 +11,18 @@
@noWrap={{true}}
title={{@data.devName}}
{{style width='100%'}}
+ data-test-storeknoxTableColumns-applicationDevName
>
{{@data.devName}}
{{#if @data.devEmail}}
-
+
{{@data.devEmail}}
{{else}}
diff --git a/app/controllers/authenticated/storeknox/inventory/pending-reviews.ts b/app/controllers/authenticated/storeknox/inventory/pending-reviews.ts
index 49b6b7936..5f4f35841 100644
--- a/app/controllers/authenticated/storeknox/inventory/pending-reviews.ts
+++ b/app/controllers/authenticated/storeknox/inventory/pending-reviews.ts
@@ -5,6 +5,7 @@ import type { AkBreadcrumbsItemProps } from 'irene/services/ak-breadcrumbs';
export default class AuthenticatedStoreknoxInventoryPendingReviewsController extends Controller {
@service declare intl: IntlService;
+
queryParams = ['app_limit', 'app_offset', 'app_search_id', 'app_query'];
app_limit = 10;
diff --git a/app/enums.ts b/app/enums.ts
index a2827529a..a1fc32fd2 100644
--- a/app/enums.ts
+++ b/app/enums.ts
@@ -267,6 +267,13 @@ const ENUMS = {
UNSCANNED: 2,
NOT_FOUND: 3,
},
+
+ SK_ORGANIZATION_MEMBERSHIP_ROLES: {
+ MEMBER: 1,
+ COLLABORATOR: 2,
+ ADMIN: 3,
+ OWNER: 4,
+ },
};
export const ENUMS_DISPLAY = {
diff --git a/app/models/sk-app.ts b/app/models/sk-app.ts
index 5888e32fa..68fb619e9 100644
--- a/app/models/sk-app.ts
+++ b/app/models/sk-app.ts
@@ -5,6 +5,7 @@ import type IntlService from 'ember-intl/services/intl';
import ENUMS from 'irene/enums';
import type OrganizationUserModel from './organization-user';
import type SkAppMetadataModel from './sk-app-metadata';
+import SkOrganizationModel from './sk-organization';
export interface AvailabilityData {
storeknox: boolean;
@@ -41,8 +42,8 @@ export default class SkAppModel extends Model {
@attr('number')
declare storeMonitoringStatus: number;
- @attr('number')
- declare skOrganization: number;
+ @attr('string')
+ declare storeMonitoringStatusDisplay: string;
@attr('date')
declare approvedOn: Date;
@@ -71,6 +72,9 @@ export default class SkAppModel extends Model {
@belongsTo('organization-user', { async: true, inverse: null })
declare rejectedBy: AsyncBelongsTo | string;
+ @belongsTo('sk-organization', { async: true, inverse: null })
+ declare skOrganization: AsyncBelongsTo;
+
get docUlid() {
return this.appMetadata.get('docUlid');
}
diff --git a/app/models/sk-inventory-app.ts b/app/models/sk-inventory-app.ts
index 14dc76c4c..3b6f72fa1 100644
--- a/app/models/sk-inventory-app.ts
+++ b/app/models/sk-inventory-app.ts
@@ -1,5 +1,7 @@
import { belongsTo } from '@ember-data/model';
+import ENUMS from 'irene/enums';
import SkAppModel from './sk-app';
+
import type ProjectModel from './project';
import type FileModel from './file';
@@ -13,6 +15,12 @@ export default class SkInventoryAppModel extends SkAppModel {
get appIsNotAvailableOnAppknox() {
return !this.coreProject?.id;
}
+
+ get containsUnscannedVersion() {
+ return (
+ this.storeMonitoringStatus === ENUMS.SK_APP_MONITORING_STATUS.UNSCANNED
+ );
+ }
}
declare module 'ember-data/types/registries/model' {
diff --git a/app/routes/authenticated/storeknox/inventory/app-list.ts b/app/routes/authenticated/storeknox/inventory/app-list.ts
index 4f1ac2008..30dc6c90b 100644
--- a/app/routes/authenticated/storeknox/inventory/app-list.ts
+++ b/app/routes/authenticated/storeknox/inventory/app-list.ts
@@ -1,4 +1,8 @@
import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route';
+import { service } from '@ember/service';
+
+import type SkPendingReviewService from 'irene/services/sk-pending-review';
+import type SkInventoryAppService from 'irene/services/sk-inventory-apps';
export interface StoreknoxInventoryAppListQueryParams {
app_limit: number;
@@ -6,6 +10,11 @@ export interface StoreknoxInventoryAppListQueryParams {
}
export default class AuthenticatedStoreknoxInventoryAppListRoute extends AkBreadcrumbsRoute {
+ @service declare skPendingReview: SkPendingReviewService;
+
+ @service('sk-inventory-apps')
+ declare skInventoryAppsService: SkInventoryAppService;
+
queryParams = {
app_limit: {
refreshModel: true,
@@ -15,11 +24,17 @@ export default class AuthenticatedStoreknoxInventoryAppListRoute extends AkBread
},
};
- model(params: Partial) {
+ async model(params: Partial) {
const { app_limit, app_offset } = params;
- return {
- queryParams: { app_limit, app_offset },
- };
+ this.skInventoryAppsService
+ .setLimitOffset({
+ limit: app_limit,
+ offset: app_offset,
+ })
+ .reload();
+
+ // To get pending review count in tabs when page is visited for the first time
+ this.skPendingReview.reload();
}
}
diff --git a/app/routes/authenticated/storeknox/inventory/pending-reviews.ts b/app/routes/authenticated/storeknox/inventory/pending-reviews.ts
index 0408e67df..c3f235ee9 100644
--- a/app/routes/authenticated/storeknox/inventory/pending-reviews.ts
+++ b/app/routes/authenticated/storeknox/inventory/pending-reviews.ts
@@ -1,4 +1,10 @@
+import { service } from '@ember/service';
+import type RouterService from '@ember/routing/router-service';
+
import AkBreadcrumbsRoute from 'irene/utils/ak-breadcrumbs-route';
+import type MeService from 'irene/services/me';
+import type SkPendingReviewService from 'irene/services/sk-pending-review';
+import type SkInventoryAppService from 'irene/services/sk-inventory-apps';
export interface StoreknoxInventoryPendingReviewsQueryParam {
app_limit: number;
@@ -6,6 +12,13 @@ export interface StoreknoxInventoryPendingReviewsQueryParam {
}
export default class AuthenticatedStoreknoxInventoryPendingReviewsRoute extends AkBreadcrumbsRoute {
+ @service declare me: MeService;
+ @service declare router: RouterService;
+ @service declare skPendingReview: SkPendingReviewService;
+
+ @service('sk-inventory-apps')
+ declare skInventoryAppsService: SkInventoryAppService;
+
queryParams = {
app_limit: {
refreshModel: true,
@@ -15,11 +28,23 @@ export default class AuthenticatedStoreknoxInventoryPendingReviewsRoute extends
},
};
+ async beforeModel() {
+ if (!this.me.org?.is_owner) {
+ this.router.transitionTo('authenticated.storeknox.inventory.app-list');
+ }
+ }
+
model(params: Partial) {
const { app_limit, app_offset } = params;
- return {
- queryParams: { app_limit, app_offset },
- };
+ this.skPendingReview
+ .setLimitOffset({
+ limit: app_limit,
+ offset: app_offset,
+ })
+ .reload();
+
+ // To get inventory list count in tabs when page is visited for the first time
+ this.skInventoryAppsService.reload();
}
}
diff --git a/app/services/sk-inventory-apps.ts b/app/services/sk-inventory-apps.ts
new file mode 100644
index 000000000..21bad2060
--- /dev/null
+++ b/app/services/sk-inventory-apps.ts
@@ -0,0 +1,67 @@
+// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
+import { DS } from 'ember-data';
+
+import Service, { inject as service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import { task } from 'ember-concurrency';
+import type Store from '@ember-data/store';
+
+import ENV from 'irene/config/environment';
+import ENUMS from 'irene/enums';
+import type SkInventoryAppModel from 'irene/models/sk-app';
+
+type SkInventoryAppModelArray =
+ DS.AdapterPopulatedRecordArray & {
+ meta: { count: number };
+ };
+
+export default class SkInventoryAppService extends Service {
+ @service declare store: Store;
+ @service('notifications') declare notify: NotificationService;
+
+ @tracked
+ skInventoryApps?: DS.AdapterPopulatedRecordArray;
+
+ @tracked limit = 10;
+ @tracked offset = 0;
+ @tracked skInventoryAppsCount = 0;
+
+ get isFetchingSkInventoryApps() {
+ return this.fetch.isRunning;
+ }
+
+ setLimitOffset({ limit = 10, offset = 0 }) {
+ this.limit = limit;
+ this.offset = offset;
+
+ return this;
+ }
+
+ async reload() {
+ await this.fetch.perform();
+ }
+
+ fetch = task({ keepLatest: true }, async () => {
+ const queryParams = {
+ limit: this.limit,
+ offset: this.offset,
+ approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED,
+ app_status: ENUMS.SK_APP_STATUS.ACTIVE,
+ };
+
+ try {
+ const skInventoryApps = (await this.store.query(
+ 'sk-app',
+ queryParams
+ )) as SkInventoryAppModelArray;
+
+ this.skInventoryApps = skInventoryApps;
+ this.skInventoryAppsCount = skInventoryApps.meta.count;
+ } catch {
+ this.notify.error(
+ 'Failed to load Inventory Apps Data. Check your network and try again.',
+ ENV.notifications
+ );
+ }
+ });
+}
diff --git a/app/services/sk-pending-review.ts b/app/services/sk-pending-review.ts
index 91d658bfa..9413d90be 100644
--- a/app/services/sk-pending-review.ts
+++ b/app/services/sk-pending-review.ts
@@ -1,20 +1,20 @@
+// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
+import type { DS } from 'ember-data';
import Service, { service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
-// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
-import type { DS } from 'ember-data';
import type Store from '@ember-data/store';
import type RouterService from '@ember/routing/router-service';
import parseError from 'irene/utils/parse-error';
-import type SkAppModel from 'irene/models/sk-app';
import ENUMS from 'irene/enums';
+import type SkAppModel from 'irene/models/sk-app';
type SkPendingReviewResponse = DS.AdapterPopulatedRecordArray & {
meta: { count: number };
};
-export default class ServiceAccountService extends Service {
+export default class SkPendingReviewService extends Service {
@tracked skPendingReviewData: SkPendingReviewResponse | null = null;
@tracked singleUpdate: boolean = false;
@@ -22,42 +22,40 @@ export default class ServiceAccountService extends Service {
@service declare router: RouterService;
@service('notifications') declare notify: NotificationService;
- fetchPendingReviewApps = task(
- { drop: true },
- async (
- limit: number = 10,
- offset: number = 0,
- setQueryParams: boolean = true
- ) => {
- try {
- if (setQueryParams) {
- this.setRouteQueryParams(limit, offset);
- }
-
- this.skPendingReviewData = (await this.store.query('skApp', {
- limit,
- offset,
- approval_status: ENUMS.SK_APPROVAL_STATUS.PENDING_APPROVAL,
- app_status: ENUMS.SK_APP_STATUS.ACTIVE,
- })) as SkPendingReviewResponse;
-
- this.singleUpdate = false;
- } catch (e) {
- this.notify.error(parseError(e));
- }
- }
- );
+ @tracked limit = 10;
+ @tracked offset = 0;
+ @tracked skPendingReviewAppsCount = 0;
+
+ get isFetchingSkPendingReviewApps() {
+ return this.fetchPendingReviewApps.isRunning;
+ }
- get totalCount() {
- return this.skPendingReviewData?.meta?.count || 0;
+ setLimitOffset({ limit = 10, offset = 0 }) {
+ this.limit = limit;
+ this.offset = offset;
+
+ return this;
}
- setRouteQueryParams(limit: number, offset: number) {
- this.router.transitionTo({
- queryParams: {
- app_limit: limit,
- app_offset: offset,
- },
- });
+ async reload() {
+ await this.fetchPendingReviewApps.perform();
}
+
+ fetchPendingReviewApps = task({ keepLatest: true }, async () => {
+ try {
+ const skPendingReviewData = (await this.store.query('sk-app', {
+ limit: this.limit,
+ offset: this.offset,
+ approval_status: ENUMS.SK_APPROVAL_STATUS.PENDING_APPROVAL,
+ app_status: ENUMS.SK_APP_STATUS.ACTIVE,
+ })) as SkPendingReviewResponse;
+
+ this.skPendingReviewData = skPendingReviewData;
+ this.skPendingReviewAppsCount = skPendingReviewData.meta.count;
+
+ this.singleUpdate = false;
+ } catch (e) {
+ this.notify.error(parseError(e));
+ }
+ });
}
diff --git a/app/templates/authenticated/storeknox/inventory/app-list.hbs b/app/templates/authenticated/storeknox/inventory/app-list.hbs
index 682649dd2..545f2c70e 100644
--- a/app/templates/authenticated/storeknox/inventory/app-list.hbs
+++ b/app/templates/authenticated/storeknox/inventory/app-list.hbs
@@ -1,3 +1,3 @@
{{page-title 'Requested'}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/templates/authenticated/storeknox/inventory/pending-reviews.hbs b/app/templates/authenticated/storeknox/inventory/pending-reviews.hbs
index cf9a15f80..6317dee3e 100644
--- a/app/templates/authenticated/storeknox/inventory/pending-reviews.hbs
+++ b/app/templates/authenticated/storeknox/inventory/pending-reviews.hbs
@@ -1,3 +1,3 @@
{{page-title (t 'storeknox.pendingReview')}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/mirage/factories/sk-app.ts b/mirage/factories/sk-app.ts
index b82e8f43f..af51de5ba 100644
--- a/mirage/factories/sk-app.ts
+++ b/mirage/factories/sk-app.ts
@@ -6,8 +6,24 @@ import { faker } from '@faker-js/faker';
import ENUMS from 'irene/enums';
export default Factory.extend({
+ approved_on: () => faker.date.past(),
+ added_on: () => faker.date.past(),
+ updated_on: () => faker.date.recent(),
+ rejected_on: () => faker.date.past(),
+ monitoring_enabled: () => faker.datatype.boolean(),
+ monitoring_status: () => faker.number.int({ min: 0, max: 3 }),
+
+ app_status: () => faker.helpers.arrayElement(ENUMS.SK_APP_STATUS.BASE_VALUES),
+
+ app_status_display() {
+ const app_status = this.app_status as number;
+
+ return ENUMS.SK_APP_STATUS.BASE_CHOICES.find((c) => c.value === app_status)
+ ?.key;
+ },
+
approval_status: () =>
- faker.helpers.arrayElement(ENUMS.SK_APPROVAL_STATUS.VALUES),
+ faker.helpers.arrayElement(ENUMS.SK_APPROVAL_STATUS.BASE_VALUES),
approval_status_display() {
const approval_status = this.approval_status as number;
@@ -17,28 +33,17 @@ export default Factory.extend({
)?.key;
},
- app_status: () => faker.helpers.arrayElement(ENUMS.SK_APP_STATUS.VALUES),
+ store_monitoring_status: () =>
+ faker.helpers.arrayElement(ENUMS.SK_APP_MONITORING_STATUS.BASE_VALUES),
- app_status_display() {
- const app_status = this.app_status as number;
+ store_monitoring_status_display() {
+ const store_monitoring_status = this.store_monitoring_status as number;
- return ENUMS.SK_APP_STATUS.BASE_CHOICES.find((c) => c.value === app_status)
- ?.key;
+ return ENUMS.SK_APP_MONITORING_STATUS.BASE_CHOICES.find(
+ (c) => c.value === store_monitoring_status
+ )?.key;
},
- monitoring_enabled: () => faker.datatype.boolean(),
- monitoring_status: () => faker.number.int({ min: 0, max: 3 }),
-
- approved_on: () => faker.date.past(),
- added_on: () => faker.date.past(),
- updated_on: () => faker.date.recent(),
- rejected_on: () => faker.date.past(),
-
- availability: () => ({
- storeknox: faker.datatype.boolean(),
- appknox: faker.datatype.boolean(),
- }),
-
// @ts-expect-error
afterCreate(skApp: ModelInstance, server: Server) {
// @ts-expect-error
@@ -58,4 +63,9 @@ export default Factory.extend({
approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED,
app_status: ENUMS.SK_APP_STATUS.ACTIVE,
}),
+
+ withAddedToAppknox: trait({
+ approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED,
+ app_status: ENUMS.SK_APP_STATUS.ACTIVE,
+ }),
});
diff --git a/mirage/factories/sk-inventory-app.ts b/mirage/factories/sk-inventory-app.ts
new file mode 100644
index 000000000..5ad2d147a
--- /dev/null
+++ b/mirage/factories/sk-inventory-app.ts
@@ -0,0 +1,3 @@
+import SkAppFactory from './sk-app';
+
+export default SkAppFactory.extend({});
diff --git a/mirage/factories/sk-organization-membership.ts b/mirage/factories/sk-organization-membership.ts
new file mode 100644
index 000000000..56ad5e81d
--- /dev/null
+++ b/mirage/factories/sk-organization-membership.ts
@@ -0,0 +1,8 @@
+import { faker } from '@faker-js/faker';
+import ENUMS from 'irene/enums';
+import Base from './base';
+
+export default Base.extend({
+ roles: () =>
+ faker.helpers.arrayElement(ENUMS.SK_ORGANIZATION_MEMBERSHIP_ROLES.VALUES),
+});
diff --git a/mirage/factories/sk-organization.ts b/mirage/factories/sk-organization.ts
new file mode 100644
index 000000000..68a1c55cc
--- /dev/null
+++ b/mirage/factories/sk-organization.ts
@@ -0,0 +1,7 @@
+import { faker } from '@faker-js/faker';
+import Base from './base';
+
+export default Base.extend({
+ add_appknox_project_to_inventory_by_default: faker.datatype.boolean(),
+ autodiscovery_onboarding_done: faker.datatype.boolean(),
+});
diff --git a/mirage/factories/sk-requested-app.ts b/mirage/factories/sk-requested-app.ts
index 04d1eb1aa..844686ebb 100644
--- a/mirage/factories/sk-requested-app.ts
+++ b/mirage/factories/sk-requested-app.ts
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-expect-error "trait" prop missing from miragejs
-import { ModelInstance, Server, trait } from 'miragejs';
+import { trait } from 'miragejs';
import { faker } from '@faker-js/faker';
import ENUMS from 'irene/enums';
@@ -28,14 +28,4 @@ export default SkAppFactory.extend({
rejected_by: () => faker.person.firstName(),
approval_status: ENUMS.SK_APPROVAL_STATUS.REJECTED,
}),
-
- // @ts-expect-error
- afterCreate(skApp: ModelInstance, server: Server) {
- // @ts-expect-error
- if (!skApp.app_metadata) {
- skApp.update({
- app_metadata: server.create('sk-app-metadata'),
- });
- }
- },
});
diff --git a/mirage/models/organization-user.ts b/mirage/models/organization-user.ts
new file mode 100644
index 000000000..db502f142
--- /dev/null
+++ b/mirage/models/organization-user.ts
@@ -0,0 +1,3 @@
+import { Model } from 'miragejs';
+
+export default Model.extend({});
diff --git a/mirage/models/sk-app.ts b/mirage/models/sk-app.ts
index c67cfc619..382090817 100644
--- a/mirage/models/sk-app.ts
+++ b/mirage/models/sk-app.ts
@@ -2,4 +2,5 @@ import { Model, belongsTo } from 'miragejs';
export default Model.extend({
app_metadata: belongsTo('sk-app-metadata'),
+ addedBy: belongsTo('organization-user'),
});
diff --git a/mirage/models/sk-inventory-app.ts b/mirage/models/sk-inventory-app.ts
index db502f142..46a269043 100644
--- a/mirage/models/sk-inventory-app.ts
+++ b/mirage/models/sk-inventory-app.ts
@@ -1,3 +1,7 @@
-import { Model } from 'miragejs';
+import { Model, belongsTo } from 'miragejs';
-export default Model.extend({});
+export default Model.extend({
+ app_metadata: belongsTo('sk-app-metadata'),
+ coreProject: belongsTo('project'),
+ coreProjectLatestVersion: belongsTo('file'),
+});
diff --git a/mirage/models/sk-organization-membership.ts b/mirage/models/sk-organization-membership.ts
new file mode 100644
index 000000000..800f6fce9
--- /dev/null
+++ b/mirage/models/sk-organization-membership.ts
@@ -0,0 +1,6 @@
+import { Model, belongsTo } from 'miragejs';
+
+export default Model.extend({
+ member: belongsTo('user'),
+ skOrganization: belongsTo('sk-organization'),
+});
diff --git a/mirage/models/sk-organization.ts b/mirage/models/sk-organization.ts
new file mode 100644
index 000000000..71d68db08
--- /dev/null
+++ b/mirage/models/sk-organization.ts
@@ -0,0 +1,6 @@
+import { Model, belongsTo, hasMany } from 'miragejs';
+
+export default Model.extend({
+ organization: belongsTo('organization'),
+ members: hasMany('sk-organization-membership'),
+});
diff --git a/tests/acceptance/storeknox/inventory-details/app-details-test.js b/tests/acceptance/storeknox/inventory-details/app-details-test.js
new file mode 100644
index 000000000..fb9caf397
--- /dev/null
+++ b/tests/acceptance/storeknox/inventory-details/app-details-test.js
@@ -0,0 +1,386 @@
+import { visit, find, click } from '@ember/test-helpers';
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import { t } from 'ember-intl/test-support';
+
+import Service from '@ember/service';
+import dayjs from 'dayjs';
+
+import { setupRequiredEndpoints } from 'irene/tests/helpers/acceptance-utils';
+import ENUMS from 'irene/enums';
+
+// Notification Service
+class NotificationsStub extends Service {
+ errorMsg = null;
+ successMsg = null;
+
+ error(msg) {
+ this.errorMsg = msg;
+ }
+
+ success(msg) {
+ this.successMsg = msg;
+ }
+
+ setDefaultAutoClear() {}
+}
+
+module(
+ 'Acceptance | storeknox/inventory-details/app-details',
+ function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(async function () {
+ const { organization, currentOrganizationMe } =
+ await setupRequiredEndpoints(this.server);
+
+ organization.update({
+ features: {
+ storeknox: true,
+ },
+ });
+
+ // Server mocks
+ this.server.get('v2/sk_app_detail/:id', (schema, req) => {
+ const app = schema.skInventoryApps.find(req.params.id);
+
+ return { ...app.toJSON(), app_metadata: app.app_metadata };
+ });
+
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ this.server.get('/v2/files/:id', (schema, req) => {
+ return schema.files.find(`${req.params.id}`)?.toJSON();
+ });
+
+ this.server.get('/v2/projects/:id', (schema, req) => {
+ return schema.projects.find(`${req.params.id}`)?.toJSON();
+ });
+
+ // Services
+ this.owner.register('service:notifications', NotificationsStub);
+ const store = this.owner.lookup('service:store');
+
+ const normalizeSKInventoryApp = (inventoryApp) =>
+ store.push(
+ store.normalize('sk-inventory-app', {
+ ...inventoryApp.toJSON(),
+ app_metadata: inventoryApp.app_metadata,
+ })
+ );
+
+ this.setProperties({
+ currentOrganizationMe,
+ store,
+ normalizeSKInventoryApp,
+ });
+ });
+
+ test.each(
+ 'it renders app details',
+ [{ available_on_appknox: true }, { available_on_appknox: false }],
+ async function (assert, { available_on_appknox }) {
+ const file = this.server.create('file');
+ const core_project = this.server.create('project');
+
+ // Models
+ const inventoryApp = this.server.create(
+ 'sk-inventory-app',
+ 'withApprovedStatus',
+ {
+ core_project: available_on_appknox ? core_project.id : null,
+ core_project_latest_version: available_on_appknox ? file.id : null,
+ }
+ );
+
+ const inventoryAppRecord = this.normalizeSKInventoryApp(inventoryApp);
+
+ await visit(
+ `/dashboard/storeknox/inventory-details/${inventoryAppRecord.id}`
+ );
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-breadcrumbContainer]')
+ .exists();
+
+ if (!available_on_appknox) {
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appNotPartOfAppknoxIcon]'
+ )
+ .exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appNotPartOfAppknoxIconText]'
+ )
+ .hasText(t('storeknox.appNotPartOfAppknox'));
+ }
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-appLogoImage]')
+ .hasAttribute('src', inventoryAppRecord.appMetadata.iconUrl);
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerAppTitle]')
+ .hasText(inventoryAppRecord.appMetadata.title);
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerAppPackageName]')
+ .hasText(inventoryAppRecord.appMetadata.packageName);
+
+ // App Logo tests
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerPlatformIcon]')
+ .hasClass(inventoryAppRecord.isAndroid ? /android/ : /apple/);
+
+ const storeLogoSelector = inventoryAppRecord.isAndroid
+ ? '[data-test-storeknoxInventoryDetails-playStoreLogo]'
+ : '[data-test-storeknoxInventoryDetails-appStoreLogo]';
+
+ assert.dom(storeLogoSelector).exists();
+
+ await click(storeLogoSelector);
+
+ assert
+ .dom('[data-test-storeknox-productInfoCaptionText]')
+ .hasText(t('infoCapitalCase'));
+
+ const productTitle = inventoryAppRecord.isAndroid
+ ? t('storeknox.playStore')
+ : t('storeknox.appStore');
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appIsPartOfText]')
+ .containsText(t('storeknox.appIsPartOf'))
+ .containsText(productTitle);
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appStoreLink]')
+ .hasAttribute('href', inventoryAppRecord.appMetadata.url);
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appStoreLinkBtn]')
+ .hasText(t('storeknox.checkOn') + ` ${productTitle}`);
+
+ await click(storeLogoSelector);
+
+ if (available_on_appknox) {
+ assert.dom('[data-test-storeknoxInventoryDetails-vaptLogo]').exists();
+ }
+
+ // Page Info tag
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-pageInfoTag]')
+ .doesNotExist();
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-monitoringStatusToggle]')
+ .exists();
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-monitoringStatusInfoText]')
+ .hasText(t('storeknox.monitoringAction'));
+
+ // App info
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-appDetailsHeaderText]')
+ .hasText(t('storeknox.appDetails'));
+
+ const appDetailsInfo = [
+ {
+ id: 'developer',
+ title: t('storeknox.developer'),
+ value: inventoryAppRecord.devName,
+ },
+ {
+ id: 'email-id',
+ title: t('emailId'),
+ value: inventoryAppRecord.devEmail,
+ },
+ {
+ id: 'date-added',
+ title: t('storeknox.addedToInventoryOn'),
+ value: dayjs(inventoryAppRecord.addedOn).format('DD, MMM YYYY'),
+ },
+ ];
+
+ appDetailsInfo.forEach((info) => {
+ const infoElement = find(
+ `[data-test-storeknoxInventoryDetails-appDetailsHeader='${info.id}']`
+ );
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appDetailsHeaderInfoText]',
+ infoElement
+ )
+ .hasText(info.title);
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appDetailsHeaderInfoValue]',
+ infoElement
+ )
+ .hasText(info.value || '-');
+ });
+ }
+ );
+
+ // Check for monitoring results pending info
+ const assertMonitoringPendingInfo = (assert) => {
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-monitoringResultsPendingWithMonitoringOffIllustration]'
+ )
+ .exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-monitoringResultsPendingOffHeaderText]'
+ )
+ .hasText(t('storeknox.enableMonitoringForApp'));
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-monitoringResultsPendingOff]'
+ )
+ .hasText(t('storeknox.monitoringResultsPendingWithMonitoringOff'));
+ };
+
+ // Check for monitoring results pending info when monitoring status is off
+ const assertMonitoringPendingInfoWithMonitoringOff = (assert) => {
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-monitoringResultsPendingIllustration]'
+ )
+ .exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-monitoringResultsPendingText]'
+ )
+ .hasText(t('storeknox.monitoringResultsPending'));
+ };
+
+ test.each(
+ 'monitoring status toggle',
+ [
+ { turn_on: true },
+ { turn_on: false },
+ { turn_on: true, cannot_toggle: true },
+ ],
+ async function (assert, { turn_on, cannot_toggle }) {
+ // Models
+ if (cannot_toggle) {
+ this.currentOrganizationMe.update({
+ is_owner: false,
+ is_admin: false,
+ });
+ }
+
+ const inventoryApp = this.server.create(
+ 'sk-inventory-app',
+ 'withApprovedStatus',
+ {
+ monitoring_enabled: turn_on ? false : true,
+
+ // Meant to test the pending state illustration and text
+ store_monitoring_status: ENUMS.SK_APP_MONITORING_STATUS.PENDING,
+ }
+ );
+
+ const inventoryAppRecord = this.normalizeSKInventoryApp(inventoryApp);
+
+ // Server mocks
+ this.server.put(
+ '/v2/sk_app/:id/update_monitoring_enabled_status',
+ (schema, req) => {
+ const { monitoring_enabled } = JSON.parse(req.requestBody);
+ const app = schema.skInventoryApps.find(req.params.id);
+
+ if (turn_on) {
+ assert.true(monitoring_enabled);
+ } else {
+ assert.false(monitoring_enabled);
+ }
+
+ return {
+ ...app.toJSON(),
+ app_metadata: app.app_metadata,
+ monitoring_enabled,
+ };
+ }
+ );
+
+ await visit(
+ `/dashboard/storeknox/inventory-details/${inventoryAppRecord.id}`
+ );
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-breadcrumbContainer]')
+ .exists();
+
+ const statusToggleSelector =
+ '[data-test-storeknoxInventoryDetails-monitoringStatusToggle] [data-test-toggle-input]';
+
+ if (cannot_toggle && turn_on) {
+ assert.dom(statusToggleSelector).isNotChecked().isDisabled();
+
+ return;
+ }
+
+ if (turn_on) {
+ assert.dom(statusToggleSelector).isNotChecked();
+
+ assertMonitoringPendingInfo(assert);
+ } else {
+ assert.dom(statusToggleSelector).isChecked();
+
+ assertMonitoringPendingInfoWithMonitoringOff(assert);
+ }
+
+ await click(statusToggleSelector);
+
+ if (turn_on) {
+ assert.dom(statusToggleSelector).isChecked();
+
+ assertMonitoringPendingInfoWithMonitoringOff(assert);
+ } else {
+ assert.dom(statusToggleSelector).isNotChecked();
+
+ assertMonitoringPendingInfo(assert);
+ }
+
+ // TODO: Check chy this isn't working
+ // Check notification message
+ // const notify = this.owner.lookup('service:notifications');
+
+ // assert.strictEqual(
+ // notify.successMsg,
+ // t('storeknox.monitoring') +
+ // ` ${turn_on ? t('enabled') : t('disabled')}`
+ // );
+ }
+ );
+
+ test.skip('it renders VA results if core project latest version exists', async function () {});
+
+ test.skip('it renders upload to appknox CTA if app is not available on appknox', async function () {});
+
+ test.skip('it renders a "no access state" in latest VA Results when user does not have access to file', async function () {});
+
+ test.skip('it initiates app upload', async function () {});
+ }
+);
diff --git a/tests/acceptance/storeknox/inventory-details/brand-abuse-test.js b/tests/acceptance/storeknox/inventory-details/brand-abuse-test.js
new file mode 100644
index 000000000..675726822
--- /dev/null
+++ b/tests/acceptance/storeknox/inventory-details/brand-abuse-test.js
@@ -0,0 +1,187 @@
+import { visit, click } from '@ember/test-helpers';
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import { t } from 'ember-intl/test-support';
+import Service from '@ember/service';
+
+import { setupRequiredEndpoints } from 'irene/tests/helpers/acceptance-utils';
+
+// Notification Service
+class NotificationsStub extends Service {
+ errorMsg = null;
+ successMsg = null;
+
+ error(msg) {
+ this.errorMsg = msg;
+ }
+
+ success(msg) {
+ this.successMsg = msg;
+ }
+
+ setDefaultAutoClear() {}
+}
+
+module(
+ 'Acceptance | storeknox/inventory-details/brand-abuse',
+ function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(async function () {
+ const { organization, currentOrganizationMe } =
+ await setupRequiredEndpoints(this.server);
+
+ organization.update({
+ features: {
+ storeknox: true,
+ },
+ });
+
+ // Server mocks
+ this.server.get('v2/sk_app_detail/:id', (schema, req) => {
+ const app = schema.skInventoryApps.find(req.params.id);
+
+ return { ...app.toJSON(), app_metadata: app.app_metadata };
+ });
+
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ this.server.get('/v2/files/:id', (schema, req) => {
+ return schema.files.find(`${req.params.id}`)?.toJSON();
+ });
+
+ this.server.get('/v2/projects/:id', (schema, req) => {
+ return schema.projects.find(`${req.params.id}`)?.toJSON();
+ });
+
+ // Services
+ this.owner.register('service:notifications', NotificationsStub);
+ const store = this.owner.lookup('service:store');
+
+ const normalizeSKInventoryApp = (inventoryApp) =>
+ store.push(
+ store.normalize('sk-inventory-app', {
+ ...inventoryApp.toJSON(),
+ app_metadata: inventoryApp.app_metadata,
+ })
+ );
+
+ this.setProperties({
+ currentOrganizationMe,
+ store,
+ normalizeSKInventoryApp,
+ });
+ });
+
+ test.each(
+ 'it renders app details',
+ [{ available_on_appknox: true }, { available_on_appknox: false }],
+ async function (assert, { available_on_appknox }) {
+ const file = this.server.create('file');
+ const core_project = this.server.create('project');
+
+ // Models
+ const inventoryApp = this.server.create(
+ 'sk-inventory-app',
+ 'withApprovedStatus',
+ {
+ core_project: available_on_appknox ? core_project.id : null,
+ core_project_latest_version: available_on_appknox ? file.id : null,
+ }
+ );
+
+ const inventoryAppRecord = this.normalizeSKInventoryApp(inventoryApp);
+
+ await visit(
+ `/dashboard/storeknox/inventory-details/${inventoryApp.id}/brand-abuse`
+ );
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-breadcrumbContainer]')
+ .exists();
+
+ if (!available_on_appknox) {
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appNotPartOfAppknoxIcon]'
+ )
+ .exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appNotPartOfAppknoxIconText]'
+ )
+ .hasText(t('storeknox.appNotPartOfAppknox'));
+ }
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-appLogoImage]')
+ .hasAttribute('src', inventoryAppRecord.appMetadata.iconUrl);
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerAppTitle]')
+ .hasText(inventoryAppRecord.appMetadata.title);
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerAppPackageName]')
+ .hasText(inventoryAppRecord.appMetadata.packageName);
+
+ // App Logo tests
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerPlatformIcon]')
+ .hasClass(inventoryAppRecord.isAndroid ? /android/ : /apple/);
+
+ const storeLogoSelector = inventoryAppRecord.isAndroid
+ ? '[data-test-storeknoxInventoryDetails-playStoreLogo]'
+ : '[data-test-storeknoxInventoryDetails-appStoreLogo]';
+
+ assert.dom(storeLogoSelector).exists();
+
+ await click(storeLogoSelector);
+
+ assert
+ .dom('[data-test-storeknox-productInfoCaptionText]')
+ .hasText(t('infoCapitalCase'));
+
+ const productTitle = inventoryAppRecord.isAndroid
+ ? t('storeknox.playStore')
+ : t('storeknox.appStore');
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appIsPartOfText]')
+ .containsText(t('storeknox.appIsPartOf'))
+ .containsText(productTitle);
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appStoreLink]')
+ .hasAttribute('href', inventoryAppRecord.appMetadata.url);
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appStoreLinkBtn]')
+ .hasText(t('storeknox.checkOn') + ` ${productTitle}`);
+
+ await click(storeLogoSelector);
+
+ if (available_on_appknox) {
+ assert.dom('[data-test-storeknoxInventoryDetails-vaptLogo]').exists();
+ }
+
+ // Page Info tag
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-pageInfoTag]')
+ .exists();
+ }
+ );
+ }
+);
diff --git a/tests/acceptance/storeknox/inventory-details/malware-detected-test.js b/tests/acceptance/storeknox/inventory-details/malware-detected-test.js
new file mode 100644
index 000000000..1a403c2ee
--- /dev/null
+++ b/tests/acceptance/storeknox/inventory-details/malware-detected-test.js
@@ -0,0 +1,188 @@
+import { visit, click } from '@ember/test-helpers';
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import { t } from 'ember-intl/test-support';
+
+import Service from '@ember/service';
+
+import { setupRequiredEndpoints } from 'irene/tests/helpers/acceptance-utils';
+
+// Notification Service
+class NotificationsStub extends Service {
+ errorMsg = null;
+ successMsg = null;
+
+ error(msg) {
+ this.errorMsg = msg;
+ }
+
+ success(msg) {
+ this.successMsg = msg;
+ }
+
+ setDefaultAutoClear() {}
+}
+
+module(
+ 'Acceptance | storeknox/inventory-details/malware-detected',
+ function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(async function () {
+ const { organization, currentOrganizationMe } =
+ await setupRequiredEndpoints(this.server);
+
+ organization.update({
+ features: {
+ storeknox: true,
+ },
+ });
+
+ // Server mocks
+ this.server.get('v2/sk_app_detail/:id', (schema, req) => {
+ const app = schema.skInventoryApps.find(req.params.id);
+
+ return { ...app.toJSON(), app_metadata: app.app_metadata };
+ });
+
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ this.server.get('/v2/files/:id', (schema, req) => {
+ return schema.files.find(`${req.params.id}`)?.toJSON();
+ });
+
+ this.server.get('/v2/projects/:id', (schema, req) => {
+ return schema.projects.find(`${req.params.id}`)?.toJSON();
+ });
+
+ // Services
+ this.owner.register('service:notifications', NotificationsStub);
+ const store = this.owner.lookup('service:store');
+
+ const normalizeSKInventoryApp = (inventoryApp) =>
+ store.push(
+ store.normalize('sk-inventory-app', {
+ ...inventoryApp.toJSON(),
+ app_metadata: inventoryApp.app_metadata,
+ })
+ );
+
+ this.setProperties({
+ currentOrganizationMe,
+ store,
+ normalizeSKInventoryApp,
+ });
+ });
+
+ test.each(
+ 'it renders app details',
+ [{ available_on_appknox: true }, { available_on_appknox: false }],
+ async function (assert, { available_on_appknox }) {
+ const file = this.server.create('file');
+ const core_project = this.server.create('project');
+
+ // Models
+ const inventoryApp = this.server.create(
+ 'sk-inventory-app',
+ 'withApprovedStatus',
+ {
+ core_project: available_on_appknox ? core_project.id : null,
+ core_project_latest_version: available_on_appknox ? file.id : null,
+ }
+ );
+
+ const inventoryAppRecord = this.normalizeSKInventoryApp(inventoryApp);
+
+ await visit(
+ `/dashboard/storeknox/inventory-details/${inventoryApp.id}/malware-detected`
+ );
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-breadcrumbContainer]')
+ .exists();
+
+ if (!available_on_appknox) {
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appNotPartOfAppknoxIcon]'
+ )
+ .exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventoryDetails-appNotPartOfAppknoxIconText]'
+ )
+ .hasText(t('storeknox.appNotPartOfAppknox'));
+ }
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-appLogoImage]')
+ .hasAttribute('src', inventoryAppRecord.appMetadata.iconUrl);
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerAppTitle]')
+ .hasText(inventoryAppRecord.appMetadata.title);
+
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerAppPackageName]')
+ .hasText(inventoryAppRecord.appMetadata.packageName);
+
+ // App Logo tests
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-headerPlatformIcon]')
+ .hasClass(inventoryAppRecord.isAndroid ? /android/ : /apple/);
+
+ const storeLogoSelector = inventoryAppRecord.isAndroid
+ ? '[data-test-storeknoxInventoryDetails-playStoreLogo]'
+ : '[data-test-storeknoxInventoryDetails-appStoreLogo]';
+
+ assert.dom(storeLogoSelector).exists();
+
+ await click(storeLogoSelector);
+
+ assert
+ .dom('[data-test-storeknox-productInfoCaptionText]')
+ .hasText(t('infoCapitalCase'));
+
+ const productTitle = inventoryAppRecord.isAndroid
+ ? t('storeknox.playStore')
+ : t('storeknox.appStore');
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appIsPartOfText]')
+ .containsText(t('storeknox.appIsPartOf'))
+ .containsText(productTitle);
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appStoreLink]')
+ .hasAttribute('href', inventoryAppRecord.appMetadata.url);
+
+ assert
+ .dom('[data-test-storeknox-productInfo-appStoreLinkBtn]')
+ .hasText(t('storeknox.checkOn') + ` ${productTitle}`);
+
+ await click(storeLogoSelector);
+
+ if (available_on_appknox) {
+ assert.dom('[data-test-storeknoxInventoryDetails-vaptLogo]').exists();
+ }
+
+ // Page Info tag
+ assert
+ .dom('[data-test-storeknoxInventoryDetails-pageInfoTag]')
+ .exists();
+ }
+ );
+ }
+);
diff --git a/tests/acceptance/storeknox/inventory/app-list-test.js b/tests/acceptance/storeknox/inventory/app-list-test.js
new file mode 100644
index 000000000..2bd64839e
--- /dev/null
+++ b/tests/acceptance/storeknox/inventory/app-list-test.js
@@ -0,0 +1,402 @@
+import { visit, findAll, find, triggerEvent, click } from '@ember/test-helpers';
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import { t } from 'ember-intl/test-support';
+import { Response } from 'miragejs';
+import Service from '@ember/service';
+
+import { setupRequiredEndpoints } from 'irene/tests/helpers/acceptance-utils';
+import { compareInnerHTMLWithIntlTranslation } from 'irene/tests/test-utils';
+import ENUMS from 'irene/enums';
+
+// Notification Service
+class NotificationsStub extends Service {
+ errorMsg = null;
+ successMsg = null;
+
+ error(msg) {
+ this.errorMsg = msg;
+ }
+
+ success(msg) {
+ this.successMsg = msg;
+ }
+
+ setDefaultAutoClear() {}
+}
+
+module('Acceptance | storeknox/inventory/app-list', function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(async function () {
+ const { organization, currentOrganizationMe } =
+ await setupRequiredEndpoints(this.server);
+
+ organization.update({
+ features: {
+ storeknox: true,
+ },
+ });
+
+ // Server mocks
+ this.server.get('v2/sk_app', () => {
+ return {
+ count: 0,
+ next: null,
+ previous: null,
+ results: [],
+ };
+ });
+
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ // Services
+ this.owner.register('service:notifications', NotificationsStub);
+
+ this.setProperties({ currentOrganizationMe });
+ });
+
+ test.each(
+ 'it renders without approved apps',
+ [{ is_owner: true }, { is_owner: false }],
+ async function (assert, { is_owner }) {
+ this.currentOrganizationMe.update({ is_owner });
+
+ await visit('/dashboard/storeknox/inventory/app-list');
+
+ assert
+ .dom('[data-test-storeknoxInventory-inventoryHeaderText]')
+ .hasText(t('storeknox.inventoryHeader'));
+
+ assert
+ .dom('[data-test-storeknoxInventory-inventoryDescriptionText]')
+ .hasText(t('storeknox.inventoryDescription'));
+
+ assert
+ .dom('[data-test-storeknoxInventory-discoveryPageLink]')
+ .hasText(t('storeknox.discoverHeader'));
+
+ // Tab items check
+ const tabItems = [
+ {
+ id: 'app-inventory',
+ label: t('storeknox.appInventory'),
+ },
+
+ // Pending Review apps tab should be displayed for only owners
+ this.currentOrganizationMe.is_owner && {
+ id: 'pending-review',
+ label: t('storeknox.pendingReview'),
+ },
+ ];
+
+ tabItems
+ .filter(Boolean)
+ .forEach((tab) =>
+ assert
+ .dom(`[data-test-storeknoxInventory-tabs='${tab.id}-tab']`)
+ .hasText(tab.label)
+ );
+
+ // Empty state
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-appListTable-tableEmptyIllustration]'
+ )
+ .exists();
+
+ assert
+ .dom('[data-test-storeknoxInventory-appListTable-tableEmptyHeaderText]')
+ .hasText(t('storeknox.noRequestedAppsFound'));
+
+ compareInnerHTMLWithIntlTranslation(assert, {
+ message: t('storeknox.noRequestedAppsFoundDescription'),
+ selector:
+ '[data-test-storeknoxInventory-appListTable-tableEmptyHeaderDescription]',
+ });
+
+ // Settings toggle check
+ if (is_owner) {
+ assert
+ .dom('[data-test-storeknoxInventory-settingsDrawerTrigger]')
+ .exists();
+ } else {
+ assert
+ .dom('[data-test-storeknoxInventory-settingsDrawerTrigger]')
+ .doesNotExist();
+ }
+ }
+ );
+
+ test.each(
+ 'it toggles adding appknox app to inventory by default if user is owner',
+ [
+ { fail: true },
+ {
+ fail: false,
+ error: { detail: 'disconnect error' },
+ },
+ ],
+ async function (assert, { fail, error }) {
+ assert.expect();
+
+ this.owner.register('service:notifications', NotificationsStub);
+
+ // Models
+ this.currentOrganizationMe.update({ is_owner: true });
+
+ const selectedSkOrg = this.server.create('sk-organization', {
+ id: 4,
+ add_appknox_project_to_inventory_by_default: false,
+ });
+
+ // Server Mocks
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ this.server.patch('/v2/sk_organization/:id', (schema, req) => {
+ if (fail) {
+ return new Response(400, {}, error);
+ }
+
+ const { add_appknox_project_to_inventory_by_default } = JSON.parse(
+ req.requestBody
+ );
+
+ // Request is made by the selected org
+ assert.strictEqual(selectedSkOrg.id, req.params.id);
+
+ // Toggle value is opposite of what is default
+ assert.strictEqual(
+ add_appknox_project_to_inventory_by_default,
+ !selectedSkOrg.add_appknox_project_to_inventory_by_default
+ );
+
+ const skOrg = schema.db.skOrganizations.update(`${req.params.id}`, {
+ add_appknox_project_to_inventory_by_default,
+ });
+
+ return skOrg;
+ });
+
+ // Test Start
+ await visit('/dashboard/storeknox/inventory/app-list');
+
+ assert
+ .dom('[data-test-storeknoxInventory-settingsDrawerTrigger]')
+ .exists();
+
+ await click('[data-test-storeknoxInventory-settingsDrawerTrigger]');
+
+ assert
+ .dom('[data-test-storeknoxInventory-settingsDrawer-closeIconBtn]')
+ .exists();
+
+ assert
+ .dom('[data-test-storeknoxInventory-settingsDrawer-title]')
+ .hasText(t('storeknox.inventorySettings'));
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-settingsDrawer-addAppknoxProjectsByDefaultTitle]'
+ )
+ .hasText(t('storeknox.addAppknoxProjectsByDefault'));
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-settingsDrawer-addAppknoxProjectsByDefaultDesc]'
+ )
+ .hasText(t('storeknox.addAppknoxProjectsByDefaultToggleDescription'));
+
+ const addAppknoxProjectsByDefaultToggle =
+ '[data-test-storeknoxInventory-settingsDrawer-addAppknoxProjectsByDefaultToggle] [data-test-toggle-input]';
+
+ assert.dom(addAppknoxProjectsByDefaultToggle).isNotChecked();
+
+ await click(addAppknoxProjectsByDefaultToggle);
+
+ assert.dom(addAppknoxProjectsByDefaultToggle).isChecked();
+
+ // const notify = this.owner.lookup('service:notifications');
+
+ // if (fail) {
+ // assert.strictEqual(
+ // notify.successMsg,
+ // t('storeknox.addAppknoxProjectsByDefaultSuccessMessage')
+ // );
+ // } else {
+ // assert.strictEqual(notify.errorMsg, error.detail);
+ // }
+ }
+ );
+
+ test.each(
+ 'it renders with approved apps (With Action Required and No Action Required)',
+ [
+ {
+ with_action_required: true,
+ store_monitoring_status: ENUMS.SK_APP_MONITORING_STATUS.UNSCANNED,
+ },
+ {
+ with_action_required: false,
+ store_monitoring_status: ENUMS.SK_APP_MONITORING_STATUS.PENDING,
+ },
+ ],
+ async function (assert, { with_action_required, store_monitoring_status }) {
+ const inventoryApps = this.server.createList(
+ 'sk-inventory-app',
+ 3,
+ 'withApprovedStatus',
+ { store_monitoring_status }
+ );
+
+ // Server mocks
+ this.server.get('v2/sk_app', (schema, req) => {
+ const { app_status, approval_status } = req.queryParams;
+
+ const inventoryApps = schema.skInventoryApps
+ .where(
+ (a) =>
+ a.app_status === Number(app_status) &&
+ a.approval_status === Number(approval_status)
+ )
+ .models.map((a) => ({
+ ...a.toJSON(),
+ app_metadata: a.app_metadata,
+ }));
+
+ return {
+ count: inventoryApps.length,
+ next: null,
+ previous: null,
+ results: inventoryApps,
+ };
+ });
+
+ await visit('/dashboard/storeknox/inventory/app-list');
+
+ const appElementList = findAll(
+ '[data-test-storeknoxInventory-appListTable-row]'
+ );
+
+ // Contains the right number of apps
+ assert.strictEqual(appElementList.length, inventoryApps.length);
+
+ // Sanity check for rendered apps
+ for (let index = 0; index < inventoryApps.length; index++) {
+ const iApp = inventoryApps[index];
+
+ const iAppElement = find(
+ `[data-test-storeknoxInventory-appListTable-rowId='${iApp.id}']`
+ );
+
+ const metadata = iApp.app_metadata;
+
+ if (iApp.platform === ENUMS.PLATFORM.ANDROID) {
+ assert
+ .dom(
+ '[data-test-storeknoxTableColumns-store-playStoreIcon]',
+ iAppElement
+ )
+ .exists();
+ }
+
+ if (iApp.platform === ENUMS.PLATFORM.IOS) {
+ assert
+ .dom('[data-test-storeknoxTableColumns-store-iosIcon]', iAppElement)
+ .exists();
+ }
+
+ assert
+ .dom(
+ '[data-test-storeknoxTableColumns-applicationDevName]',
+ iAppElement
+ )
+ .hasText(metadata.dev_name);
+
+ assert
+ .dom(
+ '[data-test-storeknoxTableColumns-applicationDevEmail]',
+ iAppElement
+ )
+ .hasText(metadata.dev_email);
+
+ assert
+ .dom(
+ '[data-test-storeknoxTableColumns-applicationTitle]',
+ iAppElement
+ )
+ .hasText(metadata.title);
+
+ assert
+ .dom(
+ '[data-test-storeknoxTableColumns-applicationPackageName]',
+ iAppElement
+ )
+ .hasText(metadata.package_name);
+
+ assert
+ .dom('[data-test-applogo-img]', iAppElement)
+ .hasAttribute('src', metadata.icon_url);
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-appListTable-monitoringStatusIcon]',
+ iAppElement
+ )
+ .hasClass(with_action_required ? /error/ : /success/)
+ .hasClass(with_action_required ? /info/ : /check-circle/);
+
+ // Check for needs action tooltip
+ const tooltipTrigger =
+ '[data-test-storeknoxInventory-appListTable-monitoringStatusIcon]';
+
+ const needsActionTooltipTriggerElement = find(tooltipTrigger);
+
+ const tooltipContentSelector = '[data-test-ak-tooltip-content]';
+
+ await triggerEvent(needsActionTooltipTriggerElement, 'mouseenter');
+
+ assert.dom(tooltipContentSelector).exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-appListTable-monitoringStatusTooltipHeaderText]'
+ )
+ .hasText(t('reason'));
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-appListTable-monitoringStatusTooltipText]'
+ )
+ .containsText(
+ with_action_required
+ ? t('storeknox.actionsNeededMsg')
+ : t('storeknox.noActionsNeededMsg')
+ )
+ .containsText(t('storeknox.haveBeenDetected'));
+
+ await triggerEvent(needsActionTooltipTriggerElement, 'mouseleave');
+ }
+ }
+ );
+});
diff --git a/tests/acceptance/storeknox/inventory/pending-review-test.js b/tests/acceptance/storeknox/inventory/pending-review-test.js
new file mode 100644
index 000000000..bb2385a28
--- /dev/null
+++ b/tests/acceptance/storeknox/inventory/pending-review-test.js
@@ -0,0 +1,449 @@
+import { visit, findAll, find, triggerEvent, click } from '@ember/test-helpers';
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import { t } from 'ember-intl/test-support';
+import Service from '@ember/service';
+import dayjs from 'dayjs';
+
+import { setupRequiredEndpoints } from 'irene/tests/helpers/acceptance-utils';
+import ENUMS from 'irene/enums';
+
+// Notification Service
+class NotificationsStub extends Service {
+ errorMsg = null;
+ successMsg = null;
+
+ error(msg) {
+ this.errorMsg = msg;
+ }
+
+ success(msg) {
+ this.successMsg = msg;
+ }
+
+ setDefaultAutoClear() {}
+}
+
+module('Acceptance | storeknox/inventory/pending-review', function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(async function () {
+ const { organization, currentOrganizationMe } =
+ await setupRequiredEndpoints(this.server);
+
+ // Models
+ organization.update({
+ features: {
+ storeknox: true,
+ },
+ });
+
+ currentOrganizationMe.update({ is_owner: true });
+
+ this.server.create('sk-organization');
+
+ // Services
+ this.owner.register('service:notifications', NotificationsStub);
+
+ this.setProperties({ currentOrganizationMe });
+ });
+
+ test('it renders with empty state', async function (assert) {
+ this.server.create('sk-organization');
+
+ // Server mocks
+ this.server.get('/v2/sk_app', () => {
+ return {
+ count: 0,
+ next: null,
+ previous: null,
+ results: [],
+ };
+ });
+
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ // Test start
+ await visit('/dashboard/storeknox/inventory/pending-reviews');
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewEmptyIllustration]')
+ .exists();
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewEmptyHeaderText]')
+ .hasText(t('storeknox.noPendingItems'));
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewEmptyDescriptionText]')
+ .hasText(t('storeknox.noPendingItemsDescription'));
+ });
+
+ test.each(
+ 'it renders with pending review apps',
+ [
+ {
+ manual_discovery: true,
+ app_source: ENUMS.SK_DISCOVERY.MANUAL,
+ },
+ {
+ auto_discovery: true,
+ app_source: ENUMS.SK_DISCOVERY.AUTO,
+ },
+ ],
+ async function (assert, { manual_discovery, app_source }) {
+ // Models
+ const added_by = this.server.create('organization-user');
+
+ const pendingReviewApp = this.server.create(
+ 'sk-app',
+ {
+ app_source,
+ added_by: manual_discovery ? added_by.id : null,
+ },
+ 'withPendingReviewStatus'
+ );
+
+ // Server mocks
+ this.server.get('v2/sk_app', (schema, req) => {
+ const { app_status, approval_status } = req.queryParams;
+
+ const skApps = schema.skApps
+ .where(
+ (a) =>
+ a.app_status === Number(app_status) &&
+ a.approval_status === Number(approval_status)
+ )
+ .models.map((a) => ({
+ ...a.toJSON(),
+ app_metadata: a.app_metadata,
+ }));
+
+ return {
+ count: skApps.length,
+ next: null,
+ previous: null,
+ results: skApps,
+ };
+ });
+
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ // Test start
+ await visit('/dashboard/storeknox/inventory/pending-reviews');
+
+ const appElementList = findAll(
+ '[data-test-storeknoxInventory-pendingReviewTable-row]'
+ );
+
+ // Contains the right number of apps
+ assert.strictEqual(appElementList.length, 1);
+
+ const metadata = pendingReviewApp.app_metadata;
+
+ const prAppElement = find(
+ `[data-test-storeknoxInventory-pendingReviewTable-rowId='${pendingReviewApp.id}']`
+ );
+
+ if (pendingReviewApp.platform === ENUMS.PLATFORM.ANDROID) {
+ assert
+ .dom(
+ '[data-test-storeknoxTableColumns-store-playStoreIcon]',
+ prAppElement
+ )
+ .exists();
+ }
+
+ if (pendingReviewApp.platform === ENUMS.PLATFORM.IOS) {
+ assert
+ .dom('[data-test-storeknoxTableColumns-store-iosIcon]', prAppElement)
+ .exists();
+ }
+
+ assert
+ .dom('[data-test-storeknoxTableColumns-applicationTitle]', prAppElement)
+ .hasText(metadata.title);
+
+ assert
+ .dom(
+ '[data-test-storeknoxTableColumns-applicationPackageName]',
+ prAppElement
+ )
+ .hasText(metadata.package_name);
+
+ assert
+ .dom('[data-test-applogo-img]', prAppElement)
+ .hasAttribute('src', metadata.icon_url);
+
+ // Requester Checks
+ if (manual_discovery) {
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-pendingReviewTable-requesterEmail]'
+ )
+ .hasText(added_by.email);
+ } else {
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-pendingReviewTable-systemRequested]'
+ )
+ .hasText(t('system'));
+ }
+
+ // Check for needs action tooltip
+ const tooltipTrigger =
+ '[data-test-storeknoxInventory-pendingReviewTable-requestByInfoTrigger]';
+
+ const requesterInfoTooltipTriggerElement = find(tooltipTrigger);
+ const tooltipContentSelector = '[data-test-ak-tooltip-content]';
+
+ await triggerEvent(requesterInfoTooltipTriggerElement, 'mouseenter');
+
+ assert.dom(tooltipContentSelector).exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-pendingReviewTable-requestedOnHeaderText]'
+ )
+ .hasText(t('requestedOn'));
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewTable-addedOnDate]')
+ .hasText(
+ dayjs(pendingReviewApp.added_on).format('MMMM D, YYYY, HH:mm')
+ );
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewTable-foundByHeader]')
+ .hasText(t('type'));
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewTable-foundBy]')
+ .hasText(
+ manual_discovery
+ ? t('storeknox.manualDiscovery')
+ : t('storeknox.autoDiscovery')
+ );
+
+ await triggerEvent(requesterInfoTooltipTriggerElement, 'mouseleave');
+
+ // Approve/Reject Btns
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewTable-approveBtn]')
+ .exists();
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewTable-approveBtnIcon]')
+ .exists();
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewTable-rejectBtn]')
+ .exists();
+
+ assert
+ .dom('[data-test-storeknoxInventory-pendingReviewTable-rejectBtnIcon]')
+ .exists();
+ }
+ );
+
+ test.each(
+ 'it rejects/accepts an app',
+ [
+ {
+ reject: true,
+ approval_status: ENUMS.SK_APPROVAL_STATUS.REJECTED,
+ approval_status_display: 'REJECTED',
+ },
+ {
+ accept: true,
+ approval_status: ENUMS.SK_APPROVAL_STATUS.APPROVED,
+ approval_status_display: 'APPROVED',
+ },
+ ],
+ async function (
+ assert,
+ { reject, accept, approval_status, approval_status_display }
+ ) {
+ // Models
+ const pendingReviewApps = this.server.createList(
+ 'sk-app',
+ 3,
+ {
+ added_by: this.server.create('organization-user').id,
+ },
+ 'withPendingReviewStatus'
+ );
+
+ const appToRejectOrAccept = pendingReviewApps[0];
+
+ // Server mocks
+ this.server.get('v2/sk_app', (schema, req) => {
+ const { app_status, approval_status } = req.queryParams;
+
+ const skApps = schema.skApps
+ .where(
+ (a) =>
+ a.app_status === Number(app_status) &&
+ a.approval_status === Number(approval_status)
+ )
+ .models.map((a) => ({
+ ...a.toJSON(),
+ app_metadata: a.app_metadata,
+ }));
+
+ return {
+ count: skApps.length,
+ next: null,
+ previous: null,
+ results: skApps,
+ };
+ });
+
+ this.server.get('/v2/sk_organization', (schema) => {
+ const skOrganizations = schema.skOrganizations.all().models;
+
+ return {
+ count: skOrganizations.length,
+ next: null,
+ previous: null,
+ results: skOrganizations,
+ };
+ });
+
+ this.server.put(
+ '/v2/sk_app/:id/update_approval_status',
+ (schema, req) => {
+ const reqBody = JSON.parse(req.requestBody);
+ const skAppId = req.params.id;
+
+ // Check if correct app is being rejected or approved
+ assert.strictEqual(appToRejectOrAccept.id, skAppId);
+ assert.strictEqual(reqBody.approval_status, Number(approval_status));
+
+ // Update DB to reflect app update
+ schema.db.skApps.update(`${req.params.id}`, {
+ approval_status,
+ });
+
+ return {
+ id: skAppId,
+ approval_status: reqBody.approval_status,
+ approval_status_display,
+ };
+ }
+ );
+
+ // Test start
+ await visit('/dashboard/storeknox/inventory/pending-reviews');
+
+ let pendingReviewAppElements = findAll(
+ '[data-test-storeknoxInventory-pendingReviewTable-row]'
+ );
+
+ assert.strictEqual(
+ pendingReviewAppElements.length,
+ pendingReviewApps.length
+ );
+
+ const appToRejectOrAcceptElement = find(
+ `[data-test-storeknoxInventory-pendingReviewTable-rowId='${appToRejectOrAccept.id}']`
+ );
+
+ // Reject an app
+ if (reject) {
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-pendingReviewTable-rejectBtn]',
+ appToRejectOrAcceptElement
+ )
+ .exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-pendingReviewTable-rejectBtnIcon]',
+ appToRejectOrAcceptElement
+ )
+ .exists();
+
+ await click(
+ appToRejectOrAcceptElement.querySelector(
+ '[data-test-storeknoxInventory-pendingReviewTable-rejectBtn]'
+ )
+ );
+ }
+
+ // Approve an app
+ if (accept) {
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-pendingReviewTable-approveBtn]',
+ appToRejectOrAcceptElement
+ )
+ .exists();
+
+ assert
+ .dom(
+ '[data-test-storeknoxInventory-pendingReviewTable-approveBtnIcon]',
+ appToRejectOrAcceptElement
+ )
+ .exists();
+
+ await click(
+ appToRejectOrAcceptElement.querySelector(
+ '[data-test-storeknoxInventory-pendingReviewTable-approveBtn]'
+ )
+ );
+ }
+
+ // Check list again
+ pendingReviewAppElements = findAll(
+ '[data-test-storeknoxInventory-pendingReviewTable-row]'
+ );
+
+ assert.strictEqual(
+ pendingReviewAppElements.length,
+ pendingReviewApps.length - 1
+ );
+
+ assert
+ .dom(
+ `[data-test-storeknoxInventory-pendingReviewTable-rowId='${appToRejectOrAccept.id}']`
+ )
+ .doesNotExist();
+
+ // Check notification message
+ const notify = this.owner.lookup('service:notifications');
+
+ const appName = {
+ appName: appToRejectOrAccept.app_metadata.title,
+ };
+
+ assert.strictEqual(
+ notify.successMsg,
+ reject
+ ? t('storeknox.appRejected', appName)
+ : t('storeknox.appAddedToInventory', appName)
+ );
+ }
+ );
+});
diff --git a/translations/en.json b/translations/en.json
index 88a7b29a3..52f0112ec 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -625,6 +625,7 @@
"inactiveCapital": "Inactive",
"includeInactiveMembers": "Include inactive users",
"info": "Info",
+ "infoCapitalCase": "INFO",
"installAppknoxPluginTo": "Install plugin to ",
"inProgress": "In Progress",
"integrateAppknoxTo": "Integrate to ",
@@ -1585,14 +1586,22 @@
"malwareDetected": "Malware Detected",
"unscannedVersion": "Unscanned Version",
"brandAbuse": "Brand Abuse",
+ "monitoring": "Monitoring",
"appNotInAppknoxHeader": "This app has never been uploaded to Appknox.",
"appNotInAppknoxDescription": "It is strongly recommended to upload this to Appknox to perform the Vulnerability Assessment.",
"monitoringResultsPending": "Monitoring activity for this newly added app has been initiated. Please come back after some time to see the results.",
+ "enableMonitoringForApp": "Enable monitoring for this app",
+ "monitoringResultsPendingWithMonitoringOff": "Use the toggle on this page to enable monitoring of this app. Once done, come back to this page after some time to view the results.",
"storeMonitoringOverview": "Store Monitoring Overview",
"inventorySettings": "Inventory Settings",
"addAppknoxProjectsByDefault": "Add Appknox Projects to Inventory by default",
"addAppknoxProjectsByDefaultSuccessMessage": "Status Updated Successfully.",
- "addAppknoxProjectsByDefaultToggleDescription": "Turning this toggle ON would ensure any new app/namespace uploaded in your Appknox account, reflects in your Org Inventory automatically"
+ "addAppknoxProjectsByDefaultToggleDescription": "Turning this toggle ON would ensure any new app/namespace uploaded in your Appknox account, reflects in your Org Inventory automatically",
+ "monitoringAction": "Monitoring Action",
+ "brandAbuseFeatureUnavailableHeader": "Coming Soon - Detection of Brand Abuse",
+ "brandAbuseFeatureUnavailableSubText": "Identify fake apps that are trying to mimicking your apps across multiple stores.",
+ "malwareDetectedFeatureUnavailableHeader": "Unlock the Malware feature soon",
+ "malwareDetectedFeatureUnavailableSubText": "Identify fake apps that are trying to mimicking your apps across multiple stores."
},
"storeLowercase": "store",
"submit": "Submit",
diff --git a/translations/ja.json b/translations/ja.json
index 1f81cbf9e..3766a2279 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -625,6 +625,7 @@
"inactiveCapital": "Inactive",
"includeInactiveMembers": "Include inactive users",
"info": "Info",
+ "infoCapitalCase": "INFO",
"installAppknoxPluginTo": "Install plugin to ",
"inProgress": "In Progress",
"integrateAppknoxTo": "Integrate to ",
@@ -1585,14 +1586,22 @@
"malwareDetected": "Malware Detected",
"unscannedVersion": "Unscanned Version",
"brandAbuse": "Brand Abuse",
+ "monitoring": "Monitoring",
"appNotInAppknoxHeader": "This app has never been uploaded to Appknox.",
"appNotInAppknoxDescription": "It is strongly recommended to upload this to Appknox to perform the Vulnerability Assessment.",
"monitoringResultsPending": "Monitoring activity for this newly added app has been initiated. Please come back after some time to see the results.",
+ "enableMonitoringForApp": "Enable monitoring for this app",
+ "monitoringResultsPendingWithMonitoringOff": "Use the toggle on this page to enable monitoring of this app. Once done, come back to this page after some time to view the results.",
"storeMonitoringOverview": "Store Monitoring Overview",
"inventorySettings": "Inventory Settings",
"addAppknoxProjectsByDefault": "Add Appknox Projects to Inventory by default",
"addAppknoxProjectsByDefaultSuccessMessage": "Status Updated Successfully.",
- "addAppknoxProjectsByDefaultToggleDescription": "Turning this toggle ON would ensure any new app/namespace uploaded in your Appknox account, reflects in your Org Inventory automatically"
+ "addAppknoxProjectsByDefaultToggleDescription": "Turning this toggle ON would ensure any new app/namespace uploaded in your Appknox account, reflects in your Org Inventory automatically",
+ "monitoringAction": "Monitoring Action",
+ "brandAbuseFeatureUnavailableHeader": "Coming Soon - Detection of Brand Abuse",
+ "brandAbuseFeatureUnavailableSubText": "Identify fake apps that are trying to mimicking your apps across multiple stores.",
+ "malwareDetectedFeatureUnavailableHeader": "Unlock the Malware feature soon",
+ "malwareDetectedFeatureUnavailableSubText": "Identify fake apps that are trying to mimicking your apps across multiple stores."
},
"storeLowercase": "store",
"submit": "Submit",
diff --git a/types/ak-svg.d.ts b/types/ak-svg.d.ts
index 548d2b704..b7feab6b6 100644
--- a/types/ak-svg.d.ts
+++ b/types/ak-svg.d.ts
@@ -77,7 +77,8 @@ export enum AkSvgComponentInvocationByNames {
SoxMalwareFeatureAbsence,
SoxUnscannedDetailsTableEmpty,
SoxUnscannedHistoryTableEmpty,
- SoxMonitoringPending,
+ SoxMonitoringResultsPending,
+ SoxMonitoringResultsPendingWithMonitoringOff,
}
export enum AkSvgComponentInvocationByPaths {
diff --git a/types/global.d.ts b/types/global.d.ts
index 5fc763513..633b26d83 100644
--- a/types/global.d.ts
+++ b/types/global.d.ts
@@ -63,9 +63,23 @@ declare module '@glint/environment-ember-loose/registry' {
Return: boolean;
}>;
+ gt: HelperLike<{
+ Args: {
+ Positional: [string | number, string | number];
+ };
+ Return: boolean;
+ }>;
+
+ or: HelperLike<{
+ Args: {
+ Positional: (string | number | boolean)[];
+ };
+ Return: boolean;
+ }>;
+
and: HelperLike<{
Args: {
- Positional: unknown[];
+ Positional: (string | number | boolean)[];
};
Return: boolean;
}>;