Refresh Jira and Linear status after credential errors (#5169)
* fix: address pr-bug-scan validated finding from #4683 Isolated CredentialDecryptionError per-item in Linear getClients (client.ts:518) and Jira getClients (client.ts:373) on the 'all' selection so one bad credential no longer collapses healthy workspaces * Refresh Jira and Linear status to clear stale credential errors Ensure stale credential decryption errors are cleared from the store status once a successful API read completes. By updating the check in shouldRefreshStatusAfterRead to trigger when a credentialError is currently set, successful issue or list fetches will trigger a status check and remove stale error flags. --------- Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
This commit is contained in:
parent
fcc0afa8cd
commit
845ea2c68c
|
|
@ -226,4 +226,78 @@ describe('createJiraSlice credential errors', () => {
|
||||||
await expect(store.getState().fetchJiraIssue('ALP-1', 'site-1')).resolves.toBeNull()
|
await expect(store.getState().fetchJiraIssue('ALP-1', 'site-1')).resolves.toBeNull()
|
||||||
expect(jiraStatus).toHaveBeenCalled()
|
expect(jiraStatus).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('refreshes status after all-site Jira list reads partially succeed', async () => {
|
||||||
|
const store = createTestStore()
|
||||||
|
const error = new Error(credentialDecryptionMessage('Jira'))
|
||||||
|
store.setState({
|
||||||
|
jiraStatus: { connected: true, viewer: null, selectedSiteId: 'all' }
|
||||||
|
})
|
||||||
|
jiraListIssues.mockResolvedValueOnce([issue('ALP-1')])
|
||||||
|
jiraStatus.mockResolvedValueOnce({
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedSiteId: 'all',
|
||||||
|
credentialError: error.message
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(store.getState().listJiraIssues('assigned', 30)).resolves.toMatchObject([
|
||||||
|
{ key: 'ALP-1' }
|
||||||
|
])
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(store.getState().jiraStatus.credentialError).toBe(error.message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears stale Jira credential errors after successful site list reads', async () => {
|
||||||
|
const store = createTestStore()
|
||||||
|
const staleError = credentialDecryptionMessage('Jira')
|
||||||
|
store.setState({
|
||||||
|
jiraStatus: {
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedSiteId: 'site-1',
|
||||||
|
credentialError: staleError
|
||||||
|
}
|
||||||
|
})
|
||||||
|
jiraListIssues.mockResolvedValueOnce([issue('ALP-1')])
|
||||||
|
jiraStatus.mockResolvedValueOnce({
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedSiteId: 'site-1'
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(store.getState().listJiraIssues('assigned', 30)).resolves.toMatchObject([
|
||||||
|
{ key: 'ALP-1' }
|
||||||
|
])
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(store.getState().jiraStatus.credentialError).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears stale Jira credential errors after successful issue detail reads', async () => {
|
||||||
|
const store = createTestStore()
|
||||||
|
const staleError = credentialDecryptionMessage('Jira')
|
||||||
|
store.setState({
|
||||||
|
jiraStatus: {
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedSiteId: 'site-1',
|
||||||
|
credentialError: staleError
|
||||||
|
}
|
||||||
|
})
|
||||||
|
jiraGetIssue.mockResolvedValueOnce(issue('ALP-1'))
|
||||||
|
jiraStatus.mockResolvedValueOnce({
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedSiteId: 'site-1'
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(store.getState().fetchJiraIssue('ALP-1', 'site-1')).resolves.toMatchObject({
|
||||||
|
key: 'ALP-1'
|
||||||
|
})
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(store.getState().jiraStatus.credentialError).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,15 @@ function getSelectedSiteId(status: JiraConnectionStatus): JiraSiteSelection | nu
|
||||||
return status.selectedSiteId ?? status.activeSiteId ?? null
|
return status.selectedSiteId ?? status.activeSiteId ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldRefreshStatusAfterRead(
|
||||||
|
siteId: JiraSiteSelection | null | undefined,
|
||||||
|
status: JiraConnectionStatus
|
||||||
|
): boolean {
|
||||||
|
// Why: 'all' reads can hide per-site decrypt failures, and a visible
|
||||||
|
// credential error may have been cleared by a successful credential read.
|
||||||
|
return siteId === 'all' || status.credentialError !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
function clearJiraInflight(): void {
|
function clearJiraInflight(): void {
|
||||||
inflightIssueRequests.clear()
|
inflightIssueRequests.clear()
|
||||||
inflightSearchRequests.clear()
|
inflightSearchRequests.clear()
|
||||||
|
|
@ -324,7 +333,9 @@ export const createJiraSlice: StateCreator<AppState, [], [], JiraSlice> = (set,
|
||||||
isIntegrationCredentialDecryptionError(error) &&
|
isIntegrationCredentialDecryptionError(error) &&
|
||||||
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
) {
|
) {
|
||||||
void get().checkJiraConnection()
|
if (!shouldRefreshStatusAfterRead(siteId, get().jiraStatus)) {
|
||||||
|
void get().checkJiraConnection()
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
looksLikeAuthError(error) &&
|
looksLikeAuthError(error) &&
|
||||||
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
|
|
@ -337,6 +348,12 @@ export const createJiraSlice: StateCreator<AppState, [], [], JiraSlice> = (set,
|
||||||
if (inflightIssueRequests.get(issueCacheKey) === entry) {
|
if (inflightIssueRequests.get(issueCacheKey) === entry) {
|
||||||
inflightIssueRequests.delete(issueCacheKey)
|
inflightIssueRequests.delete(issueCacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(siteId, get().jiraStatus) &&
|
||||||
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
|
) {
|
||||||
|
void get().checkJiraConnection()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
entry = { promise, contextKey, mutationGeneration: requestMutationGeneration }
|
entry = { promise, contextKey, mutationGeneration: requestMutationGeneration }
|
||||||
inflightIssueRequests.set(issueCacheKey, entry)
|
inflightIssueRequests.set(issueCacheKey, entry)
|
||||||
|
|
@ -382,7 +399,9 @@ export const createJiraSlice: StateCreator<AppState, [], [], JiraSlice> = (set,
|
||||||
isIntegrationCredentialDecryptionError(error) &&
|
isIntegrationCredentialDecryptionError(error) &&
|
||||||
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
) {
|
) {
|
||||||
void get().checkJiraConnection()
|
if (!shouldRefreshStatusAfterRead(siteId, get().jiraStatus)) {
|
||||||
|
void get().checkJiraConnection()
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
looksLikeAuthError(error) &&
|
looksLikeAuthError(error) &&
|
||||||
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
|
|
@ -395,6 +414,12 @@ export const createJiraSlice: StateCreator<AppState, [], [], JiraSlice> = (set,
|
||||||
if (inflightSearchRequests.get(cacheKey) === entry) {
|
if (inflightSearchRequests.get(cacheKey) === entry) {
|
||||||
inflightSearchRequests.delete(cacheKey)
|
inflightSearchRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(siteId, get().jiraStatus) &&
|
||||||
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
|
) {
|
||||||
|
void get().checkJiraConnection()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
entry = { promise, contextKey, mutationGeneration: requestMutationGeneration }
|
entry = { promise, contextKey, mutationGeneration: requestMutationGeneration }
|
||||||
inflightSearchRequests.set(cacheKey, entry)
|
inflightSearchRequests.set(cacheKey, entry)
|
||||||
|
|
@ -440,7 +465,9 @@ export const createJiraSlice: StateCreator<AppState, [], [], JiraSlice> = (set,
|
||||||
isIntegrationCredentialDecryptionError(error) &&
|
isIntegrationCredentialDecryptionError(error) &&
|
||||||
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
) {
|
) {
|
||||||
void get().checkJiraConnection()
|
if (!shouldRefreshStatusAfterRead(siteId, get().jiraStatus)) {
|
||||||
|
void get().checkJiraConnection()
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
looksLikeAuthError(error) &&
|
looksLikeAuthError(error) &&
|
||||||
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
|
|
@ -453,6 +480,12 @@ export const createJiraSlice: StateCreator<AppState, [], [], JiraSlice> = (set,
|
||||||
if (inflightListRequests.get(cacheKey) === entry) {
|
if (inflightListRequests.get(cacheKey) === entry) {
|
||||||
inflightListRequests.delete(cacheKey)
|
inflightListRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(siteId, get().jiraStatus) &&
|
||||||
|
canWriteJiraReadResult(contextKey, requestMutationGeneration, get().settings)
|
||||||
|
) {
|
||||||
|
void get().checkJiraConnection()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
entry = { promise, contextKey, mutationGeneration: requestMutationGeneration }
|
entry = { promise, contextKey, mutationGeneration: requestMutationGeneration }
|
||||||
inflightListRequests.set(cacheKey, entry)
|
inflightListRequests.set(cacheKey, entry)
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,86 @@ describe('createLinearSlice caching', () => {
|
||||||
expect(linearStatus).toHaveBeenCalled()
|
expect(linearStatus).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('clears stale Linear credential errors after successful workspace list reads', async () => {
|
||||||
|
const store = createTestStore()
|
||||||
|
const staleError = credentialDecryptionMessage('Linear')
|
||||||
|
store.setState({
|
||||||
|
linearStatus: {
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1',
|
||||||
|
credentialError: staleError
|
||||||
|
}
|
||||||
|
})
|
||||||
|
linearListIssues.mockResolvedValueOnce({ items: [issue('LIN-OK')] })
|
||||||
|
linearStatus.mockResolvedValueOnce({
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1'
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
store.getState().listLinearIssues('all', 36, { force: true })
|
||||||
|
).resolves.toMatchObject({ items: [{ id: 'LIN-OK' }] })
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(store.getState().linearStatus.credentialError).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears stale Linear credential errors after successful issue detail reads', async () => {
|
||||||
|
const store = createTestStore()
|
||||||
|
const staleError = credentialDecryptionMessage('Linear')
|
||||||
|
store.setState({
|
||||||
|
linearStatus: {
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1',
|
||||||
|
credentialError: staleError
|
||||||
|
}
|
||||||
|
})
|
||||||
|
linearGetIssue.mockResolvedValueOnce(issue('LIN-OK'))
|
||||||
|
linearStatus.mockResolvedValueOnce({
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1'
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(store.getState().fetchLinearIssue('LIN-OK', 'workspace-1')).resolves.toMatchObject(
|
||||||
|
{
|
||||||
|
id: 'LIN-OK'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(store.getState().linearStatus.credentialError).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears stale Linear credential errors after successful scoped collection reads', async () => {
|
||||||
|
const store = createTestStore()
|
||||||
|
const staleError = credentialDecryptionMessage('Linear')
|
||||||
|
store.setState({
|
||||||
|
linearStatus: {
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1',
|
||||||
|
credentialError: staleError
|
||||||
|
}
|
||||||
|
})
|
||||||
|
linearListProjectIssues.mockResolvedValueOnce({ items: [issue('LIN-OK')] })
|
||||||
|
linearStatus.mockResolvedValueOnce({
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1'
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
store.getState().listLinearProjectIssues('project-1', 'workspace-1', 20, { force: true })
|
||||||
|
).resolves.toMatchObject({ items: [{ id: 'LIN-OK' }] })
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(store.getState().linearStatus.credentialError).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('surfaces scoped project issue failures alongside cached rows', async () => {
|
it('surfaces scoped project issue failures alongside cached rows', async () => {
|
||||||
const store = createTestStore()
|
const store = createTestStore()
|
||||||
store.setState({
|
store.setState({
|
||||||
|
|
@ -484,14 +564,28 @@ describe('createLinearSlice caching', () => {
|
||||||
expect(linearListCustomViews.mock.calls[0][4]).toEqual({ force: true })
|
expect(linearListCustomViews.mock.calls[0][4]).toEqual({ force: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fetches custom views by exact id for saved-context restore', async () => {
|
it('fetches custom views by exact id and clears stale credential errors', async () => {
|
||||||
const store = createTestStore()
|
const store = createTestStore()
|
||||||
|
const staleError = credentialDecryptionMessage('Linear')
|
||||||
|
store.setState({
|
||||||
|
linearStatus: {
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1',
|
||||||
|
credentialError: staleError
|
||||||
|
}
|
||||||
|
})
|
||||||
linearGetCustomView.mockResolvedValueOnce({
|
linearGetCustomView.mockResolvedValueOnce({
|
||||||
id: 'view-1',
|
id: 'view-1',
|
||||||
name: 'Burn views',
|
name: 'Burn views',
|
||||||
model: 'project',
|
model: 'project',
|
||||||
workspaceId: 'workspace-1'
|
workspaceId: 'workspace-1'
|
||||||
})
|
})
|
||||||
|
linearStatus.mockResolvedValueOnce({
|
||||||
|
connected: true,
|
||||||
|
viewer: null,
|
||||||
|
selectedWorkspaceId: 'workspace-1'
|
||||||
|
})
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
store.getState().fetchLinearCustomView('view-1', 'workspace-1', 'project', { force: true })
|
store.getState().fetchLinearCustomView('view-1', 'workspace-1', 'project', { force: true })
|
||||||
|
|
@ -500,6 +594,9 @@ describe('createLinearSlice caching', () => {
|
||||||
expect(linearGetCustomView).toHaveBeenCalledWith(null, 'view-1', 'project', 'workspace-1', {
|
expect(linearGetCustomView).toHaveBeenCalledWith(null, 'view-1', 'project', 'workspace-1', {
|
||||||
force: true
|
force: true
|
||||||
})
|
})
|
||||||
|
await vi.waitFor(() => {
|
||||||
|
expect(store.getState().linearStatus.credentialError).toBeUndefined()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fails forced exact custom-view validation instead of reopening stale cache', async () => {
|
it('fails forced exact custom-view validation instead of reopening stale cache', async () => {
|
||||||
|
|
|
||||||
|
|
@ -250,9 +250,12 @@ function invalidateLinearCaches(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldRefreshStatusAfterRead(
|
function shouldRefreshStatusAfterRead(
|
||||||
workspaceId: LinearWorkspaceSelection | null | undefined
|
workspaceId: LinearWorkspaceSelection | null | undefined,
|
||||||
|
status: LinearConnectionStatus
|
||||||
): boolean {
|
): boolean {
|
||||||
return workspaceId === 'all'
|
// Why: 'all' reads can hide per-workspace decrypt failures, and a visible
|
||||||
|
// credential error may have been cleared by a successful credential read.
|
||||||
|
return workspaceId === 'all' || status.credentialError !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function linearCollectionCacheKey(
|
function linearCollectionCacheKey(
|
||||||
|
|
@ -851,6 +854,17 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
if (inflightIssueRequests.get(issueCacheKey) === entry) {
|
if (inflightIssueRequests.get(issueCacheKey) === entry) {
|
||||||
inflightIssueRequests.delete(issueCacheKey)
|
inflightIssueRequests.delete(issueCacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
|
canWriteLinearReadResult(
|
||||||
|
contextKey,
|
||||||
|
requestCacheGeneration,
|
||||||
|
requestMutationGeneration,
|
||||||
|
get().settings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
void get().checkLinearConnection(true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
|
|
@ -964,7 +978,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
get().settings
|
get().settings
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (!shouldRefreshStatusAfterRead(workspaceId)) {
|
if (!shouldRefreshStatusAfterRead(workspaceId, get().linearStatus)) {
|
||||||
void get().checkLinearConnection(true)
|
void get().checkLinearConnection(true)
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
|
|
@ -976,7 +990,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
inflightSearchRequests.delete(cacheKey)
|
inflightSearchRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
shouldRefreshStatusAfterRead(workspaceId) &&
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
canWriteLinearReadResult(
|
canWriteLinearReadResult(
|
||||||
contextKey,
|
contextKey,
|
||||||
requestCacheGeneration,
|
requestCacheGeneration,
|
||||||
|
|
@ -1059,7 +1073,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
get().settings
|
get().settings
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (!shouldRefreshStatusAfterRead(workspaceId)) {
|
if (!shouldRefreshStatusAfterRead(workspaceId, get().linearStatus)) {
|
||||||
void get().checkLinearConnection(true)
|
void get().checkLinearConnection(true)
|
||||||
}
|
}
|
||||||
return emptyLinearCollection<LinearIssue>()
|
return emptyLinearCollection<LinearIssue>()
|
||||||
|
|
@ -1071,7 +1085,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
inflightListRequests.delete(cacheKey)
|
inflightListRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
shouldRefreshStatusAfterRead(workspaceId) &&
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
canWriteLinearReadResult(
|
canWriteLinearReadResult(
|
||||||
contextKey,
|
contextKey,
|
||||||
requestCacheGeneration,
|
requestCacheGeneration,
|
||||||
|
|
@ -1153,7 +1167,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
get().settings
|
get().settings
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (!shouldRefreshStatusAfterRead(resolvedWorkspaceId)) {
|
if (!shouldRefreshStatusAfterRead(resolvedWorkspaceId, get().linearStatus)) {
|
||||||
void get().checkLinearConnection(true)
|
void get().checkLinearConnection(true)
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
|
|
@ -1165,7 +1179,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
inflightTeamRequests.delete(cacheKey)
|
inflightTeamRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
shouldRefreshStatusAfterRead(resolvedWorkspaceId) &&
|
shouldRefreshStatusAfterRead(resolvedWorkspaceId, get().linearStatus) &&
|
||||||
canWriteLinearReadResult(
|
canWriteLinearReadResult(
|
||||||
contextKey,
|
contextKey,
|
||||||
requestCacheGeneration,
|
requestCacheGeneration,
|
||||||
|
|
@ -1261,7 +1275,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
inflightProjectRequests.delete(cacheKey)
|
inflightProjectRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
shouldRefreshStatusAfterRead(resolvedWorkspaceId) &&
|
shouldRefreshStatusAfterRead(resolvedWorkspaceId, get().linearStatus) &&
|
||||||
canWriteLinearReadResult(
|
canWriteLinearReadResult(
|
||||||
contextKey,
|
contextKey,
|
||||||
requestCacheGeneration,
|
requestCacheGeneration,
|
||||||
|
|
@ -1353,6 +1367,17 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
if (inflightProjectDetailRequests.get(cacheKey) === entry) {
|
if (inflightProjectDetailRequests.get(cacheKey) === entry) {
|
||||||
inflightProjectDetailRequests.delete(cacheKey)
|
inflightProjectDetailRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
|
canWriteLinearReadResult(
|
||||||
|
contextKey,
|
||||||
|
requestCacheGeneration,
|
||||||
|
requestMutationGeneration,
|
||||||
|
get().settings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
void get().checkLinearConnection(true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
|
|
@ -1449,6 +1474,17 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
if (inflightProjectIssueRequests.get(cacheKey) === entry) {
|
if (inflightProjectIssueRequests.get(cacheKey) === entry) {
|
||||||
inflightProjectIssueRequests.delete(cacheKey)
|
inflightProjectIssueRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
|
canWriteLinearReadResult(
|
||||||
|
contextKey,
|
||||||
|
requestCacheGeneration,
|
||||||
|
requestMutationGeneration,
|
||||||
|
get().settings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
void get().checkLinearConnection(true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
|
|
@ -1535,7 +1571,7 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
inflightCustomViewRequests.delete(cacheKey)
|
inflightCustomViewRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
shouldRefreshStatusAfterRead(resolvedWorkspaceId) &&
|
shouldRefreshStatusAfterRead(resolvedWorkspaceId, get().linearStatus) &&
|
||||||
canWriteLinearReadResult(
|
canWriteLinearReadResult(
|
||||||
contextKey,
|
contextKey,
|
||||||
requestCacheGeneration,
|
requestCacheGeneration,
|
||||||
|
|
@ -1627,6 +1663,17 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
if (inflightCustomViewDetailRequests.get(cacheKey) === entry) {
|
if (inflightCustomViewDetailRequests.get(cacheKey) === entry) {
|
||||||
inflightCustomViewDetailRequests.delete(cacheKey)
|
inflightCustomViewDetailRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
|
canWriteLinearReadResult(
|
||||||
|
contextKey,
|
||||||
|
requestCacheGeneration,
|
||||||
|
requestMutationGeneration,
|
||||||
|
get().settings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
void get().checkLinearConnection(true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
|
|
@ -1723,6 +1770,17 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
if (inflightCustomViewIssueRequests.get(cacheKey) === entry) {
|
if (inflightCustomViewIssueRequests.get(cacheKey) === entry) {
|
||||||
inflightCustomViewIssueRequests.delete(cacheKey)
|
inflightCustomViewIssueRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
|
canWriteLinearReadResult(
|
||||||
|
contextKey,
|
||||||
|
requestCacheGeneration,
|
||||||
|
requestMutationGeneration,
|
||||||
|
get().settings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
void get().checkLinearConnection(true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
|
|
@ -1801,6 +1859,17 @@ export const createLinearSlice: StateCreator<AppState, [], [], LinearSlice> = (s
|
||||||
if (inflightCustomViewProjectRequests.get(cacheKey) === entry) {
|
if (inflightCustomViewProjectRequests.get(cacheKey) === entry) {
|
||||||
inflightCustomViewProjectRequests.delete(cacheKey)
|
inflightCustomViewProjectRequests.delete(cacheKey)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
shouldRefreshStatusAfterRead(workspaceId, get().linearStatus) &&
|
||||||
|
canWriteLinearReadResult(
|
||||||
|
contextKey,
|
||||||
|
requestCacheGeneration,
|
||||||
|
requestMutationGeneration,
|
||||||
|
get().settings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
void get().checkLinearConnection(true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue