Skip to content

Commit

Permalink
Update Analytics setup to display all available accounts and properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
ankitrox committed Oct 1, 2024
1 parent 4da92e2 commit 3ac8acc
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 24 deletions.
108 changes: 102 additions & 6 deletions assets/js/modules/analytics-4/datastore/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,40 @@ import { createFetchStore } from '../../../googlesitekit/data/create-fetch-store
import { actions as errorStoreActions } from '../../../googlesitekit/data/create-error-store';
import { createValidatedAction } from '../../../googlesitekit/data/utils';
import { isValidAccountSelection } from '../utils/validation';
import { caseInsensitiveListSort } from '../../../util/sort';

const { receiveError, clearError, clearErrors } = errorStoreActions;

const fetchGetAccountSummariesStore = createFetchStore( {
baseName: 'getAccountSummaries',
controlCallback() {
controlCallback( params ) {
const { pageToken = '' } = params || {};
return API.get(
'modules',
'analytics-4',
'account-summaries',
{},
{
pageToken,
},
{
useCache: false,
}
);
},
reducerCallback( state, accountSummaries ) {
return { ...state, accountSummaries };
argsToParams: ( args ) => {
return { pageToken: args?.pageToken ?? '' };
},
reducerCallback( state, response ) {
const { accountSummaries: newAccountSummaries } = response;
const mergedAccountSummaries = [
...( state.accountSummaries || [] ),
...newAccountSummaries,
];

return {
...state,
accountSummaries: mergedAccountSummaries,
};
},
} );

Expand Down Expand Up @@ -93,6 +109,8 @@ const fetchCreateAccountStore = createFetchStore( {
const START_SELECTING_ACCOUNT = 'START_SELECTING_ACCOUNT';
const FINISH_SELECTING_ACCOUNT = 'FINISH_SELECTING_ACCOUNT';
const RESET_ACCOUNT_SUMMARIES = 'RESET_ACCOUNT_SUMMARIES';
const TRANSFORM_AND_SORT_ACCOUNT_SUMMARIES =
'TRANSFORM_AND_SORT_ACCOUNT_SUMMARIES';

const baseInitialState = {
accountSummaries: undefined,
Expand Down Expand Up @@ -234,11 +252,66 @@ const baseActions = {

return matchedAccount || null;
},

*transformAndSortAccountSummaries() {
const registry = yield commonActions.getRegistry();
let accountSummaries = [
...registry.select( MODULES_ANALYTICS_4 ).getAccountSummaries(),
]; // Create a shallow copy to avoid mutating the original array.

const filterAccountWithIds = ( account, idKey = 'account' ) => {
const obj = { ...account };

const matches = account[ idKey ].match( /accounts\/([^/]+)/ );
if ( matches ) {
obj._id = matches[ 1 ];
}

return obj;
};

const filterPropertyWithIds = ( property, idKey = 'property' ) => {
const obj = { ...property };

const propertyMatches =
property[ idKey ]?.match( /properties\/([^/]+)/ );
if ( propertyMatches ) {
obj._id = propertyMatches[ 1 ];
}

const accountMatches =
property.parent?.match( /accounts\/([^/]+)/ );
if ( accountMatches ) {
obj._accountID = accountMatches[ 1 ];
}

return obj;
};

accountSummaries = accountSummaries.map( ( account ) => {
const obj = filterAccountWithIds( account, 'account' );
obj.propertySummaries = account.propertySummaries.map(
( property ) => filterPropertyWithIds( property, 'property' )
);

return obj;
} );

const sortedAccountSummaries = caseInsensitiveListSort(
accountSummaries,
'displayName'
);

yield {
type: TRANSFORM_AND_SORT_ACCOUNT_SUMMARIES,
payload: sortedAccountSummaries,
};
},
};

const baseControls = {};

const baseReducer = ( state, { type } ) => {
const baseReducer = ( state, { type, payload } ) => {
switch ( type ) {
case START_SELECTING_ACCOUNT: {
return {
Expand Down Expand Up @@ -268,6 +341,13 @@ const baseReducer = ( state, { type } ) => {
};
}

case TRANSFORM_AND_SORT_ACCOUNT_SUMMARIES: {
return {
...state,
accountSummaries: payload,
};
}

default: {
return state;
}
Expand All @@ -277,12 +357,28 @@ const baseReducer = ( state, { type } ) => {
const baseResolvers = {
*getAccountSummaries() {
const registry = yield commonActions.getRegistry();
let nextPageToken = null;
const summaries = registry
.select( MODULES_ANALYTICS_4 )
.getAccountSummaries();

// Fetch initial account summaries if they are undefined.
if ( summaries === undefined ) {
yield fetchGetAccountSummariesStore.actions.fetchGetAccountSummaries();
const { response } =
yield fetchGetAccountSummariesStore.actions.fetchGetAccountSummaries();
nextPageToken = response?.nextPageToken || null;
}

// Continue fetching additional summaries if nextPageToken exists.
while ( nextPageToken ) {
const { response } =
yield fetchGetAccountSummariesStore.actions.fetchGetAccountSummaries(
{ pageToken: nextPageToken }
);
nextPageToken = response?.nextPageToken || null;
}

yield baseActions.transformAndSortAccountSummaries();
},
};

Expand Down
35 changes: 35 additions & 0 deletions assets/js/util/sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Sorting functions.
*
* Site Kit by Google, Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Sorts the provided list in a case-insensitive manner.
*
* @since n.e.x.t
*
* @param {Array} listToSort The list to sort (Array of objects or arrays).
* @param {string} orderBy The field by which the list should be ordered.
* @return {Array} The sorted list.
*/
export function caseInsensitiveListSort( listToSort, orderBy ) {
return listToSort.sort( ( a, b ) => {
const nameA = a[ orderBy ]?.toLowerCase() || '';
const nameB = b[ orderBy ]?.toLowerCase() || '';

return nameA.localeCompare( nameB );
} );
}
88 changes: 88 additions & 0 deletions assets/js/util/sort.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Sorting functions tests.
*
* Site Kit by Google, Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Internal dependencies
*/
import { caseInsensitiveListSort } from './sort';

describe( 'caseInsensitiveListSort', () => {
it( 'should sort an array of objects by a given field (case-insensitive)', () => {
const listToSort = [
{ name: 'Banana' },
{ name: 'apple' },
{ name: 'Cherry' },
];

const sortedList = caseInsensitiveListSort( listToSort, 'name' );

expect( sortedList ).toEqual( [
{ name: 'apple' },
{ name: 'Banana' },
{ name: 'Cherry' },
] );
} );

it( 'should sort an array of arrays by a given field index (case-insensitive)', () => {
const listToSort = [
[ 'Banana', 2 ],
[ 'apple', 1 ],
[ 'Cherry', 3 ],
];

const sortedList = caseInsensitiveListSort( listToSort, 0 );

expect( sortedList ).toEqual( [
[ 'apple', 1 ],
[ 'Banana', 2 ],
[ 'Cherry', 3 ],
] );
} );

it( 'should return the list unchanged if already sorted', () => {
const listToSort = [
{ name: 'apple' },
{ name: 'Banana' },
{ name: 'Cherry' },
];

const sortedList = caseInsensitiveListSort( listToSort, 'name' );

expect( sortedList ).toEqual( [
{ name: 'apple' },
{ name: 'Banana' },
{ name: 'Cherry' },
] );
} );

it( 'should return an empty array if the list is empty', () => {
const listToSort = [];

const sortedList = caseInsensitiveListSort( listToSort, 'name' );

expect( sortedList ).toEqual( [] );
} );

it( 'should return the original list if non-objects/non-arrays are passed', () => {
const listToSort = [ 1, 2, 3 ];

const sortedList = caseInsensitiveListSort( listToSort, 'name' );

expect( sortedList ).toEqual( [ 1, 2, 3 ] );
} );
} );
27 changes: 9 additions & 18 deletions includes/Modules/Analytics_4.php
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,12 @@ protected function create_data_request( Data_Request $data ) {
case 'GET:accounts':
return $this->get_service( 'analyticsadmin' )->accounts->listAccounts();
case 'GET:account-summaries':
return $this->get_service( 'analyticsadmin' )->accountSummaries->listAccountSummaries( array( 'pageSize' => 200 ) );
return $this->get_service( 'analyticsadmin' )->accountSummaries->listAccountSummaries(
array(
'pageSize' => 2,
'pageToken' => $data['pageToken'] ?? '',
)
);
case 'GET:ads-links':
if ( empty( $data['propertyID'] ) ) {
throw new Missing_Required_Param_Exception( 'propertyID' );
Expand Down Expand Up @@ -1666,23 +1671,9 @@ protected function parse_data_response( Data_Request $data, $response ) {
case 'GET:accounts':
return array_map( array( self::class, 'filter_account_with_ids' ), $response->getAccounts() );
case 'GET:account-summaries':
$account_summaries = array_map(
function ( $account ) {
$obj = self::filter_account_with_ids( $account, 'account' );
$obj->propertySummaries = array_map( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
function ( $property ) {
return self::filter_property_with_ids( $property, 'property' );
},
$account->getPropertySummaries()
);

return $obj;
},
$response->getAccountSummaries()
);
return Sort::case_insensitive_list_sort(
$account_summaries,
'displayName'
return array(
'accountSummaries' => $response->getAccountSummaries(),
'nextPageToken' => $response->getNextPageToken(),
);
case 'GET:ads-links':
return (array) $response->getGoogleAdsLinks();
Expand Down

0 comments on commit 3ac8acc

Please sign in to comment.