@studyportals/fawkes 8.7.2-9 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,21 +23,23 @@ export class AreaAttendance extends OrganisationsSeoIndexabilityPolicy {
23
23
  async generateUrls() {
24
24
  const areaFragments = AreaPresenter.getInstance().getFragments();
25
25
  const attendanceFragments = AttendancePresenter.getInstance().getFragments();
26
- const paths = [];
27
- for (const area of areaFragments) {
28
- for (const attendance of attendanceFragments) {
29
- const filterKeyValues = new Map([
30
- [FilterKey.AREA, [area.id]],
31
- [FilterKey.COUNTRY, [area.countryId]],
32
- [FilterKey.DELIVERY_METHOD, [attendance.id]]
33
- ]);
34
- const result = await this.checkRulesForSitemap(filterKeyValues);
35
- if (result) {
36
- paths.push(this.getPathWithSortingOption(`${attendance.path}/${area.path}`));
37
- }
26
+ // Create array of promises for all combinations to parallelize API calls
27
+ const promises = areaFragments.flatMap(area => attendanceFragments.map(async (attendance) => {
28
+ const filterKeyValues = new Map([
29
+ [FilterKey.AREA, [area.id]],
30
+ [FilterKey.COUNTRY, [area.countryId]],
31
+ [FilterKey.DELIVERY_METHOD, [attendance.id]]
32
+ ]);
33
+ const result = await this.checkRulesForSitemap(filterKeyValues);
34
+ if (result) {
35
+ return this.getPathWithSortingOption(`${attendance.path}/${area.path}`);
38
36
  }
39
- }
40
- return paths;
37
+ return null;
38
+ }));
39
+ // Wait for all promises to resolve in parallel
40
+ const results = await Promise.all(promises);
41
+ // Filter out null values and return paths
42
+ return results.filter((path) => path !== null);
41
43
  }
42
44
  get filterCombination() {
43
45
  return FilterCombinations.AREA_ATTENDANCE;
@@ -25,19 +25,21 @@ export class CountryAttendance extends OrganisationsSeoIndexabilityPolicy {
25
25
  async generateUrls() {
26
26
  const countryFragments = CountryPresenter.getInstance().getFragments();
27
27
  const attendanceFragments = AttendancePresenter.getInstance().getFragments();
28
- const paths = [];
29
- for (const country of countryFragments) {
30
- for (const attendance of attendanceFragments) {
31
- const filterKeyValues = new Map([
32
- [FilterKey.COUNTRY, [country.id]],
33
- [FilterKey.DELIVERY_METHOD, [attendance.id]]
34
- ]);
35
- const result = await this.checkRulesForSitemap(filterKeyValues);
36
- if (result) {
37
- paths.push(this.getPathWithSortingOption(`${attendance.path}/${country.path}`));
38
- }
28
+ // Create array of promises for all combinations to parallelize API calls
29
+ const promises = countryFragments.flatMap(country => attendanceFragments.map(async (attendance) => {
30
+ const filterKeyValues = new Map([
31
+ [FilterKey.COUNTRY, [country.id]],
32
+ [FilterKey.DELIVERY_METHOD, [attendance.id]]
33
+ ]);
34
+ const result = await this.checkRulesForSitemap(filterKeyValues);
35
+ if (result) {
36
+ return this.getPathWithSortingOption(`${attendance.path}/${country.path}`);
39
37
  }
40
- }
41
- return paths;
38
+ return null;
39
+ }));
40
+ // Wait for all promises to resolve in parallel
41
+ const results = await Promise.all(promises);
42
+ // Filter out null values and return paths
43
+ return results.filter((path) => path !== null);
42
44
  }
43
45
  }
@@ -4,6 +4,7 @@ import { SingleValueSelectedForFilterRule } from '../../../common/rules/SingleVa
4
4
  import { OnlyFiltersSelectedRule } from '../../../common/rules/OnlyFiltersSelectedRule';
5
5
  import { RankedOrganisationsSeoIndexabilityPolicy } from '../RankedOrganisationsSeoIndexabilityPolicy';
6
6
  import { IOrganisationsSeoDependencies } from '../../../organisations/types/IOrganisationsSeoDependencies';
7
+ /** @deprecated Don't use this policy as it's been removed from indexability manager. */
7
8
  export declare class RankedAreaDiscipline extends RankedOrganisationsSeoIndexabilityPolicy {
8
9
  readonly name: string;
9
10
  readonly description: string;
@@ -6,6 +6,7 @@ import { OnlyFiltersSelectedRule } from '../../../common/rules/OnlyFiltersSelect
6
6
  import { AreaPresenter } from '../../../presenters/AreaPresenter';
7
7
  import { DisciplinePresenter } from '../../../presenters/DisciplinePresenter';
8
8
  import { RankedOrganisationsSeoIndexabilityPolicy } from '../RankedOrganisationsSeoIndexabilityPolicy';
9
+ /** @deprecated Don't use this policy as it's been removed from indexability manager. */
9
10
  export class RankedAreaDiscipline extends RankedOrganisationsSeoIndexabilityPolicy {
10
11
  name = 'Ranked Area Discipline Policy';
11
12
  description = 'Controls indexing for ranking-sorted pages filtered by geographic areas, optimizing for region-specific content with quality-based result ordering.';
@@ -25,6 +25,7 @@ export class RankedAttendanceDiscipline extends RankedOrganisationsSeoIndexabili
25
25
  const attendanceFragments = AttendancePresenter.getInstance().getFragments();
26
26
  const disciplineFragments = DisciplinePresenter.getInstance().getFragments();
27
27
  const paths = [];
28
+ // Process sequentially to respect API rate limits
28
29
  for (const attendance of attendanceFragments) {
29
30
  for (const discipline of disciplineFragments) {
30
31
  const filterKeyValues = new Map([
@@ -23,20 +23,22 @@ export class RankedContinentAttendance extends RankedOrganisationsSeoIndexabilit
23
23
  async generateUrls() {
24
24
  const continentFragments = ContinentPresenter.getInstance().getFragments();
25
25
  const attendanceFragments = AttendancePresenter.getInstance().getFragments();
26
- const paths = [];
27
- for (const continent of continentFragments) {
28
- for (const attendance of attendanceFragments) {
29
- const filterKeyValues = new Map([
30
- [FilterKey.CONTINENT, [continent.id]],
31
- [FilterKey.DELIVERY_METHOD, [attendance.id]],
32
- ]);
33
- const result = await this.checkRulesForSitemap(filterKeyValues);
34
- if (result) {
35
- paths.push(this.getPathWithSortingOption(`${attendance.path}/${continent.path}`));
36
- }
26
+ // Create array of promises for all combinations to parallelize API calls
27
+ const promises = continentFragments.flatMap(continent => attendanceFragments.map(async (attendance) => {
28
+ const filterKeyValues = new Map([
29
+ [FilterKey.CONTINENT, [continent.id]],
30
+ [FilterKey.DELIVERY_METHOD, [attendance.id]],
31
+ ]);
32
+ const result = await this.checkRulesForSitemap(filterKeyValues);
33
+ if (result) {
34
+ return this.getPathWithSortingOption(`${attendance.path}/${continent.path}`);
37
35
  }
38
- }
39
- return paths;
36
+ return null;
37
+ }));
38
+ // Wait for all promises to resolve in parallel
39
+ const results = await Promise.all(promises);
40
+ // Filter out null values and return paths
41
+ return results.filter((path) => path !== null);
40
42
  }
41
43
  get filterCombination() {
42
44
  return FilterCombinations.RANKED_CONTINENT_ATTENDANCE;
@@ -22,6 +22,7 @@ export class RankedCountryAttendance extends RankedOrganisationsSeoIndexabilityP
22
22
  const countryFragments = CountryPresenter.getInstance().getFragments();
23
23
  const attendanceFragments = AttendancePresenter.getInstance().getFragments();
24
24
  const paths = [];
25
+ // Process sequentially to respect API rate limits
25
26
  for (const country of countryFragments) {
26
27
  for (const attendance of attendanceFragments) {
27
28
  const filterKeyValues = new Map([
@@ -5,6 +5,7 @@ import { OnlyFiltersSelectedRule } from '../../../common/rules/OnlyFiltersSelect
5
5
  import { CountryPresenter } from '../../../presenters/CountryPresenter';
6
6
  import { DisciplinePresenter } from '../../../presenters/DisciplinePresenter';
7
7
  import { RankedOrganisationsSeoIndexabilityPolicy } from '../RankedOrganisationsSeoIndexabilityPolicy';
8
+ const DEFAULT_CONCURRENT_REQUESTS = 20; // Maximum number of concurrent requests to avoid rate limiting
8
9
  export class RankedCountryDiscipline extends RankedOrganisationsSeoIndexabilityPolicy {
9
10
  name = 'Ranked Country Discipline Policy';
10
11
  description = 'Determines which ranking-sorted pages at the intersection of country and discipline filters qualify for indexing based on search value.';
@@ -17,22 +18,37 @@ export class RankedCountryDiscipline extends RankedOrganisationsSeoIndexabilityP
17
18
  super(dependencies);
18
19
  }
19
20
  async generateUrls() {
20
- const countryFragments = CountryPresenter.getInstance().getFragments();
21
- const disciplineFragments = DisciplinePresenter.getInstance().getFragments();
22
- const paths = [];
23
- for (const country of countryFragments) {
24
- for (const discipline of disciplineFragments) {
25
- const filterKeyValues = new Map([
26
- [FilterKey.COUNTRY, [country.id]],
27
- [FilterKey.DISCIPLINES, [discipline.id]],
28
- ]);
29
- const result = await this.checkRulesForSitemap(filterKeyValues);
30
- if (result) {
31
- paths.push(this.getPathWithSortingOption(`${country.path}/${discipline.path}`));
32
- }
21
+ try {
22
+ const countryFragments = CountryPresenter.getInstance().getFragments();
23
+ const disciplineFragments = DisciplinePresenter.getInstance().getFragments();
24
+ const paths = [];
25
+ const configuredConcurrency = Number.parseInt(process.env.SITEMAP_COMBINATION_CONCURRENCY
26
+ || DEFAULT_CONCURRENT_REQUESTS.toString(), 10);
27
+ const concurrencyLimit = Math.max(1, configuredConcurrency); // Ensure at least 1
28
+ // Create all combinations
29
+ const combinations = countryFragments.flatMap(country => disciplineFragments.map(discipline => ({ country, discipline })));
30
+ // Process in batches to respect API rate limits
31
+ for (let i = 0; i < combinations.length; i += concurrencyLimit) {
32
+ const batch = combinations.slice(i, i + concurrencyLimit);
33
+ const results = await Promise.all(batch.map(async ({ country, discipline }) => {
34
+ const filterKeyValues = new Map([
35
+ [FilterKey.COUNTRY, [country.id]],
36
+ [FilterKey.DISCIPLINES, [discipline.id]]
37
+ ]);
38
+ const result = await this.checkRulesForSitemap(filterKeyValues);
39
+ if (result) {
40
+ return this.getPathWithSortingOption(`${country.path}/${discipline.path}`);
41
+ }
42
+ return null;
43
+ }));
44
+ paths.push(...results.filter((path) => path !== null));
33
45
  }
46
+ return paths;
47
+ }
48
+ catch (error) {
49
+ console.error('Error generating URLs for RankedCountryDiscipline:', error);
50
+ return [];
34
51
  }
35
- return paths;
36
52
  }
37
53
  get filterCombination() {
38
54
  return FilterCombinations.RANKED_DISCIPLINE_COUNTRY;
@@ -15,18 +15,22 @@ export class RankedDiscipline extends RankedOrganisationsSeoIndexabilityPolicy {
15
15
  super(dependencies);
16
16
  }
17
17
  async generateUrls() {
18
- const disciplineFragments = DisciplinePresenter.getInstance().getFragments();
19
- const paths = [];
20
- for (const discipline of disciplineFragments) {
21
- const filterKeyValues = new Map([
22
- [FilterKey.DISCIPLINES, [discipline.id]]
23
- ]);
24
- const result = await this.checkRulesForSitemap(filterKeyValues);
25
- if (result) {
26
- paths.push(this.getPathWithSortingOption(discipline.path));
18
+ try {
19
+ const disciplineFragments = DisciplinePresenter.getInstance().getFragments();
20
+ const paths = [];
21
+ for (const discipline of disciplineFragments) {
22
+ const filterKeyValues = new Map([[FilterKey.DISCIPLINES, [discipline.id]]]);
23
+ const result = await this.checkRulesForSitemap(filterKeyValues);
24
+ if (result) {
25
+ paths.push(this.getPathWithSortingOption(discipline.path));
26
+ }
27
27
  }
28
+ return paths;
29
+ }
30
+ catch (error) {
31
+ console.error('Error generating URLs for RankedDiscipline:', error);
32
+ return [];
28
33
  }
29
- return paths;
30
34
  }
31
35
  get filterCombination() {
32
36
  return FilterCombinations.RANKED_DISCIPLINE;
@@ -9,11 +9,17 @@ export class RankedUnfiltered extends RankedOrganisationsSeoIndexabilityPolicy {
9
9
  super(dependencies);
10
10
  }
11
11
  async generateUrls() {
12
- const filterKeyValues = new Map([]);
13
- if (await this.checkRulesForSitemap(filterKeyValues)) {
14
- return [this.getPathWithSortingOption('')];
12
+ try {
13
+ const filterKeyValues = new Map([]);
14
+ if (await this.checkRulesForSitemap(filterKeyValues)) {
15
+ return [this.getPathWithSortingOption('')];
16
+ }
17
+ return [];
18
+ }
19
+ catch (error) {
20
+ console.error('Error generating URLs for RankedUnfiltered:', error);
21
+ return [];
15
22
  }
16
- return [];
17
23
  }
18
24
  get filterCombination() {
19
25
  return FilterCombinations.RANKED_UNFILTERED;
@@ -14,6 +14,10 @@ export class MinimumAmountOfRankedResultsRule {
14
14
  if (!this.searchApiClient) {
15
15
  throw new DependencyMissingError('SearchApiClient');
16
16
  }
17
+ if (!this.searchApiClient.getCountWithRankings) {
18
+ const filterKeys = [...filterKeyValues.keys()];
19
+ throw new DependencyMissingError(`CountwithRankings ${filterKeys.join(',')}`);
20
+ }
17
21
  const rankedResultsCount = await this.searchApiClient.getCountWithRankings(filterKeyValues);
18
22
  return rankedResultsCount >= this.minimumRankedResultsCount;
19
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@studyportals/fawkes",
3
- "version": "8.7.2-9",
3
+ "version": "9.0.0",
4
4
  "description": "A package to centralize SEO related logic for SBLP and Sitemap Generator.",
5
5
  "files": [
6
6
  "./dist"
@@ -91,7 +91,8 @@
91
91
  },
92
92
  "dependencies": {
93
93
  "@studyportals/domain-client": "^8.1.0",
94
- "@studyportals/search-api-interface": "^5.9.2",
94
+ "@studyportals/ranking-api-interface": "^2.4.0",
95
+ "@studyportals/search-api-interface": "5.9.3-0",
95
96
  "@studyportals/search-filters": "^6.3.1",
96
97
  "@studyportals/static-domain-data": "^6.1.0"
97
98
  },