Skip to content

Commit

Permalink
Merge pull request #110 from atlassian-labs/COMPASS-21754-trim-gitlab…
Browse files Browse the repository at this point in the history
…-labels

Ensure only up to 20 labels are added when creating or updating components
  • Loading branch information
ezhong2 authored Aug 22, 2024
2 parents ed06f0c + 2a90847 commit fc44862
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 60 deletions.
6 changes: 3 additions & 3 deletions src/client/compass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import graphqlGateway, {
UnLinkComponentInput,
} from '@atlassian/forge-graphql';
import { ImportableProject, COMPASS_GATEWAY_MESSAGES, Metric, Team } from '../types';
import { EXTERNAL_SOURCE, IMPORT_LABEL } from '../constants';
import { EXTERNAL_SOURCE, IMPORT_LABEL, MAX_LABELS_LENGTH } from '../constants';
import { UNKNOWN_EXTERNAL_ALIAS_ERROR_MESSAGE } from '../models/error-messages';
import { AggClientError, GraphqlGatewayError } from '../models/errors';
import { getTenantContextQuery } from './get-tenat-context-query';
import { aggQuery } from './agg';
import { getTeamsQuery } from './get-teams-query';
import { formatLabels } from '../utils/format-labels';
import { formatLabels } from '../utils/labels-utils';

const throwIfErrors = function throwIfSdkErrors(method: string, errors: SdkError[]) {
// Checking if any invalid config errors to report.
Expand All @@ -39,7 +39,7 @@ const throwIfErrors = function throwIfSdkErrors(method: string, errors: SdkError

export const createComponent = async (cloudId: string, project: ImportableProject): Promise<Component | never> => {
const { name, description, typeId, labels, url, ownerId } = project;
const formattedLabels = formatLabels(labels);
const formattedLabels = formatLabels(labels).slice(0, MAX_LABELS_LENGTH - 1);
const component = {
name,
description,
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const MAX_DESCRIPTION_LENGTH = 1000;
export const EXTERNAL_SOURCE = 'gitlab-importer';
export const IMPORT_LABEL = 'source:gitlab';
export const MAX_LINKS_OF_TYPE = 5;
export const MAX_LABELS_LENGTH = 20;
export const BACK_OFF: Partial<IBackOffOptions> = {
startingDelay: 300,
timeMultiple: 3,
Expand Down
7 changes: 4 additions & 3 deletions src/resolvers/import-queue-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { storage } from '@forge/api';
import { backOff, IBackOffOptions } from 'exponential-backoff';

import { createComponent, updateComponent } from '../client/compass';
import { STORAGE_KEYS, BACK_OFF, IMPORT_LABEL } from '../constants';
import { STORAGE_KEYS, BACK_OFF, IMPORT_LABEL, MAX_LABELS_LENGTH } from '../constants';
import { appendLink } from '../utils/append-link';
import { ImportableProject } from '../resolverTypes';
import { sleep } from '../utils/time-utils';
import { createMRWithCompassYML } from '../services/create-mr-with-compass-yml';
import { formatLabels } from '../utils/format-labels';
import { formatLabels } from '../utils/labels-utils';

const backOffConfig: Partial<IBackOffOptions> = {
startingDelay: BACK_OFF.startingDelay,
Expand Down Expand Up @@ -68,7 +68,8 @@ resolver.define('import', async (req) => {
await createMRWithCompassYML(project, component.id, groupId);
}
} else if (hasComponent && !(isCompassFilePrOpened && isManaged)) {
const formattedLabels = formatLabels(labels);
const formattedLabels = formatLabels(labels).slice(0, MAX_LABELS_LENGTH - 1);

const component = {
name,
description,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ describe('syncComponent', () => {

await syncComponent(...syncPayload);

const expectedLabels = [...MOCK_COMPONENT_LABELS, IMPORT_LABEL, ...MOCK_GET_PROJECT_LABELS];
const expectedLabels = ['source:gitlab', 'koko', 'label', 'language:javascript', 'momo'];

expect(mockUpdateComponent).toBeCalledWith(
expect.objectContaining({
Expand Down
10 changes: 3 additions & 7 deletions src/services/sync-component-with-file/sync-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { CompassLinkType, Component, ConfigFileMetadata } from '@atlassian/forge
import yaml from 'js-yaml';
import { ComponentSyncDetails, ComponentSyncPayload, PushEvent } from '../../types';
import { reportSyncError } from './report-sync-error';
import { EXTERNAL_SOURCE, IMPORT_LABEL } from '../../constants';
import { EXTERNAL_SOURCE } from '../../constants';
import { syncComponentWithFile, updateComponent } from '../../client/compass';
import { getProjectLabels } from '../get-labels';
import { getProjectById } from '../../client/gitlab';
import { hasLastSyncEvent } from '../../utils/push-event-utils';
import { formatLabels } from '../../utils/format-labels';
import { mergeLabels } from '../../utils/labels-utils';

const getFileUrl = (filePath: string, event: PushEvent, branchName: string) => {
return `${event.project.web_url}/blob/${branchName}/${filePath}`;
Expand Down Expand Up @@ -57,11 +57,7 @@ export const syncComponent = async (
const { topics } = await getProjectById(token, event.project.id);
const projectLabels = await getProjectLabels(event.project.id, token, topics);

const formattedLabels = formatLabels(projectLabels);

const labels = currentComponent.labels
? [...currentComponent.labels, IMPORT_LABEL, ...formattedLabels]
: [IMPORT_LABEL, ...formattedLabels];
const labels = mergeLabels(projectLabels, currentComponent.labels);

await updateComponent({
currentComponent,
Expand Down
39 changes: 0 additions & 39 deletions src/utils/format-labels.test.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/utils/format-labels.ts

This file was deleted.

88 changes: 88 additions & 0 deletions src/utils/labels-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { IMPORT_LABEL, MAX_LABELS_LENGTH } from '../constants';
import { formatLabels, mergeLabels } from './labels-utils';

describe('formatLabels', () => {
it('should format labels correctly', () => {
const input = ['Example Label', 'AnotherExampleLabelThatIsWayTooLongAndShouldBeTruncated'];
const expectedOutput = ['example-label', 'anotherexamplelabelthatiswaytoolongandsh'];

const result = formatLabels(input);

expect(result).toEqual(expectedOutput);
});

it('should handle empty array', () => {
const input: string[] = [];
const expectedOutput: string[] = [];

const result = formatLabels(input);

expect(result).toEqual(expectedOutput);
});

it('should not alter labels shorter than 40 characters', () => {
const input = ['short', 'medium length label'];
const expectedOutput = ['short', 'medium-length-label'];

const result = formatLabels(input);

expect(result).toEqual(expectedOutput);
});

it('should convert spaces to hyphens and lowercase all characters', () => {
const input = ['Mixed CASE Label', 'Label With Spaces'];
const expectedOutput = ['mixed-case-label', 'label-with-spaces'];

const result = formatLabels(input);

expect(result).toEqual(expectedOutput);
});
});

describe('mergeLabels', () => {
it('should return an array with IMPORT_LABEL when both projectLabels and currentLabels are empty', () => {
const result = mergeLabels([], []);
expect(result).toEqual([IMPORT_LABEL]);
});

it('should return formatted projectLabels with IMPORT_LABEL when currentLabels is empty', () => {
const projectLabels = ['Label1', 'Label2'];
const result = mergeLabels(projectLabels, []);
expect(result).toEqual([IMPORT_LABEL, 'label1', 'label2']);
});

it('should return formatted currentLabels with IMPORT_LABEL when projectLabels is empty', () => {
const currentLabels = ['label2', 'label3'];
const result = mergeLabels([], currentLabels);
expect(result).toEqual([IMPORT_LABEL, 'label2', 'label3']);
});

it('should merge and deduplicate labels from projectLabels and currentLabels, including IMPORT_LABEL', () => {
const projectLabels = ['Label1', 'Label2'];
const currentLabels = ['label2', 'label3'];
const result = mergeLabels(projectLabels, currentLabels);
expect(result).toEqual([IMPORT_LABEL, 'label1', 'label2', 'label3']);
});

it('should return up to MAX_LABELS_LENGTH labels including IMPORT_LABEL', () => {
const projectLabels = Array.from({ length: 15 }, (_, i) => `Label${i + 1}`);
const currentLabels = Array.from({ length: 10 }, (_, i) => `label${i + 16}`);
const result = mergeLabels(projectLabels, currentLabels);
expect(result.length).toBe(MAX_LABELS_LENGTH);
expect(result).toContain(IMPORT_LABEL);
});

it('should include IMPORT_LABEL if not present in the combined labels', () => {
const projectLabels = ['Label1', 'Label2'];
const currentLabels = ['label3', 'label4'];
const result = mergeLabels(projectLabels, currentLabels);
expect(result).toContain(IMPORT_LABEL);
});

it('should not duplicate IMPORT_LABEL if it is already present', () => {
const projectLabels = ['Label1', 'Label2'];
const currentLabels = ['label3', IMPORT_LABEL];
const result = mergeLabels(projectLabels, currentLabels);
expect(result.filter((label) => label === IMPORT_LABEL).length).toBe(1);
});
});
29 changes: 29 additions & 0 deletions src/utils/labels-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { IMPORT_LABEL, MAX_LABELS_LENGTH } from '../constants';

// Converts the topics from a GitLab project to a list of formatted labels for a Compass component.
export const formatLabels = (labels: string[]): string[] => {
return labels.map((label) => {
const transformedLabel = label.split(' ').join('-').toLowerCase();
return transformedLabel.length > 40 ? transformedLabel.slice(0, 40) : transformedLabel;
});
};

// Helper function to merge the project labels with the current labels
export function mergeLabels(projectLabels: string[], currentLabels: string[]): string[] {
const formattedLabels = formatLabels(projectLabels);
const labels = currentLabels ? [...currentLabels, ...formattedLabels] : formattedLabels;

// Deduplicate and sort the labels for consistency
const uniqueLabels = Array.from(new Set(labels));
uniqueLabels.sort();

// Get up to 20 labels
let trimmedLabels = uniqueLabels.slice(0, MAX_LABELS_LENGTH);

// If trimmedLabels doesn't include IMPORT_LABEL, add it to the trimmed labels
if (!trimmedLabels.includes(IMPORT_LABEL)) {
trimmedLabels = [IMPORT_LABEL, ...trimmedLabels].slice(0, MAX_LABELS_LENGTH);
}

return trimmedLabels;
}

0 comments on commit fc44862

Please sign in to comment.