Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions pkg/handler/lokiclientmock/loki_client_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (o *LokiClientMock) Get(url string) ([]byte, int, error) {
if isLabel {
path = "mocks/loki/namespaces.json"
} else {
if strings.Contains(url, "query=topk") {
if strings.Contains(url, "query=topk") || strings.Contains(url, "query=bottomk") {
path = "mocks/loki/flow_metrics"

if strings.Contains(url, "|unwrap%20PktDrop") {
Expand Down Expand Up @@ -72,8 +72,32 @@ func (o *LokiClientMock) Get(url string) ([]byte, int, error) {

mlog.Debugf("Reading file path: %s", path)
file, err := os.ReadFile(path)
mlog.Debugf("here")
if err != nil {
return nil, 500, err
// return nil, 500, err
emptyResponse := []byte(`{
"status": "success",
"data": {
"resultType": "matrix",
"result": []
}
}`)

var qr model.QueryResponse
err = json.Unmarshal(emptyResponse, &qr)
if err != nil {
return nil, 500, err
}
for _, s := range qr.Data.Result.(model.Streams) {
for i := range s.Entries {
s.Entries[i].Line = decoders.NetworkEventsToString(s.Entries[i].Line)
}
}
emptyResponse, err = json.Marshal(qr)
if err != nil {
return nil, 500, err
}
return emptyResponse, 200, nil
}

if parseNetEvents {
Expand Down
4 changes: 4 additions & 0 deletions web/cypress/e2e/overview/overview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ describe('netflow-overview', () => {
cy.get('#overview-panels-modal').contains('Select all').click();

//Save
cy.setupNetworkIdleTracking('GET', '/api/flow/metrics*');
cy.get('#overview-panels-modal').contains('Save').click();

cy.waitForNetworkIdle();

cy.checkPanels(c.availablePanelsCount);

//reopen modal
Expand Down
4 changes: 3 additions & 1 deletion web/cypress/e2e/table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ describe('netflow-table', () => {

cy.addFilter('src_namespace', c.namespace);
cy.addFilter('src_name', c.pod);
cy.changeQueryOption('Show duplicates');
cy.changeQueryOption('1000');
cy.clickShowDuplicates();
cy.changeTimeRange('Last 1 day');
});



it('manage columns', () => {
//first open modal
cy.openColumnsModal();
Expand Down
124 changes: 101 additions & 23 deletions web/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,11 @@ Cypress.Commands.add('changeTimeRange', (name, topology) => {
cy.checkContent(topology);
});

Cypress.Commands.add('changeMetricFunction', (name) => {
Cypress.Commands.add('clickShowDuplicates', () => {
cy.showAdvancedOptions();
cy.showDisplayOptions();

cy.get('#metricFunction-dropdown').click();
cy.get('.pf-v5-c-menu__content').contains(name).click();
cy.get('[data-layer-id="default"]').children().its('length').should('be.gte', 5);
cy.get('#table-display-dropdown').contains('Show duplicates').click();
cy.checkContent();
});

Cypress.Commands.add('changeMetricType', (name) => {
Expand All @@ -191,27 +189,107 @@ Cypress.Commands.add('checkRecordField', (field, name, values) => {
});
});


Cypress.Commands.add('setupNetworkIdleTracking', (method: string = 'GET', urlPattern: string = '/api/**') => {
cy.wrap({
requestCount: 0,
lastRequestTime: 0,
startTime: Date.now()
}).as('networkIdleTracker');

cy.intercept(method, urlPattern, (req) => {
cy.get('@networkIdleTracker').then((tracker: Cypress.networkIdleTracker) => {
tracker.requestCount++;
tracker.lastRequestTime = Date.now();
});
req.continue();
}).as('networkIdleActivity');
});


Cypress.Commands.add('waitForNetworkIdle', (idleTime: number = 3000, timeout: number = 120000) => {
cy.get('@networkIdleTracker', { timeout: timeout })
.then((tracker: Cypress.networkIdleTracker) => {
const startTime = Date.now();

const checkIdleCondition = () => {
const now = Date.now();

if (tracker.requestCount > 0 && (now - tracker.lastRequestTime) >= idleTime) {
return true;
}

if (tracker.requestCount === 0 && (now - startTime) >= idleTime) {
return true;
}

if (now - startTime > timeout) {
throw new Error('Timed out waiting for network idle.');
}

return false;
};

const pollUntilIdle = () => {
const isIdle = checkIdleCondition();

if (isIdle) {
return;
} else {
return cy.wait(1000, { log: false }).then(pollUntilIdle);
}
};

return cy.then(pollUntilIdle);
});
});

declare global {
namespace Cypress {
interface Chainable {
openNetflowTrafficPage(clearCache?: boolean): Chainable<void>
showAdvancedOptions(): Chainable<Element>
showDisplayOptions(): Chainable<Element>
checkPanels(panels?: number): Chainable<Element>
openPanelsModal(): Chainable<Element>
checkColumns(groups?: number, cols?: number): Chainable<Element>
openColumnsModal(): Chainable<Element>
selectPopupItems(id: string, names: string[]): Chainable<Element>
checkPopupItems(id: string, ids: string[]): Chainable<Element>
sortColumn(name: string): Chainable<Element>
dropdownSelect(id: string, name: string): Chainable<Element>
checkContent(topology?: boolean): Chainable<Element>
addFilter(filter: string, value: string, topology?: boolean): Chainable<Element>
changeQueryOption(name: string, topology?: boolean): Chainable<Element>
changeTimeRange(name: string, topology?: boolean): Chainable<Element>
changeMetricFunction(name: string): Chainable<Element>
changeMetricType(name: string): Chainable<Element>
checkRecordField(field: string, name: string, values: string[])
showAdvancedOptions(): Chainable<void>
showDisplayOptions(): Chainable<void>
checkPanels(panels?: number): Chainable<void>
openPanelsModal(): Chainable<void>
checkColumns(groups?: number, cols?: number): Chainable<void>
openColumnsModal(): Chainable<void>
selectPopupItems(id: string, names: string[]): Chainable<void>
checkPopupItems(id: string, ids: string[]): Chainable<void>
sortColumn(name: string): Chainable<void>
dropdownSelect(id: string, name: string): Chainable<void>
checkContent(topology?: boolean): Chainable<void>
addFilter(filter: string, value: string, topology?: boolean): Chainable<void>
changeQueryOption(name: string, topology?: boolean): Chainable<void>
changeTimeRange(name: string, topology?: boolean): Chainable<void>
changeMetricType(name: string): Chainable<void>
checkRecordField(field: string, name: string, values: string[]): Chainable<void>
clickShowDuplicates():Chainable<void>

/**
* Sets up network interception to track active requests for idle detection.
* This command *must* be called before `cy.waitForNetworkIdle`
*
* @param method HTTP method to intercept (default: 'GET')
* @param urlPattern URL pattern to intercept (default: '/api/**')
*/
setupNetworkIdleTracking(method?: string, urlPattern?: string): Chainable<void>

/**
* Waits until no intercepted requests (matching the patterns
* set in `setupNetworkIdleTracking`) have been active for `idleTime`,
* or until the `timeout` is reached.
*
* @param idleTime How long the network must be idle (in ms)
* @param timeout Total time to wait before timing out (in ms)
*/
waitForNetworkIdle(idleTime?: number, timeout?: number): Chainable<void>

networkIdleTracker: {
requestCount: number;
lastRequestTime: number;
startTime: number;
};
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the method signature definitions in here:

declare global {
  namespace Cypress {
    interface Chainable {

Seems to not be matching the signatures of the function declaratinons. For example:

Cypress.Commands.add('changeMetricType', (name) => {
 cy.showAdvancedOptions();
 cy.showDisplayOptions();
 cy.get('#metricType-dropdown').click();
 cy.get('.pf-v5-c-menu__content').contains(name).click();
 cy.get('[data-layer-id="default"]').children().its('length').should('be.gte', 5);
});

has not return, but the return type of Chainable<Element> is defined in the interface.

Am I missing something or is this wrong?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that looks like bad copy pastes 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, I guess our custom commands have that problem too :) , mostly from copy pasting the signatures :). We mostly use custom commands to abstract set of common of UI interactions.

thanks for spotting that.

}
}
}