mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-07 06:53:09 -06:00
Run gofmt over the code
This commit is contained in:
parent
d98c33e733
commit
9aceb04b98
|
|
@ -196,14 +196,14 @@ func fillPublicRoomsReq(httpReq *http.Request, request *PublicRoomReq) *util.JSO
|
||||||
|
|
||||||
// sliceInto returns a subslice of `slice` which honours the since/limit values given.
|
// sliceInto returns a subslice of `slice` which honours the since/limit values given.
|
||||||
//
|
//
|
||||||
// 0 1 2 3 4 5 6 index
|
// 0 1 2 3 4 5 6 index
|
||||||
// [A, B, C, D, E, F, G] slice
|
// [A, B, C, D, E, F, G] slice
|
||||||
//
|
//
|
||||||
// limit=3 => A,B,C (prev='', next='3')
|
// limit=3 => A,B,C (prev='', next='3')
|
||||||
// limit=3&since=3 => D,E,F (prev='0', next='6')
|
// limit=3&since=3 => D,E,F (prev='0', next='6')
|
||||||
// limit=3&since=6 => G (prev='3', next='')
|
// limit=3&since=6 => G (prev='3', next='')
|
||||||
//
|
//
|
||||||
// A value of '-1' for prev/next indicates no position.
|
// A value of '-1' for prev/next indicates no position.
|
||||||
func sliceInto(slice []gomatrixserverlib.PublicRoom, since int64, limit int16) (subset []gomatrixserverlib.PublicRoom, prev, next int) {
|
func sliceInto(slice []gomatrixserverlib.PublicRoom, since int64, limit int16) (subset []gomatrixserverlib.PublicRoom, prev, next int) {
|
||||||
prev = -1
|
prev = -1
|
||||||
next = -1
|
next = -1
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,10 @@ var sendEventDuration = prometheus.NewHistogramVec(
|
||||||
)
|
)
|
||||||
|
|
||||||
// SendEvent implements:
|
// SendEvent implements:
|
||||||
// /rooms/{roomID}/send/{eventType}
|
//
|
||||||
// /rooms/{roomID}/send/{eventType}/{txnID}
|
// /rooms/{roomID}/send/{eventType}
|
||||||
// /rooms/{roomID}/state/{eventType}/{stateKey}
|
// /rooms/{roomID}/send/{eventType}/{txnID}
|
||||||
|
// /rooms/{roomID}/state/{eventType}/{stateKey}
|
||||||
func SendEvent(
|
func SendEvent(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
device *userapi.Device,
|
device *userapi.Device,
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,9 @@ type threePIDsResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestEmailToken implements:
|
// RequestEmailToken implements:
|
||||||
// POST /account/3pid/email/requestToken
|
//
|
||||||
// POST /register/email/requestToken
|
// POST /account/3pid/email/requestToken
|
||||||
|
// POST /register/email/requestToken
|
||||||
func RequestEmailToken(req *http.Request, threePIDAPI api.ClientUserAPI, cfg *config.ClientAPI) util.JSONResponse {
|
func RequestEmailToken(req *http.Request, threePIDAPI api.ClientUserAPI, cfg *config.ClientAPI) util.JSONResponse {
|
||||||
var body threepid.EmailAssociationRequest
|
var body threepid.EmailAssociationRequest
|
||||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequestTurnServer implements:
|
// RequestTurnServer implements:
|
||||||
// GET /voip/turnServer
|
//
|
||||||
|
// GET /voip/turnServer
|
||||||
func RequestTurnServer(req *http.Request, device *api.Device, cfg *config.ClientAPI) util.JSONResponse {
|
func RequestTurnServer(req *http.Request, device *api.Device, cfg *config.ClientAPI) util.JSONResponse {
|
||||||
turnConfig := cfg.TURN
|
turnConfig := cfg.TURN
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,9 @@ const HEAD = "HEAD"
|
||||||
// We cannot use the dockerfile associated with the repo with each version sadly due to changes in
|
// We cannot use the dockerfile associated with the repo with each version sadly due to changes in
|
||||||
// Docker versions. Specifically, earlier Dendrite versions are incompatible with newer Docker clients
|
// Docker versions. Specifically, earlier Dendrite versions are incompatible with newer Docker clients
|
||||||
// due to the error:
|
// due to the error:
|
||||||
// When using COPY with more than one source file, the destination must be a directory and end with a /
|
//
|
||||||
|
// When using COPY with more than one source file, the destination must be a directory and end with a /
|
||||||
|
//
|
||||||
// We need to run a postgres anyway, so use the dockerfile associated with Complement instead.
|
// We need to run a postgres anyway, so use the dockerfile associated with Complement instead.
|
||||||
const Dockerfile = `FROM golang:1.18-stretch as build
|
const Dockerfile = `FROM golang:1.18-stretch as build
|
||||||
RUN apt-get update && apt-get install -y postgresql
|
RUN apt-get update && apt-get install -y postgresql
|
||||||
|
|
@ -95,7 +97,9 @@ CMD /build/run_dendrite.sh `
|
||||||
const dendriteUpgradeTestLabel = "dendrite_upgrade_test"
|
const dendriteUpgradeTestLabel = "dendrite_upgrade_test"
|
||||||
|
|
||||||
// downloadArchive downloads an arbitrary github archive of the form:
|
// downloadArchive downloads an arbitrary github archive of the form:
|
||||||
// https://github.com/matrix-org/dendrite/archive/v0.3.11.tar.gz
|
//
|
||||||
|
// https://github.com/matrix-org/dendrite/archive/v0.3.11.tar.gz
|
||||||
|
//
|
||||||
// and re-tarballs it without the top-level directory which contains branch information. It inserts
|
// and re-tarballs it without the top-level directory which contains branch information. It inserts
|
||||||
// the contents of `dockerfile` as a root file `Dockerfile` in the re-tarballed directory such that
|
// the contents of `dockerfile` as a root file `Dockerfile` in the re-tarballed directory such that
|
||||||
// you can directly feed the retarballed archive to `ImageBuild` to have it run said dockerfile.
|
// you can directly feed the retarballed archive to `ImageBuild` to have it run said dockerfile.
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ type user struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// runTests performs the following operations:
|
// runTests performs the following operations:
|
||||||
// - register alice and bob with branch name muxed into the localpart
|
// - register alice and bob with branch name muxed into the localpart
|
||||||
// - create a DM room for the 2 users and exchange messages
|
// - create a DM room for the 2 users and exchange messages
|
||||||
// - create/join a public #global room and exchange messages
|
// - create/join a public #global room and exchange messages
|
||||||
func runTests(baseURL, branchName string) error {
|
func runTests(baseURL, branchName string) error {
|
||||||
// register 2 users
|
// register 2 users
|
||||||
users := []user{
|
users := []user{
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,16 @@ type JSServer struct {
|
||||||
|
|
||||||
// OnRequestFromJS is the function that JS will invoke when there is a new request.
|
// OnRequestFromJS is the function that JS will invoke when there is a new request.
|
||||||
// The JS function signature is:
|
// The JS function signature is:
|
||||||
// function(reqString: string): Promise<{result: string, error: string}>
|
//
|
||||||
|
// function(reqString: string): Promise<{result: string, error: string}>
|
||||||
|
//
|
||||||
// Usage is like:
|
// Usage is like:
|
||||||
// const res = await global._go_js_server.fetch(reqString);
|
//
|
||||||
// if (res.error) {
|
// const res = await global._go_js_server.fetch(reqString);
|
||||||
// // handle error: this is a 'network' error, not a non-2xx error.
|
// if (res.error) {
|
||||||
// }
|
// // handle error: this is a 'network' error, not a non-2xx error.
|
||||||
// const rawHttpResponse = res.result;
|
// }
|
||||||
|
// const rawHttpResponse = res.result;
|
||||||
func (h *JSServer) OnRequestFromJS(this js.Value, args []js.Value) interface{} {
|
func (h *JSServer) OnRequestFromJS(this js.Value, args []js.Value) interface{} {
|
||||||
// we HAVE to spawn a new goroutine and return immediately or else Go will deadlock
|
// we HAVE to spawn a new goroutine and return immediately or else Go will deadlock
|
||||||
// if this request blocks at all e.g for /sync calls
|
// if this request blocks at all e.g for /sync calls
|
||||||
|
|
|
||||||
|
|
@ -208,9 +208,10 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent, rew
|
||||||
// joinedHostsAtEvent works out a list of matrix servers that were joined to
|
// joinedHostsAtEvent works out a list of matrix servers that were joined to
|
||||||
// the room at the event (including peeking ones)
|
// the room at the event (including peeking ones)
|
||||||
// It is important to use the state at the event for sending messages because:
|
// It is important to use the state at the event for sending messages because:
|
||||||
// 1) We shouldn't send messages to servers that weren't in the room.
|
// 1. We shouldn't send messages to servers that weren't in the room.
|
||||||
// 2) If a server is kicked from the rooms it should still be told about the
|
// 2. If a server is kicked from the rooms it should still be told about the
|
||||||
// kick event,
|
// kick event,
|
||||||
|
//
|
||||||
// Usually the list can be calculated locally, but sometimes it will need fetch
|
// Usually the list can be calculated locally, but sometimes it will need fetch
|
||||||
// events from the room server.
|
// events from the room server.
|
||||||
// Returns an error if there was a problem talking to the room server.
|
// Returns an error if there was a problem talking to the room server.
|
||||||
|
|
|
||||||
|
|
@ -435,13 +435,13 @@ func SendJoin(
|
||||||
// a restricted room join. If the room version does not support restricted
|
// a restricted room join. If the room version does not support restricted
|
||||||
// joins then this function returns with no side effects. This returns three
|
// joins then this function returns with no side effects. This returns three
|
||||||
// values:
|
// values:
|
||||||
// * an optional JSON response body (i.e. M_UNABLE_TO_AUTHORISE_JOIN) which
|
// - an optional JSON response body (i.e. M_UNABLE_TO_AUTHORISE_JOIN) which
|
||||||
// should always be sent back to the client if one is specified
|
// should always be sent back to the client if one is specified
|
||||||
// * a user ID of an authorising user, typically a user that has power to
|
// - a user ID of an authorising user, typically a user that has power to
|
||||||
// issue invites in the room, if one has been found
|
// issue invites in the room, if one has been found
|
||||||
// * an error if there was a problem finding out if this was allowable,
|
// - an error if there was a problem finding out if this was allowable,
|
||||||
// like if the room version isn't known or a problem happened talking to
|
// like if the room version isn't known or a problem happened talking to
|
||||||
// the roomserver
|
// the roomserver
|
||||||
func checkRestrictedJoin(
|
func checkRestrictedJoin(
|
||||||
httpReq *http.Request,
|
httpReq *http.Request,
|
||||||
rsAPI api.FederationRoomserverAPI,
|
rsAPI api.FederationRoomserverAPI,
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ func setupFileHook(hook config.LogrusHook, level logrus.Level, componentName str
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//CloseAndLogIfError Closes io.Closer and logs the error if any
|
// CloseAndLogIfError Closes io.Closer and logs the error if any
|
||||||
func CloseAndLogIfError(ctx context.Context, closer io.Closer, message string) {
|
func CloseAndLogIfError(ctx context.Context, closer io.Closer, message string) {
|
||||||
if closer == nil {
|
if closer == nil {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,14 @@ func init() {
|
||||||
// - We don't have unbounded growth in proportion to the number of servers (this is more important in a P2P world where
|
// - We don't have unbounded growth in proportion to the number of servers (this is more important in a P2P world where
|
||||||
// we have many many servers)
|
// we have many many servers)
|
||||||
// - We can adjust concurrency (at the cost of memory usage) by tuning N, to accommodate mobile devices vs servers.
|
// - We can adjust concurrency (at the cost of memory usage) by tuning N, to accommodate mobile devices vs servers.
|
||||||
|
//
|
||||||
// The downsides are that:
|
// The downsides are that:
|
||||||
// - Query requests can get queued behind other servers if they hash to the same worker, even if there are other free
|
// - Query requests can get queued behind other servers if they hash to the same worker, even if there are other free
|
||||||
// workers elsewhere. Whilst suboptimal, provided we cap how long a single request can last (e.g using context timeouts)
|
// workers elsewhere. Whilst suboptimal, provided we cap how long a single request can last (e.g using context timeouts)
|
||||||
// we guarantee we will get around to it. Also, more users on a given server does not increase the number of requests
|
// we guarantee we will get around to it. Also, more users on a given server does not increase the number of requests
|
||||||
// (as /keys/query allows multiple users to be specified) so being stuck behind matrix.org won't materially be any worse
|
// (as /keys/query allows multiple users to be specified) so being stuck behind matrix.org won't materially be any worse
|
||||||
// than being stuck behind foo.bar
|
// than being stuck behind foo.bar
|
||||||
|
//
|
||||||
// In the event that the query fails, a lock is acquired and the server name along with the time to wait before retrying is
|
// In the event that the query fails, a lock is acquired and the server name along with the time to wait before retrying is
|
||||||
// set in a map. A restarter goroutine periodically probes this map and injects servers which are ready to be retried.
|
// set in a map. A restarter goroutine periodically probes this map and injects servers which are ready to be retried.
|
||||||
type DeviceListUpdater struct {
|
type DeviceListUpdater struct {
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,9 @@ import (
|
||||||
// per-room durable consumers will only progress through the stream
|
// per-room durable consumers will only progress through the stream
|
||||||
// as events are processed.
|
// as events are processed.
|
||||||
//
|
//
|
||||||
// A BC * -> positions of each consumer (* = ephemeral)
|
// A BC * -> positions of each consumer (* = ephemeral)
|
||||||
// ⌄ ⌄⌄ ⌄
|
// ⌄ ⌄⌄ ⌄
|
||||||
|
//
|
||||||
// ABAABCAABCAA -> newest (letter = subject for each message)
|
// ABAABCAABCAA -> newest (letter = subject for each message)
|
||||||
//
|
//
|
||||||
// In this example, A is still processing an event but has two
|
// In this example, A is still processing an event but has two
|
||||||
|
|
|
||||||
|
|
@ -35,17 +35,17 @@ import (
|
||||||
// event to the output log.
|
// event to the output log.
|
||||||
// The latest events are the events that aren't referenced by another event in the database:
|
// The latest events are the events that aren't referenced by another event in the database:
|
||||||
//
|
//
|
||||||
// Time goes down the page. 1 is the m.room.create event (root).
|
// Time goes down the page. 1 is the m.room.create event (root).
|
||||||
//
|
//
|
||||||
// 1 After storing 1 the latest events are {1}
|
// 1 After storing 1 the latest events are {1}
|
||||||
// | After storing 2 the latest events are {2}
|
// | After storing 2 the latest events are {2}
|
||||||
// 2 After storing 3 the latest events are {3}
|
// 2 After storing 3 the latest events are {3}
|
||||||
// / \ After storing 4 the latest events are {3,4}
|
// / \ After storing 4 the latest events are {3,4}
|
||||||
// 3 4 After storing 5 the latest events are {5,4}
|
// 3 4 After storing 5 the latest events are {5,4}
|
||||||
// | | After storing 6 the latest events are {5,6}
|
// | | After storing 6 the latest events are {5,6}
|
||||||
// 5 6 <--- latest After storing 7 the latest events are {6,7}
|
// 5 6 <--- latest After storing 7 the latest events are {6,7}
|
||||||
// |
|
// |
|
||||||
// 7 <----- latest
|
// 7 <----- latest
|
||||||
//
|
//
|
||||||
// Can only be called once at a time
|
// Can only be called once at a time
|
||||||
func (r *Inputer) updateLatestEvents(
|
func (r *Inputer) updateLatestEvents(
|
||||||
|
|
|
||||||
|
|
@ -523,7 +523,8 @@ func (b *backfillRequester) ProvideEvents(roomVer gomatrixserverlib.RoomVersion,
|
||||||
|
|
||||||
// joinEventsFromHistoryVisibility returns all CURRENTLY joined members if our server can read the room history
|
// joinEventsFromHistoryVisibility returns all CURRENTLY joined members if our server can read the room history
|
||||||
// TODO: Long term we probably want a history_visibility table which stores eventNID | visibility_enum so we can just
|
// TODO: Long term we probably want a history_visibility table which stores eventNID | visibility_enum so we can just
|
||||||
// pull all events and then filter by that table.
|
//
|
||||||
|
// pull all events and then filter by that table.
|
||||||
func joinEventsFromHistoryVisibility(
|
func joinEventsFromHistoryVisibility(
|
||||||
ctx context.Context, db storage.Database, roomID string, stateEntries []types.StateEntry,
|
ctx context.Context, db storage.Database, roomID string, stateEntries []types.StateEntry,
|
||||||
thisServer gomatrixserverlib.ServerName) ([]types.Event, error) {
|
thisServer gomatrixserverlib.ServerName) ([]types.Event, error) {
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,9 @@ type StrippedEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractContentValue from the given state event. For example, given an m.room.name event with:
|
// ExtractContentValue from the given state event. For example, given an m.room.name event with:
|
||||||
// content: { name: "Foo" }
|
//
|
||||||
|
// content: { name: "Foo" }
|
||||||
|
//
|
||||||
// this returns "Foo".
|
// this returns "Foo".
|
||||||
func ExtractContentValue(ev *gomatrixserverlib.HeaderedEvent) string {
|
func ExtractContentValue(ev *gomatrixserverlib.HeaderedEvent) string {
|
||||||
content := ev.Content()
|
content := ev.Content()
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,19 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Basic sanity check of MSC2836 logic. Injects a thread that looks like:
|
// Basic sanity check of MSC2836 logic. Injects a thread that looks like:
|
||||||
// A
|
//
|
||||||
// |
|
// A
|
||||||
// B
|
// |
|
||||||
// / \
|
// B
|
||||||
|
// / \
|
||||||
|
//
|
||||||
// C D
|
// C D
|
||||||
// /|\
|
//
|
||||||
// E F G
|
// /|\
|
||||||
// |
|
// E F G
|
||||||
// H
|
// |
|
||||||
|
// H
|
||||||
|
//
|
||||||
// And makes sure POST /event_relationships works with various parameters
|
// And makes sure POST /event_relationships works with various parameters
|
||||||
func TestMSC2836(t *testing.T) {
|
func TestMSC2836(t *testing.T) {
|
||||||
alice := "@alice:localhost"
|
alice := "@alice:localhost"
|
||||||
|
|
|
||||||
|
|
@ -364,13 +364,14 @@ func TestKeyChangeCatchupChangeAndLeft(t *testing.T) {
|
||||||
|
|
||||||
// tests that joining/leaving the SAME room puts users in `left` if the final state is leave.
|
// tests that joining/leaving the SAME room puts users in `left` if the final state is leave.
|
||||||
// NB: Consider the case:
|
// NB: Consider the case:
|
||||||
// - Alice and Bob are in a room.
|
// - Alice and Bob are in a room.
|
||||||
// - Alice goes offline, Charlie joins, sends encrypted messages then leaves the room.
|
// - Alice goes offline, Charlie joins, sends encrypted messages then leaves the room.
|
||||||
// - Alice comes back online. Technically nothing has changed in the set of users between those two points in time,
|
// - Alice comes back online. Technically nothing has changed in the set of users between those two points in time,
|
||||||
// it's still just (Alice,Bob) but then we won't be tracking Charlie -- is this okay though? It's device keys
|
// it's still just (Alice,Bob) but then we won't be tracking Charlie -- is this okay though? It's device keys
|
||||||
// which are only relevant when actively sending events I think? And if Alice does need the keys she knows
|
// which are only relevant when actively sending events I think? And if Alice does need the keys she knows
|
||||||
// charlie's (user_id, device_id) so can just hit /keys/query - no need to keep updated about it because she
|
// charlie's (user_id, device_id) so can just hit /keys/query - no need to keep updated about it because she
|
||||||
// doesn't share any rooms with him.
|
// doesn't share any rooms with him.
|
||||||
|
//
|
||||||
// Ergo, we put them in `left` as it is simpler.
|
// Ergo, we put them in `left` as it is simpler.
|
||||||
func TestKeyChangeCatchupChangeAndLeftSameRoom(t *testing.T) {
|
func TestKeyChangeCatchupChangeAndLeftSameRoom(t *testing.T) {
|
||||||
newShareUser := "@berta:localhost"
|
newShareUser := "@berta:localhost"
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ type filterResponse struct {
|
||||||
FilterID string `json:"filter_id"`
|
FilterID string `json:"filter_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//PutFilter implements POST /_matrix/client/r0/user/{userId}/filter
|
// PutFilter implements POST /_matrix/client/r0/user/{userId}/filter
|
||||||
func PutFilter(
|
func PutFilter(
|
||||||
req *http.Request, device *api.Device, syncDB storage.Database, userID string,
|
req *http.Request, device *api.Device, syncDB storage.Database, userID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
|
|
||||||
|
|
@ -122,12 +122,14 @@ type CurrentRoomState interface {
|
||||||
//
|
//
|
||||||
// We persist the previous event IDs as well, one per row, so when we do fetch even
|
// We persist the previous event IDs as well, one per row, so when we do fetch even
|
||||||
// earlier events we can simply delete rows which referenced it. Consider the graph:
|
// earlier events we can simply delete rows which referenced it. Consider the graph:
|
||||||
// A
|
//
|
||||||
// | Event C has 1 prev_event ID: A.
|
// A
|
||||||
// B C
|
// | Event C has 1 prev_event ID: A.
|
||||||
// |___| Event D has 2 prev_event IDs: B and C.
|
// B C
|
||||||
// |
|
// |___| Event D has 2 prev_event IDs: B and C.
|
||||||
// D
|
// |
|
||||||
|
// D
|
||||||
|
//
|
||||||
// The earliest known event we have is D, so this table has 2 rows.
|
// The earliest known event we have is D, so this table has 2 rows.
|
||||||
// A backfill request gives us C but not B. We delete rows where prev_event=C. This
|
// A backfill request gives us C but not B. We delete rows where prev_event=C. This
|
||||||
// still means that D is a backwards extremity as we do not have event B. However, event
|
// still means that D is a backwards extremity as we do not have event B. However, event
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,9 @@ func (s *statsStatements) monthlyUsers(ctx context.Context, txn *sql.Tx) (result
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* R30Users counts the number of 30 day retained users, defined as:
|
/*
|
||||||
|
R30Users counts the number of 30 day retained users, defined as:
|
||||||
|
|
||||||
- Users who have created their accounts more than 30 days ago
|
- Users who have created their accounts more than 30 days ago
|
||||||
- Where last seen at most 30 days ago
|
- Where last seen at most 30 days ago
|
||||||
- Where account creation and last_seen are > 30 days apart
|
- Where account creation and last_seen are > 30 days apart
|
||||||
|
|
@ -334,7 +336,9 @@ func (s *statsStatements) r30Users(ctx context.Context, txn *sql.Tx) (map[string
|
||||||
return result, rows.Err()
|
return result, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* R30UsersV2 counts the number of 30 day retained users, defined as users that:
|
/*
|
||||||
|
R30UsersV2 counts the number of 30 day retained users, defined as users that:
|
||||||
|
|
||||||
- Appear more than once in the past 60 days
|
- Appear more than once in the past 60 days
|
||||||
- Have more than 30 days between the most and least recent appearances that occurred in the past 60 days.
|
- Have more than 30 days between the most and least recent appearances that occurred in the past 60 days.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue